wbcrm 2.2.1__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.

Potentially problematic release.


This version of wbcrm might be problematic. Click here for more details.

Files changed (155) hide show
  1. wbcrm/__init__.py +1 -0
  2. wbcrm/admin/__init__.py +4 -0
  3. wbcrm/admin/accounts.py +59 -0
  4. wbcrm/admin/activities.py +101 -0
  5. wbcrm/admin/groups.py +7 -0
  6. wbcrm/admin/products.py +8 -0
  7. wbcrm/apps.py +5 -0
  8. wbcrm/configurations/__init__.py +1 -0
  9. wbcrm/configurations/base.py +16 -0
  10. wbcrm/dynamic_preferences_registry.py +38 -0
  11. wbcrm/factories/__init__.py +14 -0
  12. wbcrm/factories/accounts.py +56 -0
  13. wbcrm/factories/activities.py +125 -0
  14. wbcrm/factories/groups.py +23 -0
  15. wbcrm/factories/products.py +10 -0
  16. wbcrm/filters/__init__.py +10 -0
  17. wbcrm/filters/accounts.py +67 -0
  18. wbcrm/filters/activities.py +181 -0
  19. wbcrm/filters/groups.py +20 -0
  20. wbcrm/filters/products.py +37 -0
  21. wbcrm/filters/signals.py +94 -0
  22. wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
  23. wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
  24. wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
  25. wbcrm/migrations/0004_alter_activity_status.py +28 -0
  26. wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
  27. wbcrm/migrations/0006_alter_activity_location.py +17 -0
  28. wbcrm/migrations/0007_alter_account_status.py +23 -0
  29. wbcrm/migrations/0008_alter_activity_options.py +16 -0
  30. wbcrm/migrations/0009_alter_account_is_public.py +19 -0
  31. wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
  32. wbcrm/migrations/0011_activity_summary.py +22 -0
  33. wbcrm/migrations/0012_alter_activity_summary.py +17 -0
  34. wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
  35. wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
  36. wbcrm/migrations/0015_alter_activity_type.py +23 -0
  37. wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
  38. wbcrm/migrations/__init__.py +0 -0
  39. wbcrm/models/__init__.py +4 -0
  40. wbcrm/models/accounts.py +637 -0
  41. wbcrm/models/activities.py +1335 -0
  42. wbcrm/models/groups.py +118 -0
  43. wbcrm/models/products.py +83 -0
  44. wbcrm/models/recurrence.py +279 -0
  45. wbcrm/preferences.py +14 -0
  46. wbcrm/serializers/__init__.py +23 -0
  47. wbcrm/serializers/accounts.py +126 -0
  48. wbcrm/serializers/activities.py +526 -0
  49. wbcrm/serializers/groups.py +30 -0
  50. wbcrm/serializers/products.py +57 -0
  51. wbcrm/serializers/recurrence.py +90 -0
  52. wbcrm/serializers/signals.py +70 -0
  53. wbcrm/synchronization/__init__.py +0 -0
  54. wbcrm/synchronization/activity/__init__.py +0 -0
  55. wbcrm/synchronization/activity/admin.py +72 -0
  56. wbcrm/synchronization/activity/backend.py +207 -0
  57. wbcrm/synchronization/activity/backends/__init__.py +0 -0
  58. wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
  59. wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +399 -0
  60. wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
  61. wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
  62. wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
  63. wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
  64. wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
  65. wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
  66. wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
  67. wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
  68. wbcrm/synchronization/activity/backends/google/utils.py +216 -0
  69. wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
  70. wbcrm/synchronization/activity/backends/outlook/backend.py +576 -0
  71. wbcrm/synchronization/activity/backends/outlook/msgraph.py +438 -0
  72. wbcrm/synchronization/activity/backends/outlook/parser.py +423 -0
  73. wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
  74. wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
  75. wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
  76. wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +117 -0
  77. wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +269 -0
  78. wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +237 -0
  79. wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +173 -0
  80. wbcrm/synchronization/activity/controller.py +545 -0
  81. wbcrm/synchronization/activity/dynamic_preferences_registry.py +107 -0
  82. wbcrm/synchronization/activity/preferences.py +21 -0
  83. wbcrm/synchronization/activity/shortcuts.py +9 -0
  84. wbcrm/synchronization/activity/signals.py +28 -0
  85. wbcrm/synchronization/activity/tasks.py +21 -0
  86. wbcrm/synchronization/activity/urls.py +6 -0
  87. wbcrm/synchronization/activity/utils.py +46 -0
  88. wbcrm/synchronization/activity/views.py +37 -0
  89. wbcrm/synchronization/admin.py +1 -0
  90. wbcrm/synchronization/apps.py +15 -0
  91. wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
  92. wbcrm/synchronization/management.py +36 -0
  93. wbcrm/synchronization/tasks.py +1 -0
  94. wbcrm/synchronization/urls.py +5 -0
  95. wbcrm/tasks.py +312 -0
  96. wbcrm/tests/__init__.py +0 -0
  97. wbcrm/tests/accounts/__init__.py +0 -0
  98. wbcrm/tests/accounts/test_models.py +380 -0
  99. wbcrm/tests/accounts/test_viewsets.py +87 -0
  100. wbcrm/tests/conftest.py +76 -0
  101. wbcrm/tests/disable_signals.py +52 -0
  102. wbcrm/tests/e2e/__init__.py +1 -0
  103. wbcrm/tests/e2e/e2e_wbcrm_utility.py +82 -0
  104. wbcrm/tests/e2e/test_e2e.py +369 -0
  105. wbcrm/tests/test_assignee_methods.py +39 -0
  106. wbcrm/tests/test_chartviewsets.py +111 -0
  107. wbcrm/tests/test_dto.py +63 -0
  108. wbcrm/tests/test_filters.py +51 -0
  109. wbcrm/tests/test_models.py +216 -0
  110. wbcrm/tests/test_recurrence.py +291 -0
  111. wbcrm/tests/test_report.py +20 -0
  112. wbcrm/tests/test_serializers.py +170 -0
  113. wbcrm/tests/test_tasks.py +94 -0
  114. wbcrm/tests/test_viewsets.py +967 -0
  115. wbcrm/tests/tests.py +120 -0
  116. wbcrm/typings.py +107 -0
  117. wbcrm/urls.py +67 -0
  118. wbcrm/viewsets/__init__.py +22 -0
  119. wbcrm/viewsets/accounts.py +121 -0
  120. wbcrm/viewsets/activities.py +315 -0
  121. wbcrm/viewsets/buttons/__init__.py +7 -0
  122. wbcrm/viewsets/buttons/accounts.py +27 -0
  123. wbcrm/viewsets/buttons/activities.py +68 -0
  124. wbcrm/viewsets/buttons/signals.py +17 -0
  125. wbcrm/viewsets/display/__init__.py +12 -0
  126. wbcrm/viewsets/display/accounts.py +110 -0
  127. wbcrm/viewsets/display/activities.py +443 -0
  128. wbcrm/viewsets/display/groups.py +22 -0
  129. wbcrm/viewsets/display/products.py +105 -0
  130. wbcrm/viewsets/endpoints/__init__.py +8 -0
  131. wbcrm/viewsets/endpoints/accounts.py +32 -0
  132. wbcrm/viewsets/endpoints/activities.py +30 -0
  133. wbcrm/viewsets/endpoints/groups.py +7 -0
  134. wbcrm/viewsets/endpoints/products.py +9 -0
  135. wbcrm/viewsets/groups.py +37 -0
  136. wbcrm/viewsets/menu/__init__.py +8 -0
  137. wbcrm/viewsets/menu/accounts.py +18 -0
  138. wbcrm/viewsets/menu/activities.py +61 -0
  139. wbcrm/viewsets/menu/groups.py +16 -0
  140. wbcrm/viewsets/menu/products.py +20 -0
  141. wbcrm/viewsets/mixins.py +34 -0
  142. wbcrm/viewsets/previews/__init__.py +1 -0
  143. wbcrm/viewsets/previews/activities.py +10 -0
  144. wbcrm/viewsets/products.py +56 -0
  145. wbcrm/viewsets/recurrence.py +26 -0
  146. wbcrm/viewsets/titles/__init__.py +13 -0
  147. wbcrm/viewsets/titles/accounts.py +22 -0
  148. wbcrm/viewsets/titles/activities.py +61 -0
  149. wbcrm/viewsets/titles/products.py +13 -0
  150. wbcrm/viewsets/titles/utils.py +46 -0
  151. wbcrm/workflows/__init__.py +1 -0
  152. wbcrm/workflows/assignee_methods.py +25 -0
  153. wbcrm-2.2.1.dist-info/METADATA +11 -0
  154. wbcrm-2.2.1.dist-info/RECORD +155 -0
  155. wbcrm-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,315 @@
