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.
Files changed (182) hide show
  1. wbcrm/__init__.py +1 -0
  2. wbcrm/admin/__init__.py +5 -0
  3. wbcrm/admin/accounts.py +60 -0
  4. wbcrm/admin/activities.py +104 -0
  5. wbcrm/admin/events.py +43 -0
  6. wbcrm/admin/groups.py +8 -0
  7. wbcrm/admin/products.py +9 -0
  8. wbcrm/apps.py +5 -0
  9. wbcrm/configurations/__init__.py +1 -0
  10. wbcrm/configurations/base.py +16 -0
  11. wbcrm/dynamic_preferences_registry.py +38 -0
  12. wbcrm/factories/__init__.py +14 -0
  13. wbcrm/factories/accounts.py +57 -0
  14. wbcrm/factories/activities.py +124 -0
  15. wbcrm/factories/groups.py +24 -0
  16. wbcrm/factories/products.py +11 -0
  17. wbcrm/filters/__init__.py +10 -0
  18. wbcrm/filters/accounts.py +80 -0
  19. wbcrm/filters/activities.py +204 -0
  20. wbcrm/filters/groups.py +21 -0
  21. wbcrm/filters/products.py +38 -0
  22. wbcrm/filters/signals.py +95 -0
  23. wbcrm/fixtures/wbcrm.json +1215 -0
  24. wbcrm/kpi_handlers/activities.py +171 -0
  25. wbcrm/locale/de/LC_MESSAGES/django.mo +0 -0
  26. wbcrm/locale/de/LC_MESSAGES/django.po +1557 -0
  27. wbcrm/locale/de/LC_MESSAGES/django.po.translated +1630 -0
  28. wbcrm/locale/en/LC_MESSAGES/django.mo +0 -0
  29. wbcrm/locale/en/LC_MESSAGES/django.po +1466 -0
  30. wbcrm/locale/fr/LC_MESSAGES/django.mo +0 -0
  31. wbcrm/locale/fr/LC_MESSAGES/django.po +1467 -0
  32. wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
  33. wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
  34. wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
  35. wbcrm/migrations/0004_alter_activity_status.py +28 -0
  36. wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
  37. wbcrm/migrations/0006_alter_activity_location.py +17 -0
  38. wbcrm/migrations/0007_alter_account_status.py +23 -0
  39. wbcrm/migrations/0008_alter_activity_options.py +16 -0
  40. wbcrm/migrations/0009_alter_account_is_public.py +19 -0
  41. wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
  42. wbcrm/migrations/0011_activity_summary.py +22 -0
  43. wbcrm/migrations/0012_alter_activity_summary.py +17 -0
  44. wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
  45. wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
  46. wbcrm/migrations/0015_alter_activity_type.py +23 -0
  47. wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
  48. wbcrm/migrations/0017_event.py +40 -0
  49. wbcrm/migrations/0018_activity_search_vector.py +24 -0
  50. wbcrm/migrations/__init__.py +0 -0
  51. wbcrm/models/__init__.py +5 -0
  52. wbcrm/models/accounts.py +648 -0
  53. wbcrm/models/activities.py +1419 -0
  54. wbcrm/models/events.py +15 -0
  55. wbcrm/models/groups.py +119 -0
  56. wbcrm/models/llm/activity_summaries.py +41 -0
  57. wbcrm/models/llm/analyze_relationship.py +50 -0
  58. wbcrm/models/products.py +86 -0
  59. wbcrm/models/recurrence.py +280 -0
  60. wbcrm/preferences.py +13 -0
  61. wbcrm/report/activity_report.py +110 -0
  62. wbcrm/serializers/__init__.py +23 -0
  63. wbcrm/serializers/accounts.py +141 -0
  64. wbcrm/serializers/activities.py +525 -0
  65. wbcrm/serializers/groups.py +30 -0
  66. wbcrm/serializers/products.py +58 -0
  67. wbcrm/serializers/recurrence.py +91 -0
  68. wbcrm/serializers/signals.py +71 -0
  69. wbcrm/static/wbcrm/markdown/documentation/activity.md +86 -0
  70. wbcrm/static/wbcrm/markdown/documentation/activitytype.md +20 -0
  71. wbcrm/static/wbcrm/markdown/documentation/group.md +2 -0
  72. wbcrm/static/wbcrm/markdown/documentation/product.md +11 -0
  73. wbcrm/synchronization/__init__.py +0 -0
  74. wbcrm/synchronization/activity/__init__.py +0 -0
  75. wbcrm/synchronization/activity/admin.py +73 -0
  76. wbcrm/synchronization/activity/backend.py +214 -0
  77. wbcrm/synchronization/activity/backends/__init__.py +0 -0
  78. wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
  79. wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +406 -0
  80. wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
  81. wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/create.py +75 -0
  82. wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/delete.py +78 -0
  83. wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/update.py +155 -0
  84. wbcrm/synchronization/activity/backends/google/request_utils/internal_to_external/update.py +181 -0
  85. wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
  86. wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
  87. wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
  88. wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
  89. wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
  90. wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
  91. wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
  92. wbcrm/synchronization/activity/backends/google/utils.py +217 -0
  93. wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
  94. wbcrm/synchronization/activity/backends/outlook/backend.py +593 -0
  95. wbcrm/synchronization/activity/backends/outlook/msgraph.py +436 -0
  96. wbcrm/synchronization/activity/backends/outlook/parser.py +432 -0
  97. wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
  98. wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
  99. wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
  100. wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +118 -0
  101. wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +274 -0
  102. wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +249 -0
  103. wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +174 -0
  104. wbcrm/synchronization/activity/controller.py +627 -0
  105. wbcrm/synchronization/activity/dynamic_preferences_registry.py +119 -0
  106. wbcrm/synchronization/activity/preferences.py +27 -0
  107. wbcrm/synchronization/activity/shortcuts.py +16 -0
  108. wbcrm/synchronization/activity/tasks.py +21 -0
  109. wbcrm/synchronization/activity/urls.py +7 -0
  110. wbcrm/synchronization/activity/utils.py +46 -0
  111. wbcrm/synchronization/activity/views.py +41 -0
  112. wbcrm/synchronization/admin.py +1 -0
  113. wbcrm/synchronization/apps.py +14 -0
  114. wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
  115. wbcrm/synchronization/management.py +36 -0
  116. wbcrm/synchronization/tasks.py +1 -0
  117. wbcrm/synchronization/urls.py +5 -0
  118. wbcrm/tasks.py +264 -0
  119. wbcrm/templates/email/activity.html +98 -0
  120. wbcrm/templates/email/activity_report.html +6 -0
  121. wbcrm/templates/email/daily_summary.html +72 -0
  122. wbcrm/templates/email/global_daily_summary.html +85 -0
  123. wbcrm/tests/__init__.py +0 -0
  124. wbcrm/tests/accounts/__init__.py +0 -0
  125. wbcrm/tests/accounts/test_models.py +393 -0
  126. wbcrm/tests/accounts/test_viewsets.py +88 -0
  127. wbcrm/tests/conftest.py +76 -0
  128. wbcrm/tests/disable_signals.py +62 -0
  129. wbcrm/tests/e2e/__init__.py +1 -0
  130. wbcrm/tests/e2e/e2e_wbcrm_utility.py +83 -0
  131. wbcrm/tests/e2e/test_e2e.py +370 -0
  132. wbcrm/tests/test_assignee_methods.py +40 -0
  133. wbcrm/tests/test_chartviewsets.py +112 -0
  134. wbcrm/tests/test_dto.py +64 -0
  135. wbcrm/tests/test_filters.py +52 -0
  136. wbcrm/tests/test_models.py +217 -0
  137. wbcrm/tests/test_recurrence.py +292 -0
  138. wbcrm/tests/test_report.py +21 -0
  139. wbcrm/tests/test_serializers.py +171 -0
  140. wbcrm/tests/test_tasks.py +95 -0
  141. wbcrm/tests/test_viewsets.py +967 -0
  142. wbcrm/tests/tests.py +121 -0
  143. wbcrm/typings.py +109 -0
  144. wbcrm/urls.py +67 -0
  145. wbcrm/viewsets/__init__.py +22 -0
  146. wbcrm/viewsets/accounts.py +122 -0
  147. wbcrm/viewsets/activities.py +341 -0
  148. wbcrm/viewsets/buttons/__init__.py +7 -0
  149. wbcrm/viewsets/buttons/accounts.py +27 -0
  150. wbcrm/viewsets/buttons/activities.py +89 -0
  151. wbcrm/viewsets/buttons/signals.py +17 -0
  152. wbcrm/viewsets/display/__init__.py +12 -0
  153. wbcrm/viewsets/display/accounts.py +110 -0
  154. wbcrm/viewsets/display/activities.py +444 -0
  155. wbcrm/viewsets/display/groups.py +22 -0
  156. wbcrm/viewsets/display/products.py +105 -0
  157. wbcrm/viewsets/endpoints/__init__.py +8 -0
  158. wbcrm/viewsets/endpoints/accounts.py +25 -0
  159. wbcrm/viewsets/endpoints/activities.py +30 -0
  160. wbcrm/viewsets/endpoints/groups.py +7 -0
  161. wbcrm/viewsets/endpoints/products.py +9 -0
  162. wbcrm/viewsets/groups.py +38 -0
  163. wbcrm/viewsets/menu/__init__.py +8 -0
  164. wbcrm/viewsets/menu/accounts.py +18 -0
  165. wbcrm/viewsets/menu/activities.py +49 -0
  166. wbcrm/viewsets/menu/groups.py +16 -0
  167. wbcrm/viewsets/menu/products.py +20 -0
  168. wbcrm/viewsets/mixins.py +35 -0
  169. wbcrm/viewsets/previews/__init__.py +1 -0
  170. wbcrm/viewsets/previews/activities.py +10 -0
  171. wbcrm/viewsets/products.py +57 -0
  172. wbcrm/viewsets/recurrence.py +27 -0
  173. wbcrm/viewsets/titles/__init__.py +13 -0
  174. wbcrm/viewsets/titles/accounts.py +23 -0
  175. wbcrm/viewsets/titles/activities.py +61 -0
  176. wbcrm/viewsets/titles/products.py +13 -0
  177. wbcrm/viewsets/titles/utils.py +46 -0
  178. wbcrm/workflows/__init__.py +1 -0
  179. wbcrm/workflows/assignee_methods.py +25 -0
  180. wbcrm-1.56.8.dist-info/METADATA +11 -0
  181. wbcrm-1.56.8.dist-info/RECORD +182 -0
  182. wbcrm-1.56.8.dist-info/WHEEL +5 -0
