wbcrm 1.56.8__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wbcrm/__init__.py +1 -0
- wbcrm/admin/__init__.py +5 -0
- wbcrm/admin/accounts.py +60 -0
- wbcrm/admin/activities.py +104 -0
- wbcrm/admin/events.py +43 -0
- wbcrm/admin/groups.py +8 -0
- wbcrm/admin/products.py +9 -0
- wbcrm/apps.py +5 -0
- wbcrm/configurations/__init__.py +1 -0
- wbcrm/configurations/base.py +16 -0
- wbcrm/dynamic_preferences_registry.py +38 -0
- wbcrm/factories/__init__.py +14 -0
- wbcrm/factories/accounts.py +57 -0
- wbcrm/factories/activities.py +124 -0
- wbcrm/factories/groups.py +24 -0
- wbcrm/factories/products.py +11 -0
- wbcrm/filters/__init__.py +10 -0
- wbcrm/filters/accounts.py +80 -0
- wbcrm/filters/activities.py +204 -0
- wbcrm/filters/groups.py +21 -0
- wbcrm/filters/products.py +38 -0
- wbcrm/filters/signals.py +95 -0
- wbcrm/fixtures/wbcrm.json +1215 -0
- wbcrm/kpi_handlers/activities.py +171 -0
- wbcrm/locale/de/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/de/LC_MESSAGES/django.po +1557 -0
- wbcrm/locale/de/LC_MESSAGES/django.po.translated +1630 -0
- wbcrm/locale/en/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/en/LC_MESSAGES/django.po +1466 -0
- wbcrm/locale/fr/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/fr/LC_MESSAGES/django.po +1467 -0
- wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
- wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
- wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
- wbcrm/migrations/0004_alter_activity_status.py +28 -0
- wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
- wbcrm/migrations/0006_alter_activity_location.py +17 -0
- wbcrm/migrations/0007_alter_account_status.py +23 -0
- wbcrm/migrations/0008_alter_activity_options.py +16 -0
- wbcrm/migrations/0009_alter_account_is_public.py +19 -0
- wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
- wbcrm/migrations/0011_activity_summary.py +22 -0
- wbcrm/migrations/0012_alter_activity_summary.py +17 -0
- wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
- wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
- wbcrm/migrations/0015_alter_activity_type.py +23 -0
- wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
- wbcrm/migrations/0017_event.py +40 -0
- wbcrm/migrations/0018_activity_search_vector.py +24 -0
- wbcrm/migrations/__init__.py +0 -0
- wbcrm/models/__init__.py +5 -0
- wbcrm/models/accounts.py +648 -0
- wbcrm/models/activities.py +1419 -0
- wbcrm/models/events.py +15 -0
- wbcrm/models/groups.py +119 -0
- wbcrm/models/llm/activity_summaries.py +41 -0
- wbcrm/models/llm/analyze_relationship.py +50 -0
- wbcrm/models/products.py +86 -0
- wbcrm/models/recurrence.py +280 -0
- wbcrm/preferences.py +13 -0
- wbcrm/report/activity_report.py +110 -0
- wbcrm/serializers/__init__.py +23 -0
- wbcrm/serializers/accounts.py +141 -0
- wbcrm/serializers/activities.py +525 -0
- wbcrm/serializers/groups.py +30 -0
- wbcrm/serializers/products.py +58 -0
- wbcrm/serializers/recurrence.py +91 -0
- wbcrm/serializers/signals.py +71 -0
- wbcrm/static/wbcrm/markdown/documentation/activity.md +86 -0
- wbcrm/static/wbcrm/markdown/documentation/activitytype.md +20 -0
- wbcrm/static/wbcrm/markdown/documentation/group.md +2 -0
- wbcrm/static/wbcrm/markdown/documentation/product.md +11 -0
- wbcrm/synchronization/__init__.py +0 -0
- wbcrm/synchronization/activity/__init__.py +0 -0
- wbcrm/synchronization/activity/admin.py +73 -0
- wbcrm/synchronization/activity/backend.py +214 -0
- wbcrm/synchronization/activity/backends/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
- wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +406 -0
- wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/create.py +75 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/delete.py +78 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/update.py +155 -0
- wbcrm/synchronization/activity/backends/google/request_utils/internal_to_external/update.py +181 -0
- wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
- wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
- wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
- wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
- wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
- wbcrm/synchronization/activity/backends/google/utils.py +217 -0
- wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/backend.py +593 -0
- wbcrm/synchronization/activity/backends/outlook/msgraph.py +436 -0
- wbcrm/synchronization/activity/backends/outlook/parser.py +432 -0
- wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +118 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +274 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +249 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +174 -0
- wbcrm/synchronization/activity/controller.py +627 -0
- wbcrm/synchronization/activity/dynamic_preferences_registry.py +119 -0
- wbcrm/synchronization/activity/preferences.py +27 -0
- wbcrm/synchronization/activity/shortcuts.py +16 -0
- wbcrm/synchronization/activity/tasks.py +21 -0
- wbcrm/synchronization/activity/urls.py +7 -0
- wbcrm/synchronization/activity/utils.py +46 -0
- wbcrm/synchronization/activity/views.py +41 -0
- wbcrm/synchronization/admin.py +1 -0
- wbcrm/synchronization/apps.py +14 -0
- wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
- wbcrm/synchronization/management.py +36 -0
- wbcrm/synchronization/tasks.py +1 -0
- wbcrm/synchronization/urls.py +5 -0
- wbcrm/tasks.py +264 -0
- wbcrm/templates/email/activity.html +98 -0
- wbcrm/templates/email/activity_report.html +6 -0
- wbcrm/templates/email/daily_summary.html +72 -0
- wbcrm/templates/email/global_daily_summary.html +85 -0
- wbcrm/tests/__init__.py +0 -0
- wbcrm/tests/accounts/__init__.py +0 -0
- wbcrm/tests/accounts/test_models.py +393 -0
- wbcrm/tests/accounts/test_viewsets.py +88 -0
- wbcrm/tests/conftest.py +76 -0
- wbcrm/tests/disable_signals.py +62 -0
- wbcrm/tests/e2e/__init__.py +1 -0
- wbcrm/tests/e2e/e2e_wbcrm_utility.py +83 -0
- wbcrm/tests/e2e/test_e2e.py +370 -0
- wbcrm/tests/test_assignee_methods.py +40 -0
- wbcrm/tests/test_chartviewsets.py +112 -0
- wbcrm/tests/test_dto.py +64 -0
- wbcrm/tests/test_filters.py +52 -0
- wbcrm/tests/test_models.py +217 -0
- wbcrm/tests/test_recurrence.py +292 -0
- wbcrm/tests/test_report.py +21 -0
- wbcrm/tests/test_serializers.py +171 -0
- wbcrm/tests/test_tasks.py +95 -0
- wbcrm/tests/test_viewsets.py +967 -0
- wbcrm/tests/tests.py +121 -0
- wbcrm/typings.py +109 -0
- wbcrm/urls.py +67 -0
- wbcrm/viewsets/__init__.py +22 -0
- wbcrm/viewsets/accounts.py +122 -0
- wbcrm/viewsets/activities.py +341 -0
- wbcrm/viewsets/buttons/__init__.py +7 -0
- wbcrm/viewsets/buttons/accounts.py +27 -0
- wbcrm/viewsets/buttons/activities.py +89 -0
- wbcrm/viewsets/buttons/signals.py +17 -0
- wbcrm/viewsets/display/__init__.py +12 -0
- wbcrm/viewsets/display/accounts.py +110 -0
- wbcrm/viewsets/display/activities.py +444 -0
- wbcrm/viewsets/display/groups.py +22 -0
- wbcrm/viewsets/display/products.py +105 -0
- wbcrm/viewsets/endpoints/__init__.py +8 -0
- wbcrm/viewsets/endpoints/accounts.py +25 -0
- wbcrm/viewsets/endpoints/activities.py +30 -0
- wbcrm/viewsets/endpoints/groups.py +7 -0
- wbcrm/viewsets/endpoints/products.py +9 -0
- wbcrm/viewsets/groups.py +38 -0
- wbcrm/viewsets/menu/__init__.py +8 -0
- wbcrm/viewsets/menu/accounts.py +18 -0
- wbcrm/viewsets/menu/activities.py +49 -0
- wbcrm/viewsets/menu/groups.py +16 -0
- wbcrm/viewsets/menu/products.py +20 -0
- wbcrm/viewsets/mixins.py +35 -0
- wbcrm/viewsets/previews/__init__.py +1 -0
- wbcrm/viewsets/previews/activities.py +10 -0
- wbcrm/viewsets/products.py +57 -0
- wbcrm/viewsets/recurrence.py +27 -0
- wbcrm/viewsets/titles/__init__.py +13 -0
- wbcrm/viewsets/titles/accounts.py +23 -0
- wbcrm/viewsets/titles/activities.py +61 -0
- wbcrm/viewsets/titles/products.py +13 -0
- wbcrm/viewsets/titles/utils.py +46 -0
- wbcrm/workflows/__init__.py +1 -0
- wbcrm/workflows/assignee_methods.py +25 -0
- wbcrm-1.56.8.dist-info/METADATA +11 -0
- wbcrm-1.56.8.dist-info/RECORD +182 -0
- wbcrm-1.56.8.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from django.db.models import Model
|
|
2
|
+
from django.forms import ValidationError
|
|
3
|
+
from django.utils.translation import gettext
|
|
4
|
+
from rest_framework import serializers
|
|
5
|
+
from rest_framework.reverse import reverse
|
|
6
|
+
from wbcore import serializers as wb_serializers
|
|
7
|
+
|
|
8
|
+
from wbcrm.models.recurrence import Recurrence
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RecurrenceModelSerializerMixin:
|
|
12
|
+
@wb_serializers.register_only_instance_resource()
|
|
13
|
+
def next_occurrence(self, instance: Model, request, user, **kwargs) -> dict:
|
|
14
|
+
resources = {}
|
|
15
|
+
if next_occurrence := instance.next_occurrence:
|
|
16
|
+
resources["next_occurrence"] = reverse(
|
|
17
|
+
f"{instance.get_endpoint_basename()}-detail", args=[next_occurrence.id], request=request
|
|
18
|
+
)
|
|
19
|
+
return resources
|
|
20
|
+
|
|
21
|
+
@wb_serializers.register_only_instance_resource()
|
|
22
|
+
def previous_occurrence(self, instance: Model, request, user, **kwargs) -> dict:
|
|
23
|
+
resources = {}
|
|
24
|
+
if previous_occurrence := instance.previous_occurrence:
|
|
25
|
+
resources["previous_occurrence"] = reverse(
|
|
26
|
+
f"{instance.get_endpoint_basename()}-detail", args=[previous_occurrence.id], request=request
|
|
27
|
+
)
|
|
28
|
+
return resources
|
|
29
|
+
|
|
30
|
+
@wb_serializers.register_only_instance_resource()
|
|
31
|
+
def get_parent_occurrence(self, instance: Model, request, user, **kwargs) -> dict:
|
|
32
|
+
resources = {}
|
|
33
|
+
if instance.parent_occurrence:
|
|
34
|
+
resources["get_parent_occurrence"] = reverse(
|
|
35
|
+
f"{instance.get_endpoint_basename()}-detail", args=[instance.parent_occurrence.id], request=request
|
|
36
|
+
)
|
|
37
|
+
return resources
|
|
38
|
+
|
|
39
|
+
@wb_serializers.register_only_instance_resource()
|
|
40
|
+
def delete_occurrences(self, instance, request, user, **kwargs):
|
|
41
|
+
resources = dict()
|
|
42
|
+
parent = instance.parent_occurrence if instance.parent_occurrence else instance
|
|
43
|
+
child_occurrences = parent.get_recurrent_valid_children()
|
|
44
|
+
if instance.period and child_occurrences.exists():
|
|
45
|
+
child_occurrences = child_occurrences.filter(period__startswith__gt=instance.period.lower)
|
|
46
|
+
if child_occurrences.exists():
|
|
47
|
+
resources["delete_next_occurrences"] = reverse(
|
|
48
|
+
f"{instance.get_endpoint_basename()}-delete-next-occurrences", args=[instance.id], request=request
|
|
49
|
+
)
|
|
50
|
+
return resources
|
|
51
|
+
|
|
52
|
+
def validate(self, data):
|
|
53
|
+
period = data.get("period", self.instance.period if self.instance else None)
|
|
54
|
+
recurrence_count = data.get("recurrence_count", self.instance.recurrence_count if self.instance else None)
|
|
55
|
+
recurrence_end = data.get("recurrence_end", self.instance.recurrence_end if self.instance else None)
|
|
56
|
+
repeat_choice = data.get("repeat_choice", self.instance.repeat_choice if self.instance else None)
|
|
57
|
+
if not period:
|
|
58
|
+
raise serializers.ValidationError({"period": gettext("Please provide a valid timeframe.")})
|
|
59
|
+
|
|
60
|
+
if recurrence_end and recurrence_count:
|
|
61
|
+
error = gettext("You can only pick either a recurrence count or an end date but not both.")
|
|
62
|
+
raise ValidationError({"recurrence_end": error, "recurrence_count": error})
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
not self.instance
|
|
66
|
+
and (recurrence_end or recurrence_count)
|
|
67
|
+
and repeat_choice == Recurrence.ReoccuranceChoice.NEVER
|
|
68
|
+
):
|
|
69
|
+
data["recurrence_end"] = None
|
|
70
|
+
data["recurrence_count"] = None
|
|
71
|
+
if data.get("repeat_choice") and data.get("repeat_choice") != Recurrence.ReoccuranceChoice.NEVER:
|
|
72
|
+
if data.get("recurrence_end") and period.lower.date() >= data.get("recurrence_end"):
|
|
73
|
+
raise ValidationError(
|
|
74
|
+
{
|
|
75
|
+
"recurrence_end": gettext(
|
|
76
|
+
'The "Repeat Until" date needs to be after the "Recurrence Start" date.'
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
if data.get("repeat_choice") == Recurrence.ReoccuranceChoice.BUSINESS_DAILY and period.lower.weekday() > 4:
|
|
81
|
+
raise ValidationError({"period": gettext("Period must correspond to the recurrence 'Business Daily'")})
|
|
82
|
+
|
|
83
|
+
if self.instance and self.instance.period and self.instance.is_recurrent:
|
|
84
|
+
if (
|
|
85
|
+
self.instance.period.lower.date() != period.lower.date()
|
|
86
|
+
or self.instance.period.upper.date() != period.upper.date()
|
|
87
|
+
):
|
|
88
|
+
raise ValidationError(
|
|
89
|
+
{"period": gettext("It is only possible to change the time of the period of an occurrence.")}
|
|
90
|
+
)
|
|
91
|
+
return super().validate(data)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
from urllib.parse import urlencode
|
|
4
|
+
|
|
5
|
+
from django.dispatch import receiver
|
|
6
|
+
from django.utils.translation import gettext as _
|
|
7
|
+
from rest_framework.reverse import reverse
|
|
8
|
+
from wbcore.contrib.directory.models import Entry
|
|
9
|
+
from wbcore.contrib.directory.serializers import (
|
|
10
|
+
CompanyModelSerializer,
|
|
11
|
+
EntryModelSerializer,
|
|
12
|
+
EntryRepresentationSerializer,
|
|
13
|
+
PersonModelListSerializer,
|
|
14
|
+
PersonModelSerializer,
|
|
15
|
+
TelephoneContactSerializer,
|
|
16
|
+
)
|
|
17
|
+
from wbcore.signals import add_additional_resource, add_instance_additional_resource
|
|
18
|
+
|
|
19
|
+
from wbcrm.models import ActivityType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@functools.lru_cache()
|
|
23
|
+
def get_call_activity_type() -> int:
|
|
24
|
+
return ActivityType.objects.get_or_create(slugify_title="call", defaults={"title": "Call"})[0].id
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@receiver(add_additional_resource, sender=TelephoneContactSerializer)
|
|
28
|
+
def add_telephone_contact_activity_resources(sender, serializer, instance, request, user, **kwargs):
|
|
29
|
+
res = {}
|
|
30
|
+
with suppress(Entry.DoesNotExist):
|
|
31
|
+
if entry := instance.entry:
|
|
32
|
+
activity_reverse_url = reverse("wbcrm:activity-list", args=[], request=request)
|
|
33
|
+
|
|
34
|
+
# Creates the URL for the 'Create New Call Activity'-Button
|
|
35
|
+
query_args = {
|
|
36
|
+
"type": get_call_activity_type(),
|
|
37
|
+
"new_mode": True,
|
|
38
|
+
"participants": [str(request.user.profile.id)],
|
|
39
|
+
"title": _("Call with {name}").format(name=entry.computed_str),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if entry.is_company:
|
|
43
|
+
query_args["companies"] = [str(entry.id)]
|
|
44
|
+
activity_reverse_url = f"{activity_reverse_url}?companies={entry.id}"
|
|
45
|
+
else:
|
|
46
|
+
query_args["participants"].append(str(entry.id))
|
|
47
|
+
activity_reverse_url = f"{activity_reverse_url}?participants={entry.id}"
|
|
48
|
+
|
|
49
|
+
query_args["participants"] = ",".join(query_args["participants"])
|
|
50
|
+
if "companies" in query_args:
|
|
51
|
+
query_args["companies"] = ",".join(query_args["companies"])
|
|
52
|
+
res["list_of_activities"] = activity_reverse_url
|
|
53
|
+
res["new_call"] = reverse("wbcrm:activity-list", args=[], request=request) + "?" + urlencode(query_args)
|
|
54
|
+
return res
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@receiver(add_instance_additional_resource, sender=CompanyModelSerializer)
|
|
58
|
+
@receiver(add_instance_additional_resource, sender=PersonModelSerializer)
|
|
59
|
+
@receiver(add_instance_additional_resource, sender=EntryModelSerializer)
|
|
60
|
+
@receiver(add_instance_additional_resource, sender=PersonModelListSerializer)
|
|
61
|
+
@receiver(add_instance_additional_resource, sender=EntryRepresentationSerializer)
|
|
62
|
+
def add_entry_additional_resources(sender, serializer, instance, request, user, **kwargs):
|
|
63
|
+
res = {"account": f'{reverse("wbcrm:account-list", args=[], request=request)}?customer={instance.id}'}
|
|
64
|
+
if instance.is_company:
|
|
65
|
+
res["activity"] = f'{reverse("wbcrm:activity-list", request=request)}?companies={instance.id}'
|
|
66
|
+
res["interested_products"] = reverse(
|
|
67
|
+
"wbcrm:company-interestedproduct-list", args=[instance.id], request=request
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
res["activity"] = f'{reverse("wbcrm:activity-list", request=request)}?participants={instance.id}'
|
|
71
|
+
return res
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Activities
|
|
2
|
+
A list of every activity saved in the database. Will be filtered by activities that happen in the timeframe from a month ago to one week in the future by default. Will be ordered by the date an activity was last edited.
|
|
3
|
+
|
|
4
|
+
## Workflow:
|
|
5
|
+
An activity can have one of five different states that can be switched between by clicking on the corresponding buttons. These are displayed in the context menu if you right click the list row or at the top of the instance.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Planned:
|
|
11
|
+
The initial status after creating a new activity. From here you can either cancel, finish or directly review the activity which creates a popup where you can write your review.
|
|
12
|
+
|
|
13
|
+
### Finished:
|
|
14
|
+
The activity's status after it is over. From here you can review the activity.
|
|
15
|
+
|
|
16
|
+
### Canceled:
|
|
17
|
+
After canceling the activity this is its status.
|
|
18
|
+
|
|
19
|
+
### Reviewed:
|
|
20
|
+
After review this is the final status of the activity.
|
|
21
|
+
|
|
22
|
+
## Columns:
|
|
23
|
+
Each column title has three lines on the right if you hover over it. Click on them to show options for that column. The second tab of the options menu will allow you to filter the column, the third to completely hide entire columns. Click on anywhere else on the column to order it, cycling between ascending, descending and no ordering. Hold shift while clicking to order multiple columns with individual weights.
|
|
24
|
+
|
|
25
|
+
### Type:
|
|
26
|
+
The type of the activity. Activity types can be added, edited and deleted by managers under CRM > Administration > Activity Types.
|
|
27
|
+
|
|
28
|
+
### Title:
|
|
29
|
+
The activity's name.
|
|
30
|
+
|
|
31
|
+
### Period:
|
|
32
|
+
The timeframe where the activity takes place.
|
|
33
|
+
|
|
34
|
+
### Participants:
|
|
35
|
+
A list of persons participating in the activity. Hover over a name to display more information.
|
|
36
|
+
|
|
37
|
+
### Companies:
|
|
38
|
+
A list of companies participating in the activity. Hover over a name to display more information.
|
|
39
|
+
|
|
40
|
+
### Groups:
|
|
41
|
+
A list of groups participating in the activity. Groups can consist of persons and companies which will all be automatically added to the activity's participants and companies fields. Groups can be added, edited and deleted under CRM > Administration > Groups.
|
|
42
|
+
|
|
43
|
+
### Edited:
|
|
44
|
+
The date at which the activity was last edited.
|
|
45
|
+
|
|
46
|
+
### Created:
|
|
47
|
+
The date at which the activity was originally created.
|
|
48
|
+
|
|
49
|
+
### Description:
|
|
50
|
+
A description of what the activity is about. Hover over it to display the full description.
|
|
51
|
+
|
|
52
|
+
### Review:
|
|
53
|
+
A written review of the activity which can be created by anyone who can see the activity.
|
|
54
|
+
|
|
55
|
+
### Latest Reviewer:
|
|
56
|
+
The last person who updated the activity's review. Hover over the name to display more details about him/her.
|
|
57
|
+
|
|
58
|
+
## Legend:
|
|
59
|
+
Click on a status in the legend to filter the list. Repeating activities have a special symbol to the left of their column.
|
|
60
|
+
|
|
61
|
+
## Filters:
|
|
62
|
+
Filters are accessed by clicking on the symbol in the top left corner of the window.
|
|
63
|
+
|
|
64
|
+
### Status:
|
|
65
|
+
Filter activities by a specific status they're in, smiliarly to what clicking on the legend does.
|
|
66
|
+
|
|
67
|
+
### Frequency:
|
|
68
|
+
Filter activities by if and how often an activity is repeated.
|
|
69
|
+
|
|
70
|
+
### Conference Room:
|
|
71
|
+
Filter activities by if they take place in the conference room.
|
|
72
|
+
|
|
73
|
+
### Clients of:
|
|
74
|
+
Display only activities involving clients of the selected person.
|
|
75
|
+
|
|
76
|
+
### Visibility:
|
|
77
|
+
Filter activities by their visibility: Public, private or confidential.
|
|
78
|
+
|
|
79
|
+
### Importance:
|
|
80
|
+
Filter activities by their importance. The default importance value for activities is "Low".
|
|
81
|
+
|
|
82
|
+
### Only Recent:
|
|
83
|
+
Display activities that happen in the timeframe from a month ago to one week in the future. Will be active by default.
|
|
84
|
+
|
|
85
|
+
## Search Field:
|
|
86
|
+
Typing in the search field allows to filter the activities by name, description and review.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Activity Types
|
|
2
|
+
This is a list of every activity type in the database where you can modify, delete and add new activity types. Typing in the search field filters the activity types by name.
|
|
3
|
+
|
|
4
|
+
## Columns:
|
|
5
|
+
Each column title has three lines on the right if you hover over it. Click on them to show options for that column. The second tab of the options menu will allow you to filter the column, the third to completely hide entire columns. Click on anywhere else on the column to order it, cycling between ascending, descending and no ordering. Hold shift while clicking to order multiple columns with individual weights.
|
|
6
|
+
|
|
7
|
+
### Name:
|
|
8
|
+
The name of the activity type.
|
|
9
|
+
|
|
10
|
+
### Icon:
|
|
11
|
+
The icon representing the activity that is displayed in the calendar.
|
|
12
|
+
|
|
13
|
+
### Color:
|
|
14
|
+
The hexadecimal color code used to plot activity types. Must be unique.
|
|
15
|
+
|
|
16
|
+
### Multiplier:
|
|
17
|
+
Every activity type has a multiplier for the activity heat calculation. Multipliers range from low (2, i.e. e-mail) and medium (3, i.e. call) to high (4, i.e. meeting).
|
|
18
|
+
|
|
19
|
+
### Is Default:
|
|
20
|
+
If true sets the default value for type in new activities to be this instance.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
# Groups
|
|
2
|
+
This is a list of every group in the database where you can modify, delete and add new groups. Typing in the search field or the column filter accessible from the three lines at the end of the column title filters the groups by name. Click on the column title to order it.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Products
|
|
2
|
+
This is a list of every CRM product in the database where you can modify, delete and add new products. Typing in the search field filters the products by name.
|
|
3
|
+
|
|
4
|
+
## Columns:
|
|
5
|
+
Each column title has three lines on the right if you hover over it. Click on them to show options for that column. The second tab of the options menu will allow you to filter the column, the third to completely hide entire columns. Click on anywhere else on the column to order it, cycling between ascending, descending and no ordering. Hold shift while clicking to order multiple columns with individual weights.
|
|
6
|
+
|
|
7
|
+
### Title:
|
|
8
|
+
The name of the product.
|
|
9
|
+
|
|
10
|
+
### Is Competitor:
|
|
11
|
+
Indicates if this is a competitor's product.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from django.contrib import admin, messages
|
|
2
|
+
from django.contrib.auth import get_user_model
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
from wbcore.contrib.authentication.admin import UserAdmin
|
|
5
|
+
|
|
6
|
+
from wbcrm.admin import ActivityAdmin
|
|
7
|
+
from wbcrm.models import Activity
|
|
8
|
+
|
|
9
|
+
from .shortcuts import get_backend
|
|
10
|
+
|
|
11
|
+
User = get_user_model()
|
|
12
|
+
admin.site.unregister(User)
|
|
13
|
+
admin.site.unregister(Activity)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@admin.register(Activity)
|
|
17
|
+
class ActivitySyncAdmin(ActivityAdmin):
|
|
18
|
+
def delete_queryset(self, request, queryset):
|
|
19
|
+
"""Given a queryset, delete it from the database."""
|
|
20
|
+
for obj in queryset.filter(is_active=True):
|
|
21
|
+
obj.delete()
|
|
22
|
+
super().delete_queryset(request, queryset)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@admin.register(User)
|
|
26
|
+
class UserSyncAdmin(UserAdmin):
|
|
27
|
+
def set_web_hook(self, request, queryset):
|
|
28
|
+
try:
|
|
29
|
+
if controller := get_backend():
|
|
30
|
+
for user in queryset:
|
|
31
|
+
controller.backend.set_web_hook(user)
|
|
32
|
+
self.message_user(
|
|
33
|
+
request,
|
|
34
|
+
_("Operation completed, we have set the webhook for {} users.").format(queryset.count()),
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError("No backend set in preferences")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self.message_user(request, _("Operation Failed, {}").format(e), messages.WARNING)
|
|
40
|
+
|
|
41
|
+
def stop_web_hook(self, request, queryset):
|
|
42
|
+
try:
|
|
43
|
+
if controller := get_backend():
|
|
44
|
+
for user in queryset:
|
|
45
|
+
controller.backend.stop_web_hook(user)
|
|
46
|
+
self.message_user(
|
|
47
|
+
request,
|
|
48
|
+
_("Operation completed, we have stopped the webhook for {} users.").format(queryset.count()),
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError("No backend set in preferences")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
self.message_user(request, _("Operation Failed, {}").format(e), messages.WARNING)
|
|
54
|
+
|
|
55
|
+
def check_web_hook(self, request, queryset):
|
|
56
|
+
try:
|
|
57
|
+
if controller := get_backend():
|
|
58
|
+
for user in queryset:
|
|
59
|
+
controller.backend.check_web_hook(user)
|
|
60
|
+
self.message_user(
|
|
61
|
+
request,
|
|
62
|
+
_("Operation completed, we checked the webhook for {} users.").format(queryset.count()),
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
raise ValueError("No backend set in preferences")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.message_user(request, _("Operation Failed, {}").format(e), messages.WARNING)
|
|
68
|
+
|
|
69
|
+
actions = UserAdmin.actions + (
|
|
70
|
+
set_web_hook,
|
|
71
|
+
stop_web_hook,
|
|
72
|
+
check_web_hook,
|
|
73
|
+
)
|