1
+ import pandas as pd
2
+ import plotly.express as px
3
+ import plotly.graph_objects as go
4
+ from django.contrib.messages import warning
5
+ from django.db.models import Exists, F, OuterRef, QuerySet, Subquery
6
+ from django.shortcuts import get_object_or_404
7
+ from django.utils.functional import cached_property
8
+ from django.utils.translation import gettext as _
9
+ from wbcore import viewsets
10
+ from wbcore.contrib.agenda.models import CalendarItem
11
+ from wbcore.contrib.agenda.viewsets import CalendarItemViewSet
12
+ from wbcore.contrib.directory.models import (
13
+ Company,
14
+ EmailContact,
15
+ EmployerEmployeeRelationship,
16
+ Entry,
17
+ Person,
18
+ TelephoneContact,
19
+ )
20
+ from wbcore.filters import DjangoFilterBackend
21
+ from wbcrm import serializers as crm_serializers
22
+ from wbcrm.filters import (
23
+ ActivityChartFilter,
24
+ ActivityFilter,
25
+ ActivityParticipantFilter,
26
+ ActivityTypeFilter,
27
+ )
28
+ from wbcrm.models import Activity, ActivityParticipant, ActivityType
29
+ from wbcrm.viewsets.buttons import ActivityButtonConfig, ActivityParticipantButtonConfig
30
+ from wbcrm.viewsets.display import (
31
+ ActivityDisplay,
32
+ ActivityParticipantDisplayConfig,
33
+ ActivityTypeDisplay,
34
+ )
35
+ from wbcrm.viewsets.endpoints import (
36
+ ActivityEndpointConfig,
37
+ ActivityParticipantModelEndpointConfig,
38
+ )
39
+ from wbcrm.viewsets.previews.activities import ActivityPreviewConfig
40
+ from wbcrm.viewsets.titles import (
41
+ ActivityChartModelTitleConfig,
42
+ ActivityParticipantTitleConfig,
43
+ ActivityTitleConfig,
44
+ ActivityTypeTitleConfig,
45
+ )
46
+
47
+ from ..serializers import (
48
+ ActivityTypeModelSerializer,
49
+ ActivityTypeRepresentationSerializer,
50
+ )
51
+ from .recurrence import RecurrenceModelViewSetMixin
52
+
53
+
54
+ class ActivityTypeModelViewSet(viewsets.ModelViewSet):
55
+ IDENTIFIER = "wbcrm:activitytype"
56
+ LIST_DOCUMENTATION = "wbcrm/markdown/documentation/activitytype.md"
57
+ queryset = ActivityType.objects.all()
58
+ serializer_class = ActivityTypeModelSerializer
59
+ display_config_class = ActivityTypeDisplay
60
+ title_config_class = ActivityTypeTitleConfig
61
+ search_fields = ("title",)
62
+ filterset_class = ActivityTypeFilter
63
+ ordering = ("title",)
64
+ ordering_fields = (
65
+ "title",
66
+ "color",
67
+ "score",
68
+ "icon",
69
+ "default",
70
+ )
71
+
72
+
73
+ class ActivityTypeRepresentationViewSet(viewsets.RepresentationViewSet):
74
+ IDENTIFIER = "wbcrm:activitytyperepresentation"
75
+ serializer_class = ActivityTypeRepresentationSerializer
76
+ queryset = ActivityType.objects.all()
77
+
78
+
79
+ class ActivityRepresentationViewSet(viewsets.RepresentationViewSet):
80
+ queryset = Activity.objects.all()
81
+ serializer_class = crm_serializers.ActivityRepresentationSerializer
82
+
83
+
84
+ class ActivityViewSet(RecurrenceModelViewSetMixin, CalendarItemViewSet):
85
+ IDENTIFIER = "wbcrm:activity"
86
+ DEPENDANT_IDENTIFIER = ["wbcrm:calendaritem"]
87
+ LIST_DOCUMENTATION = "wbcrm/markdown/documentation/activity.md"
88
+
89
+ ordering = ["-edited", "id"]
90
+ search_fields = ("title", "description", "result")
91
+ serializer_class = crm_serializers.ActivityModelSerializer
92
+ display_config_class = ActivityDisplay
93
+ title_config_class = ActivityTitleConfig
94
+ filterset_class = ActivityFilter
95
+ endpoint_config_class = ActivityEndpointConfig
96
+ preview_config_class = ActivityPreviewConfig
97
+ button_config_class = ActivityButtonConfig
98
+ queryset = Activity.objects.all()
99
+
100
+ ordering_fields = [
101
+ "created",
102
+ "description",
103
+ "edited",
104
+ "period",
105
+ "latest_reviewer__computed_str",
106
+ "type",
107
+ "result",
108
+ "title",
109
+ ]
110
+
111
+ @cached_property
112
+ def is_external_activity(self):
113
+ if "pk" in self.kwargs:
114
+ if activity := self.get_object():
115
+ return (creator := activity.creator) and not creator.is_internal
116
+ return False
117
+
118
+ @cached_property
119
+ def is_private_for_user(self):
120
+ if "pk" in self.kwargs:
121
+ if activity := self.get_object():
122
+ return activity.is_private_for_user(self.request.user)
123
+ return not self.new_mode
124
+
125
+ @cached_property
126
+ def is_confidential_for_user(self):
127
+ if "pk" in self.kwargs:
128
+ if activity := self.get_object():
129
+ return activity.is_confidential_for_user(self.request.user)
130
+ return not self.new_mode
131
+
132
+ @cached_property
133
+ def participants(self) -> QuerySet[Person]:
134
+ try:
135
+ participant_ids = self.request.GET["participants"].split(",")
136
+ except KeyError:
137
+ participant_ids = []
138
+ return Person.objects.filter(id__in=participant_ids)
139
+
140
+ @cached_property
141
+ def companies(self) -> QuerySet[Company]:
142
+ try:
143
+ company_ids = self.request.GET["companies"].split(",")
144
+ except KeyError:
145
+ company_ids = []
146
+ return Company.objects.filter(id__in=company_ids)
147
+
148
+ @cached_property
149
+ def entry(self) -> Entry | None:
150
+ try:
151
+ return Entry.objects.get(id=self.kwargs.get("person_id", self.kwargs["company_id"]))
152
+ except (KeyError, Entry.DoesNotExist):
153
+ return self.participants.first() or self.companies.first()
154
+
155
+ def get_serializer_class(self):
156
+ if self.get_action() in ["list", "list-metadata"]:
157
+ return crm_serializers.ActivityModelListSerializer
158
+ if self.is_private_for_user or self.is_confidential_for_user:
159
+ return crm_serializers.ReadOnlyActivityModelSerializer
160
+ return super().get_serializer_class()
161
+
162
+ def get_queryset(self):
163
+ user = self.request.user
164
+ return (
165
+ Activity.get_activities_for_user(user, base_qs=super().get_queryset())
166
+ .select_related("latest_reviewer", "type")
167
+ .prefetch_related(
168
+ "groups",
169
+ "participants",
170
+ "companies",
171
+ )
172
+ )
173
+
174
+ def add_messages(self, request, instance: Activity | None = None, **kwargs):
175
+ super().add_messages(request, instance=instance, **kwargs)
176
+ if instance:
177
+ if self.is_external_activity:
178
+ warning(
179
+ self.request,
180
+ "This activity was created by an external user and synchronized. The modification is restricted.",
181
+ )
182
+ if warning_message := instance.participants_company_check_message():
183
+ warning(request, warning_message, extra_tags="auto_close=0")
184
+
185
+ # Throws a warning message when there are more people (probably) participating in an activity on-site
186
+ # than the selected conference room has capacity for.
187
+ if (
188
+ instance.conference_room
189
+ and instance.conference_room.capacity is not None
190
+ and instance.participants.exclude(
191
+ activity_participants__participation_status__in=[
192
+ ActivityParticipant.ParticipationStatus.ATTENDS_DIGITALLY,
193
+ ActivityParticipant.ParticipationStatus.CANCELLED,
194
+ ]
195
+ ).count()
196
+ > instance.conference_room.capacity
197
+ ):
198
+ warning(
199
+ request,
200
+ _(
201
+ "There are more participants currently participating in this activity than the maximum capacity of the selected conference room allows."
202
+ ),
203
+ )
204
+
205
+
206
+ class ActivityChartModelViewSet(viewsets.ChartViewSet):
207
+ filter_backends = (DjangoFilterBackend,)
208
+ queryset = Activity.objects.all()
209
+ filterset_class = ActivityChartFilter
210
+ IDENTIFIER = "wbcrm:activitychart"
211
+ title_config_class = ActivityChartModelTitleConfig
212
+
213
+ def get_queryset(self):
214
+ return (
215
+ Activity.get_activities_for_user(self.request.user)
216
+ .exclude(period__isnull=True)
217
+ .filter(visibility=CalendarItem.Visibility.PUBLIC)
218
+ .annotate(
219
+ activity_type_color=F("type__color"),
220
+ activity_type_title=F("type__title"),
221
+ start_date=F("period__startswith"),
222
+ end_date=F("period__endswith"),
223
+ )
224
+ .select_related("type")
225
+ )
226
+
227
+ def get_plotly(self, queryset):
228
+ fig = go.Figure()
229
+ if queryset.exists():
230
+ df = pd.DataFrame(
231
+ queryset.values("type", "start_date", "end_date", "activity_type_color", "activity_type_title")
232
+ )
233
+ df["start_date"] = df["start_date"].dt.floor("H")
234
+ df["end_date"] = df["end_date"].dt.ceil("H")
235
+ df = (
236
+ pd.concat(
237
+ [
238
+ pd.DataFrame(index=pd.date_range(r.start_date, r.end_date, freq="1H")).assign(
239
+ type=r.type,
240
+ activity_type_color=r.activity_type_color,
241
+ activity_type_title=r.activity_type_title,
242
+ )
243
+ for r in df.itertuples()
244
+ ]
245
+ ).reset_index()
246
+ ).rename(columns={"index": "Period", "activity_type_title": "Type"})
247
+ df["Period"] = pd.to_datetime(df["Period"])
248
+ fig = px.histogram(
249
+ df,
250
+ x="Period",
251
+ color="Type",
252
+ labels="Type",
253
+ nbins=len(pd.date_range(df["Period"].min(), df["Period"].max(), freq="1H")) + 1,
254
+ )
255
+ fig.update_layout(
256
+ paper_bgcolor="rgba(0,0,0,0)",
257
+ plot_bgcolor="rgba(0,0,0,0)",
258
+ title=_("<b>User Activity</b>"),
259
+ bargap=0.2,
260
+ autosize=True,
261
+ )
262
+
263
+ return fig
264
+
265
+
266
+ class ActivityParticipantModelViewSet(viewsets.ModelViewSet):
267
+ IDENTIFIER = "wbcrm:activity-participant"
268
+
269
+ button_config_class = ActivityParticipantButtonConfig
270
+ display_config_class = ActivityParticipantDisplayConfig
271
+ endpoint_config_class = ActivityParticipantModelEndpointConfig
272
+ filterset_class = ActivityParticipantFilter
273
+ serializer_class = crm_serializers.ActivityParticipantModelSerializer
274
+ search_fields = []
275
+ title_config_class = ActivityParticipantTitleConfig
276
+ ordering = ["participation_status", "id"]
277
+ queryset = ActivityParticipant.objects.all()
278
+
279
+ def get_queryset(self):
280
+ activity = get_object_or_404(Activity, id=self.kwargs["activity_id"])
281
+ return (
282
+ super()
283
+ .get_queryset()
284
+ .filter(activity=activity)
285
+ .annotate(
286
+ customer_status=Subquery(
287
+ EmployerEmployeeRelationship.objects.filter(
288
+ primary=True, employee=OuterRef("participant__pk")
289
+ ).values("employer__customer_status__title")[:1]
290
+ ),
291
+ position=Subquery(
292
+ EmployerEmployeeRelationship.objects.filter(
293
+ primary=True, employee=OuterRef("participant__pk")
294
+ ).values("position__title")[:1]
295
+ ),
296
+ primary_telephone=Subquery(
297
+ TelephoneContact.objects.filter(primary=True, entry__id=OuterRef("participant__pk")).values(
298
+ "number"
299
+ )[:1],
300
+ ),
301
+ primary_email=Subquery(
302
+ EmailContact.objects.filter(primary=True, entry__id=OuterRef("participant__pk")).values("address")[
303
+ :1
304
+ ],
305
+ ),
306
+ is_occupied=Exists(
307
+ Activity.objects.filter(
308
+ period__overlap=activity.period,
309
+ participants__id=OuterRef("participant__pk"),
310
+ )
311
+ .exclude(id=activity.id)
312
+ .exclude(status=Activity.Status.CANCELLED)
313
+ ),
314
+ )
315
+ )
@@ -0,0 +1,7 @@
1
+ from wbcrm.viewsets.buttons.activities import (
2
+ ActivityButtonConfig,
3
+ ActivityParticipantButtonConfig,
4
+ )
5
+
6
+ from .accounts import AccountButtonConfig
7
+ from .signals import *
@@ -0,0 +1,27 @@
1
+ from django.dispatch import receiver
2
+ from wbcore.contrib.directory.viewsets import (
3
+ CompanyModelViewSet,
4
+ EntryModelViewSet,
5
+ PersonModelViewSet,
6
+ )
7
+ from wbcore.contrib.icons import WBIcon
8
+ from wbcore.metadata.configs import buttons as bt
9
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
10
+ from wbcore.signals.instance_buttons import add_instance_button
11
+
12
+
13
+ class AccountButtonConfig(ButtonViewConfig):
14
+ def get_custom_instance_buttons(self):
15
+ return {bt.WidgetButton(key="claims", label="Show Claims", icon=WBIcon.TRADE.icon)}
16
+
17
+ def get_custom_list_instance_buttons(self):
18
+ return self.get_custom_instance_buttons()
19
+
20
+
21
+ @receiver(add_instance_button, sender=PersonModelViewSet)
22
+ @receiver(add_instance_button, sender=EntryModelViewSet)
23
+ @receiver(add_instance_button, sender=CompanyModelViewSet)
24
+ def crm_adding_instance_buttons(sender, many, *args, **kwargs):
25
+ if many:
26
+ return
27
+ return bt.WidgetButton(key="account", label="Accounts", icon=WBIcon.FOLDERS_MONEY.icon)
@@ -0,0 +1,68 @@
1
+ from django.utils.translation import gettext as _
2
+ from rest_framework.reverse import reverse
3
+ from wbcore.contrib.icons import WBIcon
4
+ from wbcore.enums import RequestType
5
+ from wbcore.metadata.configs import buttons as bt
6
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
7
+ from wbcrm.models.activities import Activity
8
+
9
+ DESCRIPTION: str = _(
10
+ "<p> Are you sure you want to delete all the future instances of this activity? <br> \
11
+ Only 'Planned' and 'Canceled' activities will be deleted. <br>\
12
+ Depending on the number of activities to be deleted <br> \
13
+ it may take some time until the deleted activities are no longer displayed in the activity list. </p>",
14
+ )
15
+
16
+
17
+ class ActivityButtonConfig(ButtonViewConfig):
18
+ def get_custom_instance_buttons(self) -> set:
19
+ if self.view.kwargs.get("pk"):
20
+ return {
21
+ bt.WidgetButton(
22
+ label=_("Parent Activity"), icon=WBIcon.CALENDAR.icon, key="get_parent_occurrence", weight=110
23
+ ),
24
+ bt.ActionButton(
25
+ method=RequestType.DELETE,
26
+ identifiers=("wbcrm:activity",),
27
+ key="delete_next_occurrences",
28
+ label=_("Delete Next Occurrences"),
29
+ icon=WBIcon.DELETE.icon,
30
+ description_fields=DESCRIPTION,
31
+ title=_("Delete"),
32
+ action_label=_("Delete"),
33
+ weight=140,
34
+ ),
35
+ bt.WidgetButton(label=_("Next Activity"), icon=WBIcon.NEXT.icon, key="next_occurrence", weight=130),
36
+ bt.WidgetButton(
37
+ label=_("Previous Activity"),
38
+ icon=WBIcon.PREVIOUS.icon,
39
+ key="previous_occurrence",
40
+ weight=120,
41
+ ),
42
+ }
43
+
44
+ return set()
45
+
46
+
47
+ class ActivityParticipantButtonConfig(ButtonViewConfig):
48
+ def get_custom_buttons(self) -> set:
49
+ if not self.view.kwargs.get("pk"):
50
+ base_url = reverse("wbcrm:activity-list", args=[], request=self.request)
51
+ activity: Activity = Activity.objects.get(id=self.view.kwargs.get("activity_id"))
52
+ if activity.period:
53
+ participants_id_set: set[int] = set(activity.participants.values_list("id", flat=True))
54
+ id_str = ",".join(str(id) for id in participants_id_set)
55
+
56
+ start = activity.period.lower.date()
57
+ end = activity.period.upper.date()
58
+
59
+ endpoint = f"{base_url}?participants={id_str}&period={start:%Y-%m-%d},{end:%Y-%m-%d}"
60
+
61
+ return {
62
+ bt.WidgetButton(
63
+ endpoint=endpoint,
64
+ label=_("Show Participants' Activities"),
65
+ icon=WBIcon.CALENDAR.icon,
66
+ )
67
+ }
68
+ return set()
@@ -0,0 +1,17 @@
1
+ from django.dispatch import receiver
2
+ from django.utils.translation import gettext as _
3
+ from wbcore.contrib.directory.viewsets import (
4
+ CompanyModelViewSet,
5
+ EntryModelViewSet,
6
+ PersonModelViewSet,
7
+ )
8
+ from wbcore.contrib.icons import WBIcon
9
+ from wbcore.metadata.configs import buttons as bt
10
+ from wbcore.signals.instance_buttons import add_instance_button
11
+
12
+
13
+ @receiver(add_instance_button, sender=PersonModelViewSet)
14
+ @receiver(add_instance_button, sender=EntryModelViewSet)
15
+ @receiver(add_instance_button, sender=CompanyModelViewSet)
16
+ def add_activity_instance_button_in_directory_viewsets(sender, many, *args, **kwargs):
17
+ return bt.WidgetButton(key="activity", label=_("Activities"), icon=WBIcon.CALENDAR.icon, weight=1)
@@ -0,0 +1,12 @@
1
+ from .accounts import (
2
+ AccountDisplayConfig,
3
+ AccountRoleAccountDisplayConfig,
4
+ InheritedAccountRoleAccountDisplayConfig,
5
+ )
6
+ from .activities import (
7
+ ActivityDisplay,
8
+ ActivityParticipantDisplayConfig,
9
+ ActivityTypeDisplay,
10
+ )
11
+ from .groups import GroupModelDisplay
12
+ from .products import ProductCompanyRelationshipDisplay, ProductDisplay
@@ -0,0 +1,110 @@
1
+ from typing import Optional
2
+
3
+ from rest_framework.reverse import reverse
4
+ from wbcore.metadata.configs import display as dp
5
+ from wbcore.metadata.configs.display.instance_display import Display
6
+ from wbcore.metadata.configs.display.instance_display.shortcuts import (
7
+ create_simple_display,
8
+ create_simple_section,
9
+ )
10
+ from wbcore.metadata.configs.display.instance_display.utils import repeat_field
11
+ from wbcore.metadata.configs.display.view_config import DisplayViewConfig
12
+
13
+
14
+ class AccountDisplayConfig(DisplayViewConfig):
15
+ def get_list_display(self) -> Optional[dp.ListDisplay]:
16
+ return dp.ListDisplay(
17
+ fields=[
18
+ dp.Field(
19
+ key="owner",
20
+ label="Owner",
21
+ ),
22
+ dp.Field(key="reference_id", label="Reference ID"),
23
+ dp.Field(key="is_terminal_account", label="Terminal Account"),
24
+ dp.Field(key="is_public", label="Public"),
25
+ dp.Field(key="is_active", label="Active"),
26
+ dp.Field(
27
+ key="llm",
28
+ label="LLM Analysis",
29
+ children=[
30
+ dp.Field(key="relationship_status", label="Relationship Status"),
31
+ dp.Field(key="relationship_summary", label="Relationship Summary", show="open"),
32
+ dp.Field(key="action_plan", label="Action Plan", show="open"),
33
+ ],
34
+ ),
35
+ ],
36
+ tree=True,
37
+ tree_group_pinned="left",
38
+ tree_group_field="title",
39
+ tree_group_label="Title",
40
+ tree_group_field_sortable=True,
41
+ tree_group_level_options=[
42
+ dp.TreeGroupLevelOption(
43
+ filter_key="parent",
44
+ filter_depth=1,
45
+ # lookup="id_repr",
46
+ list_endpoint=reverse(
47
+ "wbcrm:account-list",
48
+ args=[],
49
+ request=self.request,
50
+ ),
51
+ )
52
+ ],
53
+ )
54
+
55
+ def get_instance_display(self) -> Display:
56
+ child_account_section = create_simple_section(
57
+ "child_account_section", "Child Accounts", [["childaccounts"]], "childaccounts", collapsed=True
58
+ )
59
+ account_role_section = create_simple_section(
60
+ "account_role_section", "Account Roles", [["accountroles"]], "accountroles", collapsed=True
61
+ )
62
+ inherited_account_role_section = create_simple_section(
63
+ "inherited_account_role_section",
64
+ "Inherited Account Roles",
65
+ [["inheritedaccountroles"]],
66
+ "inheritedaccountroles",
67
+ collapsed=True,
68
+ )
69
+ return create_simple_display(
70
+ [
71
+ [repeat_field(3, "status")],
72
+ ["title", "title", "reference_id"],
73
+ ["is_active", "is_terminal_account", "is_public"],
74
+ ["parent", "owner", "owner"] if "account_id" not in self.view.kwargs else [repeat_field(3, "owner")],
75
+ [repeat_field(3, "child_account_section")],
76
+ [repeat_field(3, "account_role_section")],
77
+ [repeat_field(3, "inherited_account_role_section")],
78
+ ],
79
+ [child_account_section, account_role_section, inherited_account_role_section],
80
+ )
81
+
82
+
83
+ class AccountRoleAccountDisplayConfig(DisplayViewConfig):
84
+ def get_instance_display(self) -> Display:
85
+ return create_simple_display([["role_type", "entry"], ["is_hidden", "authorized_hidden_users"]])
86
+
87
+ def get_list_display(self) -> Optional[dp.ListDisplay]:
88
+ return dp.ListDisplay(
89
+ fields=[
90
+ dp.Field(key="role_type", label="Role"),
91
+ dp.Field(key="entry", label="entry"),
92
+ dp.Field(key="is_currently_valid", label="Valid"),
93
+ dp.Field(key="is_hidden", label="Hidden"),
94
+ dp.Field(key="authorized_hidden_users", label="Authorized Hidden Users"),
95
+ ]
96
+ )
97
+
98
+
99
+ class InheritedAccountRoleAccountDisplayConfig(AccountRoleAccountDisplayConfig):
100
+ def get_list_display(self) -> Optional[dp.ListDisplay]:
101
+ return dp.ListDisplay(
102
+ fields=[
103
+ dp.Field(key="account", label="Account"),
104
+ dp.Field(key="role_type", label="Role"),
105
+ dp.Field(key="entry", label="Entry"),
106
+ dp.Field(key="is_currently_valid", label="Valid"),
107
+ dp.Field(key="is_hidden", label="Hidden"),
108
+ dp.Field(key="authorized_hidden_users", label="Authorized Hidden Users"),
109
+ ]
110
+ )