@@ -0,0 +1,204 @@
1
+ from datetime import date, timedelta
2
+
3
+ import django_filters
4
+ from django.contrib.auth import get_user_model
5
+ from django.db.models import Exists, OuterRef, Q
6
+ from django.db.models.query import QuerySet
7
+ from django.utils import timezone
8
+ from django.utils.translation import gettext_lazy
9
+ from django.utils.translation import gettext_lazy as _
10
+ from dynamic_preferences.registries import global_preferences_registry
11
+ from psycopg.types.range import DateRange, TimestamptzRange
12
+ from wbcore import filters as wb_filters
13
+ from wbcore.contrib.agenda.filters import CalendarItemPeriodBaseFilterSet
14
+ from wbcore.contrib.agenda.models import CalendarItem
15
+ from wbcore.contrib.directory.models import CustomerStatus
16
+ from wbcore.contrib.directory.models.entries import Company, Person
17
+ from wbcore.filters.lookups import ALL_TEXT_LOOKUPS
18
+
19
+ from wbcrm.models import Activity, ActivityParticipant, ActivityType, Group
20
+ from wbcrm.models.activities import ActivityCompanyThroughModel
21
+
22
+
23
+ def get_employee_filter_params(request, view):
24
+ if employer_id := global_preferences_registry.manager()["directory__main_company"]:
25
+ return {"employers": employer_id}
26
+ return {}
27
+
28
+
29
+ class ActivityBaseFilterSet(CalendarItemPeriodBaseFilterSet):
30
+ type = wb_filters.ModelMultipleChoiceFilter(
31
+ label=gettext_lazy("Types"),
32
+ queryset=ActivityType.objects.all(),
33
+ endpoint=ActivityType.get_representation_endpoint(),
34
+ value_key=ActivityType.get_representation_value_key(),
35
+ label_key=ActivityType.get_representation_label_key(),
36
+ )
37
+
38
+ visibility = wb_filters.MultipleChoiceFilter(
39
+ choices=CalendarItem.Visibility.choices,
40
+ label=gettext_lazy("Visibility"),
41
+ )
42
+
43
+ participants = wb_filters.ModelMultipleChoiceFilter(
44
+ label=gettext_lazy("Participants"),
45
+ queryset=Person.objects.all(),
46
+ endpoint=Person.get_representation_endpoint(),
47
+ value_key=Person.get_representation_value_key(),
48
+ label_key=Person.get_representation_label_key(),
49
+ )
50
+
51
+ companies = wb_filters.ModelMultipleChoiceFilter(
52
+ label=gettext_lazy("Companies"),
53
+ queryset=Company.objects.all(),
54
+ endpoint=Company.get_representation_endpoint(),
55
+ value_key=Company.get_representation_value_key(),
56
+ label_key=Company.get_representation_label_key(),
57
+ )
58
+
59
+ groups = wb_filters.ModelMultipleChoiceFilter(
60
+ label=gettext_lazy("Groups"),
61
+ queryset=Group.objects.all(),
62
+ endpoint=Group.get_representation_endpoint(),
63
+ value_key=Group.get_representation_value_key(),
64
+ label_key=Group.get_representation_label_key(),
65
+ )
66
+
67
+
68
+ class ActivityFilter(ActivityBaseFilterSet):
69
+ clients_of = wb_filters.ModelChoiceFilter(
70
+ label=gettext_lazy("Clients of"),
71
+ queryset=Person.objects.all(),
72
+ endpoint=Person.get_representation_endpoint(),
73
+ value_key=Person.get_representation_value_key(),
74
+ label_key=Person.get_representation_label_key(),
75
+ method="filter_clients_of",
76
+ filter_params=get_employee_filter_params,
77
+ )
78
+
79
+ latest_reviewer = wb_filters.ModelMultipleChoiceFilter(
80
+ label=gettext_lazy("Latest Reviewers"),
81
+ queryset=Person.objects.all(),
82
+ endpoint=Person.get_representation_endpoint(),
83
+ value_key=Person.get_representation_value_key(),
84
+ label_key=Person.get_representation_label_key(),
85
+ )
86
+
87
+ importance = wb_filters.MultipleChoiceFilter(
88
+ label=gettext_lazy("Importance"), choices=Activity.Importance.choices, widget=django_filters.widgets.CSVWidget
89
+ )
90
+
91
+ created = wb_filters.DateTimeRangeFilter(label=gettext_lazy("Created"))
92
+ edited = wb_filters.DateTimeRangeFilter(label=gettext_lazy("Edited"))
93
+
94
+ only_recent = wb_filters.BooleanFilter(label=gettext_lazy("Only Recent"), method="boolean_only_recent")
95
+
96
+ customer_status = wb_filters.ModelChoiceFilter(
97
+ label=gettext_lazy("Customer Status"),
98
+ queryset=CustomerStatus.objects.all(),
99
+ endpoint=CustomerStatus.get_representation_endpoint(),
100
+ value_key=CustomerStatus.get_representation_value_key(),
101
+ label_key=CustomerStatus.get_representation_label_key(),
102
+ method="filter_customer_status",
103
+ help_text="Filter activities based on the company customer status at the activity creation time",
104
+ )
105
+
106
+ def boolean_only_recent(self, queryset: QuerySet[Activity], name, value: bool | None) -> QuerySet[Activity]:
107
+ if value:
108
+ today = date.today()
109
+ next_week = today + timedelta(days=7)
110
+ last_month = today - timedelta(days=30)
111
+ return queryset.filter(period__overlap=TimestamptzRange(last_month, next_week))
112
+ return queryset
113
+
114
+ def filter_clients_of(self, queryset, name, value):
115
+ if value:
116
+ user = self.request.user
117
+ return Activity.objects.filter(
118
+ (Q(participants__relationship_managers=value) | Q(companies__relationship_managers=value))
119
+ & (
120
+ Q(visibility=CalendarItem.Visibility.PUBLIC)
121
+ | (
122
+ Q(visibility=CalendarItem.Visibility.CONFIDENTIAL) # TODO move that to a queryset method
123
+ & Exists(
124
+ get_user_model().objects.filter(
125
+ Q(id=user.id)
126
+ & (
127
+ Q(groups__permissions__codename="administrate_confidential_items")
128
+ | Q(user_permissions__codename="administrate_confidential_items")
129
+ )
130
+ )
131
+ )
132
+ )
133
+ | Q(
134
+ Exists(
135
+ ActivityParticipant.objects.filter(activity_id=OuterRef("pk"), participant=user.profile)
136
+ )
137
+ )
138
+ | Q(assigned_to=user.profile)
139
+ )
140
+ )
141
+ return queryset
142
+
143
+ def filter_customer_status(self, queryset, name, value):
144
+ if value:
145
+ rel = ActivityCompanyThroughModel.objects.filter(customer_status=value)
146
+ return queryset.filter(id__in=rel.values("activity"))
147
+ return queryset
148
+
149
+ class Meta:
150
+ model = Activity
151
+ fields = {
152
+ "status": ["exact"],
153
+ "repeat_choice": ["exact"],
154
+ "title": ALL_TEXT_LOOKUPS,
155
+ "result": ALL_TEXT_LOOKUPS,
156
+ "description": ALL_TEXT_LOOKUPS,
157
+ }
158
+
159
+
160
+ def default_activitychart_get_params(*args, **kwargs):
161
+ current_last_week_date_start = timezone.now().date() - timedelta(days=7)
162
+ current_next_week_date_end = timezone.now().date() + timedelta(days=7)
163
+ return DateRange(current_last_week_date_start, current_next_week_date_end)
164
+
165
+
166
+ class ActivityChartFilter(ActivityBaseFilterSet):
167
+ status = wb_filters.MultipleChoiceFilter(
168
+ label=gettext_lazy("Status"), choices=Activity.Status.choices, widget=django_filters.widgets.CSVWidget
169
+ )
170
+ period = wb_filters.DateTimeRangeFilter(label="Period", required=True, initial=default_activitychart_get_params)
171
+
172
+ class Meta:
173
+ model = Activity
174
+ fields = {}
175
+
176
+
177
+ class ActivityTypeFilter(wb_filters.FilterSet):
178
+ score = wb_filters.MultipleChoiceFilter(
179
+ label=_("Multipliers"), choices=ActivityType.Score.choices[:3], widget=django_filters.widgets.CSVWidget
180
+ )
181
+
182
+ class Meta:
183
+ model = ActivityType
184
+ fields = {
185
+ "title": ["exact", "icontains"],
186
+ "color": ["exact", "icontains"],
187
+ "default": ["exact"],
188
+ }
189
+
190
+
191
+ class ActivityParticipantFilter(wb_filters.FilterSet):
192
+ is_occupied_filter = wb_filters.BooleanFilter(
193
+ label=gettext_lazy("Is occupied by different activity"), method="boolean_is_occupied"
194
+ )
195
+
196
+ def boolean_is_occupied(self, queryset, name, value):
197
+ if value is None:
198
+ return queryset
199
+ else:
200
+ return queryset.filter(is_occupied=value)
201
+
202
+ class Meta:
203
+ model = ActivityParticipant
204
+ fields = {}
@@ -0,0 +1,21 @@
1
+ from django.utils.translation import gettext_lazy as _
2
+ from wbcore import filters as wb_filters
3
+ from wbcore.contrib.directory.models import Entry
4
+
5
+ from wbcrm.models import Group
6
+
7
+
8
+ class GroupFilter(wb_filters.FilterSet):
9
+ members = wb_filters.ModelMultipleChoiceFilter(
10
+ label=_("Members"),
11
+ queryset=Entry.objects.all(),
12
+ endpoint=Entry.get_representation_endpoint(),
13
+ value_key=Entry.get_representation_value_key(),
14
+ label_key=Entry.get_representation_label_key(),
15
+ )
16
+
17
+ class Meta:
18
+ model = Group
19
+ fields = {
20
+ "title": ["exact", "icontains"],
21
+ }
@@ -0,0 +1,38 @@
1
+ from django.utils.translation import gettext_lazy as _
2
+ from wbcore import filters as wb_filters
3
+ from wbcore.contrib.directory.models import Company
4
+
5
+ from wbcrm.models import Product, ProductCompanyRelationship
6
+
7
+
8
+ class ProductFilterSet(wb_filters.FilterSet):
9
+ prospects = wb_filters.ModelMultipleChoiceFilter(
10
+ label=_("Prospects"),
11
+ queryset=Company.objects.all(),
12
+ endpoint=Company.get_representation_endpoint(),
13
+ value_key=Company.get_representation_value_key(),
14
+ label_key=Company.get_representation_label_key(),
15
+ )
16
+
17
+ class Meta:
18
+ model = Product
19
+ fields = {
20
+ "title": ["exact", "icontains"],
21
+ "is_competitor": ["exact"],
22
+ }
23
+
24
+
25
+ class ProductCompanyFilterSet(wb_filters.FilterSet):
26
+ competitor_product = wb_filters.BooleanFilter(label=_("Is Competitor"), method="filter_competitor_product")
27
+
28
+ def filter_competitor_product(self, queryset, label, value):
29
+ if value is None:
30
+ return queryset
31
+ else:
32
+ return queryset.filter(product__is_competitor=value)
33
+
34
+ class Meta:
35
+ model = ProductCompanyRelationship
36
+ fields = {
37
+ "product": ["exact"],
38
+ }
@@ -0,0 +1,95 @@
1
+ from datetime import timedelta
2
+
3
+ from django.db.models.query import QuerySet
4
+ from django.dispatch import receiver
5
+ from django.utils import timezone
6
+ from django.utils.translation import gettext_lazy as _
7
+ from psycopg.types.range import TimestamptzRange
8
+ from wbcore import filters as wb_filters
9
+ from wbcore.contrib.directory.filters.entries import (
10
+ CompanyFilter,
11
+ EntryFilter,
12
+ PersonFilter,
13
+ UserIsManagerFilter,
14
+ )
15
+ from wbcore.contrib.directory.models import Entry
16
+ from wbcore.signals.filters import add_filters
17
+
18
+ from wbcrm.models import Account, AccountRole, Product
19
+
20
+
21
+ def choice_noactivity(queryset: QuerySet[Entry], name, value: str | None) -> QuerySet[Entry]:
22
+ """Returns only those entries who didn't participate in any activities during the selected timeframe"""
23
+
24
+ if value is None:
25
+ return queryset
26
+ else:
27
+ value = int(value)
28
+ if value:
29
+ end = timezone.now()
30
+ start = end - timedelta(days=value)
31
+ no_activity_timeframe = TimestamptzRange(start, end)
32
+ return queryset.exclude(
33
+ calendar_entities__period__overlap=no_activity_timeframe,
34
+ calendar_entities__item_type="wbcrm.Activity",
35
+ )
36
+ # A value of 0 corresponds to "All Time" in the filter so we need exclude any activities the entries participated in
37
+ return queryset.exclude(calendar_entities__item_type="wbcrm.Activity")
38
+
39
+
40
+ @receiver(add_filters, sender=EntryFilter)
41
+ @receiver(add_filters, sender=PersonFilter)
42
+ @receiver(add_filters, sender=CompanyFilter)
43
+ @receiver(add_filters, sender=UserIsManagerFilter)
44
+ def add_account_filter(sender, request=None, *args, **kwargs):
45
+ def _filter_with_account(queryset: QuerySet[Entry], name, value: str | None) -> QuerySet[Entry]:
46
+ accounts = Account.objects.filter_for_user(request.user)
47
+ if value:
48
+ return queryset.filter(accounts__in=accounts).distinct()
49
+ return queryset
50
+
51
+ def _filter_with_account_role(queryset: QuerySet[Entry], name, value: str | None) -> QuerySet[Entry]:
52
+ roles = AccountRole.objects.filter_for_user(request.user)
53
+ if value:
54
+ return queryset.filter(account_roles__in=roles).distinct()
55
+ return queryset
56
+
57
+ def filter_interested_products(queryset, name, value):
58
+ if value:
59
+ return queryset.filter(interested_products__in=value)
60
+ return queryset
61
+
62
+ interested_products = wb_filters.ModelMultipleChoiceFilter(
63
+ label="Interested Products",
64
+ help_text="Filter by products (ours and comptetitors) that the customer is interested in",
65
+ queryset=Product.objects.all(),
66
+ endpoint=Product.get_representation_endpoint(),
67
+ value_key=Product.get_representation_value_key(),
68
+ label_key=Product.get_representation_label_key(),
69
+ method=filter_interested_products,
70
+ )
71
+
72
+ return {
73
+ "without_activity": wb_filters.ChoiceFilter(
74
+ label=_("No Activity"),
75
+ help_text="Filter for entries without any activities",
76
+ field_name="without_activity",
77
+ choices=[
78
+ (31, _("Last Month")),
79
+ (92, _("Last 3 Months")),
80
+ (182, _("Last 6 Months")),
81
+ (365, _("Last 12 Months")),
82
+ (0, _("All Time")),
83
+ ],
84
+ method=choice_noactivity,
85
+ ),
86
+ "with_account": wb_filters.BooleanFilter(
87
+ label="With Account", help_text="Filter for entries without an account", method=_filter_with_account
88
+ ),
89
+ "with_account_role": wb_filters.BooleanFilter(
90
+ label="With Account Role",
91
+ help_text="Filter for entries without an account role",
92
+ method=_filter_with_account_role,
93
+ ),
94
+ "interested_products": interested_products,
95
+ }