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,393 @@
1
+ from datetime import date, timedelta
2
+
3
+ import pytest
4
+ from django.contrib.auth import get_user_model
5
+ from django.contrib.auth.models import Permission
6
+ from faker import Faker
7
+ from psycopg.types.range import DateRange
8
+ from wbcore.contrib.directory.models import Entry
9
+ from wbcore.contrib.directory.signals import deactivate_profile
10
+
11
+ from wbcrm.models import Account, AccountRole, AccountRoleValidity
12
+
13
+ fake = Faker()
14
+
15
+
16
+ @pytest.mark.django_db
17
+ class TestAccountModel:
18
+ def test_init(self, account):
19
+ assert account.id is not None
20
+
21
+ def test_close_account_set_inactive(self, account):
22
+ assert account.is_active
23
+ account.status = Account.Status.CLOSE
24
+ account.save()
25
+ assert not account.is_active
26
+
27
+ def test_disable_account_set_status_close(self, account):
28
+ assert account.status == Account.Status.OPEN
29
+ account.delete()
30
+ assert not account.is_active
31
+ assert account.status == Account.Status.CLOSE
32
+
33
+ def test_account_with_owner_create_account_role(self, account_factory, entry_factory):
34
+ customer = entry_factory.create()
35
+ account = account_factory.create(owner=customer)
36
+ assert account.roles.filter(entry=customer, role_type__key="customer").exists()
37
+
38
+ def test_leaf_account_automatically_set_to_terminal(self, account_factory):
39
+ parent_account = account_factory.create()
40
+
41
+ assert parent_account.is_terminal_account is True
42
+ child_account1 = account_factory.create(parent=parent_account, is_terminal_account=False)
43
+ child_account2 = account_factory.create(parent=parent_account, is_terminal_account=False)
44
+
45
+ parent_account.refresh_from_db()
46
+ child_account1.refresh_from_db()
47
+ child_account2.refresh_from_db()
48
+
49
+ assert parent_account.is_terminal_account is False
50
+ assert child_account1.is_terminal_account is True
51
+ assert child_account2.is_terminal_account is True
52
+
53
+ child_account1.delete(no_deletion=False)
54
+ parent_account.refresh_from_db()
55
+ assert parent_account.is_terminal_account is False # because there is still another child
56
+
57
+ child_account2.delete(no_deletion=False)
58
+ parent_account.refresh_from_db()
59
+ assert parent_account.is_terminal_account is True # There is no children left so we expect it to become leaf
60
+
61
+ def test_disabled_parent_account_disabled_children(self, account_factory):
62
+ parent_account = account_factory.create()
63
+ child_account = account_factory.create(parent=parent_account)
64
+ assert child_account.is_active
65
+ assert parent_account.is_active
66
+
67
+ parent_account.is_active = False
68
+ parent_account.save()
69
+
70
+ child_account.refresh_from_db()
71
+ assert not child_account.is_active
72
+
73
+ def test_private_root_account_set_children_as_private(self, account_factory):
74
+ parent_account = account_factory.create(is_public=True)
75
+ child_account = account_factory.create(parent=parent_account, is_public=True)
76
+ parent_account.is_public = False
77
+ parent_account.save()
78
+ child_account.refresh_from_db()
79
+ assert not child_account.is_public
80
+
81
+ def test_can_manage(self, account, user):
82
+ assert not account.can_administrate(user)
83
+
84
+ perm = Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
85
+ user.user_permissions.add(perm)
86
+
87
+ user = get_user_model().objects.get(
88
+ id=user.id
89
+ ) # we refetch user to clear perm cache, doesn't happen with refresh_from_db
90
+ assert account.can_administrate(user)
91
+
92
+ def test_get_inherited_roles_for_account(self, account_factory, account_role_factory):
93
+ parent_account = account_factory.create()
94
+ parent_role = account_role_factory.create(account=parent_account)
95
+ child_account = account_factory.create(parent=parent_account)
96
+ child_role = account_role_factory.create(account=child_account)
97
+ assert set(child_account.get_inherited_roles_for_account()) == {parent_role}
98
+ assert set(child_account.get_inherited_roles_for_account(include_self=True)) == {child_role, parent_role}
99
+
100
+ @pytest.mark.parametrize("validity_date,account__is_public", [(fake.date_object(), False)])
101
+ def test_can_see_account(self, user, account, account_role_factory, validity_date):
102
+ # Check user doesn't see permission on the account by default
103
+ assert not account.can_see_account(user, validity_date)
104
+ role = account_role_factory.create(account=account, entry=Entry.objects.get(id=user.profile.id))
105
+ # with a valid role, they can see it
106
+ assert account.can_see_account(user, validity_date=validity_date)
107
+
108
+ # with an unvalid role, they cannot see it
109
+ role.validity_set.first().timespan = DateRange(date.min, validity_date - timedelta(days=1))
110
+ role.validity_set.first().save()
111
+ assert account.can_see_account(user, validity_date=validity_date)
112
+
113
+ @pytest.mark.parametrize("account__is_public", [True, False])
114
+ def test_can_see_account_with_internal_employee(self, internal_user_factory, account):
115
+ # Check user doesn't see permission on the account by default
116
+
117
+ # with being an internal user, they can see it
118
+ assert account.can_see_account(internal_user_factory.create()) == account.is_public
119
+
120
+ @pytest.mark.parametrize("account__is_public", [True])
121
+ def test_normaluser_cannot_see_account_public_account(self, user, account):
122
+ assert not account.can_see_account(user)
123
+
124
+ def test_get_accounts_for_customer(self, account_factory, company_factory, employer_employee_relationship_factory):
125
+ owner1 = company_factory.create()
126
+ employee1 = employer_employee_relationship_factory.create(employer=owner1).employee
127
+ owner2 = company_factory.create()
128
+ employee2 = employer_employee_relationship_factory.create(employer=owner2).employee
129
+
130
+ parent_account = account_factory.create(owner=owner1)
131
+ other_parent_account = account_factory.create(owner=owner1)
132
+ child_account = account_factory.create(parent=parent_account, owner=owner2)
133
+ # test if all child account also shows in the queryset as well as the direct related account for that owner
134
+ assert set(Account.get_accounts_for_customer(owner1.entry_ptr)) == {
135
+ parent_account,
136
+ other_parent_account,
137
+ child_account,
138
+ }
139
+ assert set(Account.get_accounts_for_customer(employee1.entry_ptr)) == {
140
+ parent_account,
141
+ other_parent_account,
142
+ child_account,
143
+ }
144
+ assert set(Account.get_accounts_for_customer(owner2.entry_ptr)) == {child_account}
145
+ assert set(Account.get_accounts_for_customer(employee2.entry_ptr)) == {child_account}
146
+
147
+ def test_get_managed_accounts_for_entry(self, entry, account_factory, account_role_factory):
148
+ main_role = account_role_factory.create(entry=entry)
149
+ child_account = account_factory.create(parent=main_role.account)
150
+ other_account = account_factory.create() # noqa
151
+ other_role = account_role_factory.create() # noqa
152
+ assert set(Account.get_managed_accounts_for_entry(entry)) == {main_role.account, child_account}
153
+
154
+ def test_handle_user_deactivation(self, account_role_factory, person_factory):
155
+ profile = person_factory.create()
156
+ substitute_profile = person_factory.create()
157
+
158
+ valid_role = account_role_factory.create(entry=Entry.objects.get(id=profile.id))
159
+ unvalid_role = account_role_factory.create(
160
+ entry=Entry.objects.get(id=profile.id), visibility_daterange=DateRange(date.min, date.today())
161
+ )
162
+
163
+ deactivate_profile.send(AccountRole, instance=profile, substitute_profile=substitute_profile)
164
+ valid_role.refresh_from_db()
165
+ unvalid_role.refresh_from_db()
166
+ assert valid_role.validity_set.first().timespan == DateRange(date.min, date.today() - timedelta(days=1))
167
+ assert valid_role.entry.id == profile.id
168
+ assert unvalid_role.entry.id == profile.id
169
+
170
+ new_role = AccountRole.objects.get(entry_id=substitute_profile.id, account=valid_role.account)
171
+ assert new_role.validity_set.first().timespan == DateRange(date.today() - timedelta(days=1), date.max)
172
+
173
+ @pytest.mark.parametrize(
174
+ "validity_date,is_internal_user, is_superuser",
175
+ [
176
+ (fake.date_object(), True, True),
177
+ (fake.date_object(), False, True),
178
+ (fake.date_object(), True, False),
179
+ (fake.date_object(), False, False),
180
+ ],
181
+ )
182
+ def test_filter_for_user(
183
+ self,
184
+ user_factory,
185
+ internal_user_factory,
186
+ account_factory,
187
+ account_role_factory,
188
+ validity_date,
189
+ is_internal_user,
190
+ is_superuser,
191
+ ):
192
+ # We assume role logic are tested in the AccountRole manager unit test
193
+ # so we test here only:
194
+ # - chain filtering
195
+ # - Return account where user has a valid role
196
+ # - and Return public account if user is superuser or internal
197
+
198
+ if is_internal_user:
199
+ user = internal_user_factory.create()
200
+ else:
201
+ user = user_factory.create()
202
+ if is_superuser:
203
+ user.user_permissions.add(
204
+ Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
205
+ )
206
+ profile_entry = Entry.objects.get(id=user.profile.id)
207
+ public_account = account_factory.create(is_public=True)
208
+ private_account = account_factory.create(is_public=False) # noqa
209
+ valid_role = account_role_factory.create(account__is_public=fake.pybool(), entry=profile_entry)
210
+ children_valid_role_account = account_factory.create(
211
+ parent=valid_role.account, is_public=True
212
+ ) # check that entry with role on the parent account can access the public children account
213
+ account_factory.create(
214
+ parent=valid_role.account, is_public=False
215
+ ) # check that entry with role on the parent account cannot access private children account
216
+
217
+ unvalid_role = account_role_factory.create( # noqa
218
+ account__is_public=False,
219
+ entry=profile_entry,
220
+ visibility_daterange=DateRange(date.min, validity_date - timedelta(days=1)),
221
+ )
222
+ user = get_user_model().objects.get(id=user.id)
223
+ if is_superuser:
224
+ assert set(Account.objects.filter_for_user(user, validity_date)) == set(Account.objects.all())
225
+ elif is_internal_user:
226
+ assert set(Account.objects.filter_for_user(user, validity_date)) == {
227
+ public_account,
228
+ valid_role.account,
229
+ children_valid_role_account,
230
+ }
231
+ assert set(Account.objects.filter(id=public_account.id).filter_for_user(user, validity_date)) == {
232
+ public_account
233
+ } # basic test filter chainage
234
+ else:
235
+ assert set(Account.objects.filter_for_user(user, validity_date)) == {
236
+ valid_role.account,
237
+ children_valid_role_account,
238
+ }
239
+
240
+ def test_cannot_merge_account_with_children(self, account_factory):
241
+ """
242
+ Check that we cannot merge an account that still has children
243
+ """
244
+ base_account = account_factory.create()
245
+ merged_account = account_factory.create()
246
+ children_merged_account = account_factory.create(parent=merged_account)
247
+ base_account.merge(merged_account)
248
+ merged_account.refresh_from_db()
249
+ assert merged_account
250
+
251
+ # but can merge the children into the parent account
252
+ merged_account.merge(children_merged_account)
253
+ with pytest.raises(Account.DoesNotExist):
254
+ children_merged_account.refresh_from_db()
255
+
256
+ def test_cannot_merge_account_on_different_trees(self, account_factory):
257
+ """
258
+ Check that we cannot merge an account that still has children
259
+ """
260
+ base_account = account_factory.create(parent=account_factory.create())
261
+ merged_account = account_factory.create(parent=account_factory.create())
262
+ account_factory.create(parent=merged_account)
263
+ base_account.merge(merged_account)
264
+ merged_account.refresh_from_db()
265
+ assert merged_account
266
+
267
+ def test_account_merging(self, account_factory, account_role_factory, user):
268
+ # TODO implemetns for commission
269
+ pivot_date = date(2023, 1, 1)
270
+ base_account = account_factory.create()
271
+ base_account_role = account_role_factory.create(
272
+ account=base_account, visibility_daterange=DateRange(date.min, pivot_date)
273
+ )
274
+ base_account_role_validity = base_account_role.validity_set.first()
275
+
276
+ merged_account = account_factory.create()
277
+ merged_account_role_overlapping = account_role_factory.create(
278
+ account=merged_account, entry=base_account_role.entry, visibility_daterange=DateRange(pivot_date, date.max)
279
+ )
280
+ merged_account_role_overlapping.authorized_hidden_users.add(user)
281
+ merged_account_role_validity = merged_account_role_overlapping.validity_set.first()
282
+
283
+ merged_account_role_different = account_role_factory.create(account=merged_account)
284
+ merged_account_role_different_validity = merged_account_role_different.validity_set.first()
285
+
286
+ base_account.merge(merged_account)
287
+
288
+ # test that the none overlapping role were forwarded to the base account
289
+ assert set(base_account.roles.all()) == {base_account_role, merged_account_role_different}
290
+
291
+ # test that the validity set for the base role got the new validity from the base merged account role
292
+ assert set(base_account_role.validity_set.all()) == {base_account_role_validity, merged_account_role_validity}
293
+
294
+ # test that the authorized user set on the merged account role were forwarded to the base account
295
+ assert set(base_account_role.authorized_hidden_users.all()) == {user}
296
+
297
+ # test that the merged account was deleted
298
+ with pytest.raises(Account.DoesNotExist):
299
+ merged_account.refresh_from_db()
300
+
301
+ # test that the non overlapping role correctly follows the role
302
+ role = AccountRole.objects.get(account=base_account, entry=merged_account_role_different.entry)
303
+ assert set(role.validity_set.all()) == {merged_account_role_different_validity}
304
+
305
+
306
+ @pytest.mark.django_db
307
+ class TestAccountRoleModel:
308
+ def test_init(self, account_role):
309
+ assert account_role.id is not None
310
+ validity = account_role.validity_set.first()
311
+ assert validity
312
+ assert validity.timespan == DateRange(date.min, date.max)
313
+
314
+ def test_hidden_role_set_account_to_private(self, account_factory, account_role_factory):
315
+ public_account = account_factory.create(is_public=True)
316
+ role = account_role_factory.create(account=public_account, is_hidden=True)
317
+ assert not role.account.is_public
318
+
319
+ # def test_filter_for_user(self, ):
320
+ # # here we want to check that the chained filtering works and
321
+ # # - Internal or super user can see their roles + public account roles if there are unhidden + hidden role if they have the right to see them
322
+ # # - Otherwise, can only see direct roles
323
+ # # - Check if the annotation is_currently_valid is there
324
+ @pytest.mark.parametrize(
325
+ "is_superuser,is_internal_user",
326
+ [
327
+ (True, True),
328
+ (True, False),
329
+ (False, True),
330
+ (False, False),
331
+ ],
332
+ )
333
+ def test_filter_related_account_roles_for_internal_user(
334
+ self, user_factory, internal_user_factory, account_role_factory, is_superuser, is_internal_user
335
+ ):
336
+ if is_internal_user:
337
+ user = internal_user_factory.create()
338
+ else:
339
+ user = user_factory.create()
340
+ if is_superuser:
341
+ user.user_permissions.add(
342
+ Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
343
+ )
344
+ role_unhidden_public_account = account_role_factory.create(is_hidden=False, account__is_public=True)
345
+ role_unhidden_private_account = account_role_factory.create(is_hidden=False, account__is_public=False) # noqa
346
+ role_hidden_public_account_without_privilege = account_role_factory.create( # noqa
347
+ is_hidden=True, account__is_public=True
348
+ )
349
+
350
+ role_hidden_private_account_with_privilege = account_role_factory.create(
351
+ is_hidden=True, account__is_public=False, authorized_hidden_users=[user]
352
+ )
353
+ role_direct = account_role_factory.create(
354
+ entry=Entry.objects.get(id=user.profile.id),
355
+ account__is_public=fake.pybool(),
356
+ )
357
+ role_indirect_private_account = account_role_factory.create(account__is_public=False) # noqa
358
+
359
+ if is_superuser:
360
+ assert set(AccountRole.objects.filter_for_user(user)) == set(AccountRole.objects.all())
361
+ elif is_internal_user:
362
+ assert set(AccountRole.objects.filter_for_user(user)) == {
363
+ role_unhidden_public_account,
364
+ role_hidden_private_account_with_privilege,
365
+ role_direct,
366
+ }
367
+ assert set(AccountRole.objects.filter(is_hidden=True).filter_for_user(user)) == {
368
+ role_hidden_private_account_with_privilege
369
+ } # Test that chain filtering works
370
+ else:
371
+ assert set(AccountRole.objects.filter_for_user(user)) == {role_direct}
372
+
373
+
374
+ @pytest.mark.django_db
375
+ class TestAccountRoleValidityModel:
376
+ @pytest.mark.parametrize("validity_date", [fake.date_object()])
377
+ def test_get_role_validity_subquery(self, account_role, validity_date):
378
+ assert AccountRole.objects.count() == 1
379
+ assert (
380
+ AccountRole.objects.annotate(is_valid=AccountRoleValidity.get_role_validity_subquery(validity_date))
381
+ .filter(is_valid=True)
382
+ .count()
383
+ == 1
384
+ )
385
+
386
+ validity = account_role.validity_set.first()
387
+ validity.timespan = DateRange(date.min, validity_date - timedelta(days=1))
388
+ validity.save()
389
+ assert (
390
+ not AccountRole.objects.annotate(is_valid=AccountRoleValidity.get_role_validity_subquery(validity_date))
391
+ .filter(is_valid=True)
392
+ .exists()
393
+ )
@@ -0,0 +1,88 @@
1
+ import pytest
2
+ from faker import Faker
3
+ from rest_framework.exceptions import AuthenticationFailed
4
+ from rest_framework.test import APIRequestFactory
5
+ from wbcore.contrib.authentication.factories.users import UserFactory
6
+ from wbcore.contrib.directory.models import Entry
7
+
8
+ from wbcrm.factories.accounts import AccountFactory, AccountRoleFactory
9
+ from wbcrm.models import Account
10
+ from wbcrm.viewsets.accounts import (
11
+ AccountModelViewSet,
12
+ AccountRepresentationViewSet,
13
+ AccountRoleAccountModelViewSet,
14
+ ChildAccountAccountModelViewSet,
15
+ InheritedAccountRoleAccountModelViewSet,
16
+ )
17
+
18
+ fake = Faker()
19
+
20
+
21
+ @pytest.mark.django_db
22
+ class TestAccountViewset:
23
+ @pytest.fixture
24
+ def account_user(self):
25
+ # True, we create a superuser
26
+
27
+ if fake.pybool():
28
+ user = UserFactory.create(is_superuser=True)
29
+ else:
30
+ user = UserFactory.create(is_superuser=True)
31
+ entry = Entry.objects.get(id=user.profile.id)
32
+
33
+ # Create a bunch of account and roles
34
+ public_account = AccountFactory.create(is_public=True)
35
+ AccountRoleFactory.create(account=public_account, entry=entry)
36
+ AccountRoleFactory.create(account=public_account)
37
+ private_account = AccountFactory.create(is_public=False)
38
+ AccountRoleFactory.create(account=private_account, entry=entry)
39
+ return user
40
+
41
+ @pytest.mark.parametrize(
42
+ "viewset_class",
43
+ [
44
+ AccountRepresentationViewSet,
45
+ AccountModelViewSet,
46
+ ],
47
+ )
48
+ def test_ensure_permission_on_account(self, account_user, viewset_class):
49
+ request = APIRequestFactory().get("")
50
+ request.user = account_user
51
+ viewset = viewset_class(request=request)
52
+ assert set(viewset.get_queryset()) == set(Account.objects.filter_for_user(account_user))
53
+
54
+ def test_ensure_permission_for_nested_view(self, account_user, account_factory):
55
+ request = APIRequestFactory().get("")
56
+ request.user = account_user
57
+ parent_account = Account.objects.first()
58
+ account_factory.create(parent=parent_account, is_public=True)
59
+ viewset = ChildAccountAccountModelViewSet(request=request, kwargs={"account_id": parent_account.id})
60
+
61
+ assert set(viewset.get_queryset()) == set(
62
+ Account.objects.filter(parent=parent_account).filter_for_user(account_user)
63
+ )
64
+
65
+ def test_ensure_permission_on_account_role_account(self, user, account_factory, account_role_factory):
66
+ request = APIRequestFactory().get("")
67
+ request.user = user
68
+ parent_account = account_factory.create()
69
+ child_account = account_factory.create(parent=parent_account)
70
+ viewset_account_role = AccountRoleAccountModelViewSet(
71
+ request=request, kwargs={"account_id": parent_account.id}
72
+ )
73
+ viewset_inherited_account_role = InheritedAccountRoleAccountModelViewSet(
74
+ request=request, kwargs={"account_id": child_account.id}
75
+ )
76
+ with pytest.raises((AuthenticationFailed,)):
77
+ viewset_account_role.get_queryset()
78
+ with pytest.raises((AuthenticationFailed,)):
79
+ viewset_inherited_account_role.get_queryset()
80
+
81
+ role = account_role_factory.create(account=parent_account, entry=Entry.objects.get(id=user.profile.id))
82
+ account_role_factory.create(account=parent_account) # noise account role
83
+ assert set(viewset_account_role.get_queryset()) == {role}
84
+
85
+ account_role_factory.create(
86
+ account=child_account, entry=Entry.objects.get(id=user.profile.id)
87
+ ) # noise account role
88
+ assert set(viewset_inherited_account_role.get_queryset()) == {role}
@@ -0,0 +1,76 @@
1
+ from wbcore.tests.conftest import * # isort:skip
2
+ import os
3
+
4
+ from django.db.models.signals import pre_migrate
5
+ from pytest_factoryboy import register
6
+ from wbcore.contrib.agenda.factories import CalendarItemFactory, ConferenceRoomFactory
7
+ from wbcore.contrib.authentication.factories import (
8
+ InternalUserFactory,
9
+ SuperUserFactory,
10
+ UserActivityFactory,
11
+ UserFactory,
12
+ )
13
+ from wbcore.contrib.directory.factories import (
14
+ CompanyFactory,
15
+ EmailContactFactory,
16
+ EmployerEmployeeRelationshipFactory,
17
+ EntryFactory,
18
+ PersonFactory,
19
+ )
20
+ from wbcrm.factories import (
21
+ AccountFactory,
22
+ AccountRoleFactory,
23
+ AccountRoleTypeFactory,
24
+ ActivityCompanyFactory,
25
+ ActivityFactory,
26
+ ActivityParticipantFactory,
27
+ ActivityPersonFactory,
28
+ ActivityTypeFactory,
29
+ GroupFactory,
30
+ ProductFactory,
31
+ RecurringActivityFactory,
32
+ )
33
+
34
+ register(EntryFactory)
35
+ register(InternalUserFactory)
36
+ register(EmailContactFactory)
37
+ register(CompanyFactory)
38
+ register(PersonFactory)
39
+ register(EmployerEmployeeRelationshipFactory)
40
+ register(ActivityFactory)
41
+ register(ActivityTypeFactory)
42
+ register(RecurringActivityFactory)
43
+ register(ActivityCompanyFactory)
44
+ register(ActivityPersonFactory)
45
+ register(ActivityParticipantFactory)
46
+ register(GroupFactory)
47
+ register(ProductFactory)
48
+ register(ConferenceRoomFactory)
49
+ register(AccountFactory)
50
+ register(AccountRoleFactory)
51
+ register(AccountRoleTypeFactory)
52
+ register(CalendarItemFactory)
53
+
54
+ # Authentication
55
+ register(UserFactory)
56
+ register(SuperUserFactory, "superuser")
57
+ register(UserActivityFactory)
58
+
59
+
60
+ @pytest.fixture(autouse=True, scope="session")
61
+ def django_test_environment(django_test_environment):
62
+ from django.apps import apps
63
+
64
+ get_models = apps.get_models
65
+
66
+ for m in [m for m in get_models() if not m._meta.managed]:
67
+ m._meta.managed = True
68
+
69
+
70
+ @pytest.fixture
71
+ def chrome_options(chrome_options):
72
+ # chrome_options.add_argument("--headless=new")
73
+ return chrome_options
74
+
75
+
76
+ pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("geography"))
@@ -0,0 +1,62 @@
1
+ import inspect
2
+ import re
3
+ from collections import defaultdict
4
+
5
+ from django.db.models.signals import (
6
+ ModelSignal,
7
+ post_delete,
8
+ post_init,
9
+ post_migrate,
10
+ post_save,
11
+ pre_delete,
12
+ pre_init,
13
+ pre_migrate,
14
+ pre_save,
15
+ )
16
+
17
+
18
+ class DisableSignals(object):
19
+ def __init__(self, disabled_signals=None):
20
+ self.stashed_signals = defaultdict(list)
21
+ self.disabled_signals = disabled_signals or [
22
+ pre_init,
23
+ post_init,
24
+ pre_save,
25
+ post_save,
26
+ pre_delete,
27
+ post_delete,
28
+ pre_migrate,
29
+ post_migrate,
30
+ ]
31
+
32
+ def __enter__(self):
33
+ for signal in self.disabled_signals:
34
+ self.disconnect(signal)
35
+
36
+ def __exit__(self, exc_type, exc_val, exc_tb):
37
+ for signal in list(self.stashed_signals):
38
+ self.reconnect(signal)
39
+
40
+ def disconnect(self, signal):
41
+ self.stashed_signals[signal] = signal.receivers
42
+ signal.receivers = []
43
+
44
+ def reconnect(self, signal):
45
+ signal.receivers = self.stashed_signals.get(signal, [])
46
+ del self.stashed_signals[signal]
47
+
48
+
49
+ # https://stackoverflow.com/questions/55578230/django-how-to-visualize-signals-and-save-overrides
50
+ RECEIVER_MODELS = re.compile(r"sender=(\w+)\W")
51
+
52
+
53
+ class DisableSignalsNotification(DisableSignals):
54
+ def __enter__(self):
55
+ for signal in self.disabled_signals:
56
+ if not isinstance(signal, ModelSignal):
57
+ continue
58
+ for _, receiver in signal.receivers:
59
+ rcode = inspect.getsource(receiver())
60
+ rmodel = RECEIVER_MODELS.findall(rcode)
61
+ if "Notification" in rmodel:
62
+ self.disconnect(signal)
@@ -0,0 +1 @@
1
+ from .e2e_wbcrm_utility import create_new_activity_instance, set_up_activities