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,52 @@
1
+ import pytest
2
+ from django.utils import timezone
3
+ from rest_framework.test import APIRequestFactory
4
+ from wbcore.contrib.authentication.factories import UserFactory
5
+ from wbcore.contrib.directory.factories.entries import PersonWithEmployerFactory
6
+ from wbcore.contrib.directory.models import Company, Person
7
+
8
+ from wbcrm.filters.signals import choice_noactivity
9
+ from wbcrm.viewsets import ActivityViewSet
10
+
11
+
12
+ @pytest.mark.django_db
13
+ class TestSpecificFiltersActivities:
14
+ def test_filter_clients_of(self, activity_factory, person_factory):
15
+ person1 = person_factory()
16
+ person2 = person_factory()
17
+ activity_factory(participants=(person_factory(relationship_managers=(person1,)),))
18
+ mvs = ActivityViewSet(kwargs={})
19
+ request = APIRequestFactory().get("")
20
+ request.user = UserFactory(is_active=True, is_superuser=True)
21
+ mvs.request = APIRequestFactory().get("")
22
+ qs = ActivityViewSet.get_model().objects.all()
23
+ assert mvs.filterset_class(request=request).filter_clients_of(qs, "", None) == qs
24
+ assert mvs.filterset_class(request=request).filter_clients_of(qs, "", person1).count() == 1
25
+ assert mvs.filterset_class(request=request).filter_clients_of(qs, "", person2).count() == 0
26
+
27
+
28
+ @pytest.mark.django_db
29
+ class TestSpecificFiltersEntries:
30
+ @pytest.mark.parametrize("base_class", [Person, Company])
31
+ def test_choice_noactivity(self, activity_factory, base_class):
32
+ person = PersonWithEmployerFactory()
33
+ company = person.employer.first().employer
34
+ entry_instance = person if base_class == Person else company
35
+ activity = activity_factory(
36
+ creator=person,
37
+ assigned_to=person,
38
+ latest_reviewer=person,
39
+ preceded_by=None,
40
+ participants=(person,),
41
+ companies=(company,),
42
+ )
43
+ number_of_days_since_last_activity = (timezone.now() - activity.period.upper).days + 1
44
+ number_of_days_no_activity = (timezone.now() - activity.period.upper).days - 1
45
+ qs = base_class.objects.all()
46
+ qs_count = qs.count()
47
+ assert choice_noactivity(qs, "", None) == qs
48
+ assert choice_noactivity(qs, "", 0).count() == qs_count - 1
49
+ assert entry_instance not in choice_noactivity(qs, "", 0)
50
+ assert choice_noactivity(qs, "", number_of_days_since_last_activity).count() == qs_count - 1
51
+ assert entry_instance not in choice_noactivity(qs, "", number_of_days_since_last_activity)
52
+ assert choice_noactivity(qs, "", number_of_days_no_activity).count() == qs_count
@@ -0,0 +1,217 @@
1
+ from datetime import timedelta
2
+
3
+ import pytest
4
+ from django.apps import apps
5
+ from django.utils import timezone
6
+ from django_fsm import TransitionNotAllowed
7
+ from dynamic_preferences.registries import global_preferences_registry
8
+ from wbcore.contrib.agenda.models import CalendarItem
9
+ from wbcore.contrib.authentication.factories import (
10
+ AuthenticatedPersonFactory,
11
+ UserFactory,
12
+ )
13
+ from wbcore.contrib.directory.factories import PersonWithEmployerFactory
14
+ from wbcore.contrib.directory.models import Entry
15
+ from wbcore.contrib.icons import WBIcon
16
+
17
+ from wbcrm.models import Activity, add_employer_to_activities
18
+
19
+
20
+ @pytest.mark.django_db
21
+ class TestSpecificModelsActivities:
22
+ def test_get_color_map(self, activity_factory):
23
+ activity1 = activity_factory()
24
+ assert activity1.Status.get_color_map()
25
+
26
+ def test_get_minutes_correspondance(self, activity_factory):
27
+ activity1 = activity_factory()
28
+ assert activity1.ReminderChoice.get_minutes_correspondance("NEVER")
29
+ assert activity1.ReminderChoice.get_minutes_correspondance("EVENT_TIME") == 0
30
+ assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_5")
31
+ assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_15")
32
+ assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_30")
33
+ assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_1")
34
+ assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_2")
35
+ assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_12")
36
+ assert activity1.ReminderChoice.get_minutes_correspondance("WEEKS_1")
37
+
38
+ def test_str(self, activity_factory):
39
+ assert activity_factory(preceded_by=None).__str__()
40
+
41
+ def test_get_participants(self, activity_factory, person_factory, internal_user_factory, company_factory):
42
+ person1 = person_factory()
43
+ company1 = company_factory()
44
+ if apps.is_installed("wbhuman_resources"):
45
+ from wbhuman_resources.factories import EmployeeHumanResourceFactory
46
+
47
+ person2 = AuthenticatedPersonFactory(employers=(company1,))
48
+ EmployeeHumanResourceFactory(profile=person2)
49
+ else:
50
+ person2 = person_factory(employers=(company1,))
51
+ u = internal_user_factory.create()
52
+ u.profile = person2
53
+ u.save()
54
+ activity1 = activity_factory(participants=(person1,))
55
+ assert activity1.get_participants().count() == 1
56
+ assert activity1.get_participants()[0] == person1
57
+
58
+ def test_get_activities_for_user(self, activity_factory):
59
+ activity0 = activity_factory(preceded_by=None)
60
+ user = UserFactory(is_active=True, is_superuser=True)
61
+ qs = activity0.get_activities_for_user(user)
62
+ assert qs.count() == 1
63
+
64
+ user2 = UserFactory(is_active=True)
65
+ qs2 = activity0.get_activities_for_user(user2)
66
+ assert qs2.count() == 0
67
+
68
+ def test_add_group_to_activity(self, activity_factory, group_factory, person_factory, company_factory):
69
+ activity_participant_a = person_factory()
70
+ group_member_a = person_factory()
71
+ member_a_entry = Entry.objects.get(pk=group_member_a.pk)
72
+ group_member_b = company_factory()
73
+ member_b_entry = Entry.objects.get(pk=group_member_b.pk)
74
+ group_a = group_factory(members=(member_a_entry, member_b_entry))
75
+
76
+ activity = activity_factory(
77
+ participants=[
78
+ activity_participant_a,
79
+ ]
80
+ )
81
+ assert activity.participants.filter(pk=activity_participant_a.pk).exists()
82
+ assert not activity.participants.filter(pk=group_member_a.pk).exists()
83
+ assert not activity.companies.filter(pk=group_member_b.pk).exists()
84
+
85
+ activity.groups.set((group_a,))
86
+ activity.save()
87
+
88
+ assert activity.participants.filter(pk=activity_participant_a.pk).exists()
89
+ assert activity.participants.filter(pk=group_member_a.pk).exists()
90
+ assert activity.companies.filter(pk=group_member_b.pk).exists()
91
+
92
+ def test_remove_group_to_activity(self, activity_factory, group_factory, person_factory, company_factory):
93
+ group_member_a = person_factory()
94
+ group_member_b = person_factory()
95
+ group_member_c = person_factory()
96
+ group_member_d = company_factory()
97
+ group_a = group_factory(members=(group_member_a.entry_ptr, group_member_c.entry_ptr, group_member_d.entry_ptr))
98
+ group_b = group_factory(members=(group_member_b.entry_ptr, group_member_c.entry_ptr))
99
+
100
+ activity = activity_factory(groups=[group_a, group_b])
101
+
102
+ assert activity.participants.filter(pk=group_member_a.pk).exists()
103
+ assert activity.participants.filter(pk=group_member_b.pk).exists()
104
+ assert activity.participants.filter(pk=group_member_c.pk).exists()
105
+ assert activity.companies.filter(pk=group_member_d.pk).exists()
106
+
107
+ activity.groups.remove(group_a.pk)
108
+ activity.save()
109
+
110
+ assert not activity.participants.filter(pk=group_member_a.pk).exists()
111
+ assert activity.participants.filter(pk=group_member_b.pk).exists()
112
+ assert activity.participants.filter(pk=group_member_c.pk).exists()
113
+ assert not activity.companies.filter(pk=group_member_d.pk).exists()
114
+
115
+ def test_add_participants_employer(self, activity_factory, person_factory):
116
+ person_a = PersonWithEmployerFactory()
117
+ person_b = person_factory()
118
+ employer_a = person_a.employer.get(primary=True).employer
119
+ activity_a = activity_factory(participants=(person_a, person_b))
120
+ assert person_a in activity_a.participants.all()
121
+ assert person_b in activity_a.participants.all()
122
+ assert employer_a in activity_a.companies.all()
123
+
124
+ def test_add_participants_employer_on_update(self, activity_factory, person_factory):
125
+ person_a = PersonWithEmployerFactory()
126
+ person_b = person_factory()
127
+ employer_a = person_a.employer.get(primary=True).employer
128
+ activity_a = activity_factory(participants=(person_b,))
129
+ assert person_a not in activity_a.participants.all()
130
+ assert person_b in activity_a.participants.all()
131
+ assert employer_a not in activity_a.companies.all()
132
+ activity_a.participants.set([person_a, person_b])
133
+ assert person_a in activity_a.participants.all()
134
+ assert person_b in activity_a.participants.all()
135
+ assert employer_a in activity_a.companies.all()
136
+
137
+ def test_add_participants_employer_to_canceled_activity(self, activity_factory, person_factory):
138
+ person_a = PersonWithEmployerFactory()
139
+ person_b = person_factory()
140
+ activity_a = activity_factory(participants=(person_a, person_b), status=Activity.Status.CANCELLED)
141
+ assert set(activity_a.participants.all()) == {person_a, person_b}
142
+
143
+ def test_participant_cannot_attend_digitally(
144
+ self, activity_participant_factory, activity_factory, conference_room_factory
145
+ ):
146
+ room = conference_room_factory(is_videoconference_capable=False)
147
+ activity = activity_factory(conference_room=room)
148
+ obj = activity_participant_factory(activity=activity)
149
+ with pytest.raises(TransitionNotAllowed):
150
+ obj.attendsdigitally()
151
+
152
+ def test_participant_can_attend_digitally(self, activity_participant_factory, activity_factory):
153
+ activity = activity_factory()
154
+ obj = activity_participant_factory(activity=activity)
155
+ obj.attendsdigitally()
156
+ assert True
157
+
158
+ def test_get_casted_calendar_item(self, activity_factory):
159
+ activity = activity_factory()
160
+ calendar_item = CalendarItem.objects.get(id=activity.id)
161
+ assert calendar_item.get_casted_calendar_item() == activity
162
+
163
+ def test_add_employer_to_activities(
164
+ self, activity_factory, employer_employee_relationship_factory, person_factory
165
+ ):
166
+ employee = person_factory()
167
+ act1 = activity_factory(
168
+ status=Activity.Status.PLANNED, start=timezone.now() + timedelta(1), participants=[employee]
169
+ )
170
+ act2 = activity_factory(
171
+ status=Activity.Status.REVIEWED, start=timezone.now() + timedelta(1), participants=[employee]
172
+ )
173
+ act3 = activity_factory(
174
+ status=Activity.Status.PLANNED, start=timezone.now() - timedelta(1), participants=[employee]
175
+ )
176
+ eer = employer_employee_relationship_factory(employee=employee)
177
+ add_employer_to_activities(eer.id)
178
+ act1.refresh_from_db()
179
+ act2.refresh_from_db()
180
+ act3.refresh_from_db()
181
+ assert eer.employer in act1.companies.all()
182
+ assert eer.employer not in act2.companies.all()
183
+ assert eer.employer not in act3.companies.all()
184
+ assert eer.employer.entry_ptr in act1.entities.all()
185
+ assert eer.employer.entry_ptr not in act2.entities.all()
186
+ assert eer.employer.entry_ptr not in act3.entities.all()
187
+
188
+ def test_set_color_icons(self, activity_factory, calendar_item_factory):
189
+ activity = activity_factory(type__icon=WBIcon.EURO.icon)
190
+ assert activity.color == activity.type.color
191
+ assert activity.icon == activity.type.icon
192
+
193
+ activity_type = activity.type
194
+ activity_type.icon = WBIcon.LOCATION.icon
195
+ activity_type.color = "#42f444"
196
+ activity_type.save()
197
+ activity.refresh_from_db()
198
+ assert activity.color == activity_type.color
199
+ assert activity.icon == activity_type.icon
200
+
201
+ calendar_item = calendar_item_factory()
202
+ assert calendar_item.color
203
+ assert calendar_item.icon
204
+
205
+ def test_main_company_removed_from_m2m(self, activity, company_factory):
206
+ external_company = company_factory.create()
207
+ activity.companies.add(external_company)
208
+ assert set(activity.companies.all()) == {
209
+ external_company,
210
+ }
211
+
212
+ main_company = company_factory.create()
213
+ global_preferences_registry.manager()["directory__main_company"] = main_company.id
214
+ activity.companies.add(main_company)
215
+ assert set(activity.companies.all()) == {
216
+ external_company,
217
+ }
@@ -0,0 +1,292 @@
1
+ import pytest
2
+ from django.db.models import Q
3
+ from django.forms.models import model_to_dict
4
+ from rest_framework.test import APIRequestFactory
5
+ from wbcore.contrib.authentication.factories import UserFactory
6
+ from wbcore.messages import InMemoryMessageStorage
7
+
8
+ from wbcrm.models.activities import Activity
9
+ from wbcrm.models.recurrence import Recurrence
10
+ from wbcrm.preferences import (
11
+ get_maximum_allowed_recurrent_date,
12
+ get_recurrence_maximum_count,
13
+ )
14
+ from wbcrm.serializers import ActivityModelSerializer
15
+ from wbcrm.viewsets import ActivityViewSet
16
+
17
+
18
+ @pytest.mark.django_db
19
+ class RecurrenceFixture:
20
+ @pytest.fixture
21
+ def occurrence_fixture(self, activity_factory):
22
+ return activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None)
23
+
24
+ @pytest.fixture
25
+ def occurrences_fixture(self, occurrence_fixture):
26
+ return Activity.objects.filter(Q(id=occurrence_fixture.id) | Q(parent_occurrence=occurrence_fixture)).order_by(
27
+ "start"
28
+ )
29
+
30
+
31
+ @pytest.mark.django_db
32
+ class TestRecurrenceModel(RecurrenceFixture):
33
+ @pytest.mark.parametrize("repeat_choice", [*list(Recurrence.ReoccuranceChoice)])
34
+ def test_is_recurrent(self, repeat_choice, recurring_activity_factory):
35
+ act = recurring_activity_factory(repeat_choice=repeat_choice)
36
+ assert act.is_recurrent == (act.repeat_choice != Recurrence.ReoccuranceChoice.NEVER)
37
+
38
+ def test_recurring_activity_without_end(self, recurring_activity_factory):
39
+ act = recurring_activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=None)
40
+ occurrences = Activity.objects.filter(parent_occurrence=act)
41
+ assert occurrences.count() <= get_recurrence_maximum_count()
42
+ assert (
43
+ occurrences.order_by("period__startswith").last().period.lower.date()
44
+ <= get_maximum_allowed_recurrent_date()
45
+ )
46
+
47
+ def test_is_root(self, occurrences_fixture):
48
+ for occurrence in occurrences_fixture:
49
+ assert occurrence.is_root == (occurrence.is_recurrent and not occurrence.parent_occurrence)
50
+
51
+ def test_is_leaf(self, occurrences_fixture):
52
+ for occurrence in occurrences_fixture:
53
+ assert occurrence.is_leaf == (occurrence.is_recurrent and not occurrence.next_occurrence)
54
+
55
+ def test_next_occurrence(self, occurrences_fixture):
56
+ last_row = len(occurrences_fixture) - 1
57
+ for row, occurrence in enumerate(occurrences_fixture):
58
+ if row == last_row:
59
+ assert occurrence.next_occurrence is None
60
+ else:
61
+ assert occurrence.next_occurrence == occurrences_fixture[row + 1]
62
+
63
+ def test_previous_occurrence(self, occurrences_fixture):
64
+ for row, occurrence in enumerate(occurrences_fixture):
65
+ if row == 0:
66
+ assert occurrence.previous_occurrence is None
67
+ else:
68
+ assert occurrence.previous_occurrence == occurrences_fixture[row - 1]
69
+
70
+ # def test_get_recurrent_valid_children(self, occurrences_fixture):
71
+ # parent = occurrences_fixture[0]
72
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:])
73
+ # assert parent.get_recurrent_invalid_children().count() == 0
74
+ # occurrences_fixture[1].cancel()
75
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 1
76
+ # assert parent.get_recurrent_invalid_children().count() == 1
77
+ # occurrences_fixture[2].review()
78
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 2
79
+ # assert parent.get_recurrent_invalid_children().count() == 2
80
+ # occurrences_fixture[3].finish()
81
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 3
82
+ # assert parent.get_recurrent_invalid_children().count() == 3
83
+
84
+ @pytest.mark.parametrize("position, only_next_occurrence", [(0, False), (0, True), (1, False), (1, True)])
85
+ def test_forward_deletion(self, occurrences_fixture, only_next_occurrence, position):
86
+ nb_activities = Activity.objects.count()
87
+ occ = occurrences_fixture[position]
88
+ next_occurrences = occurrences_fixture.filter(period__startswith__gt=occ.period.lower)
89
+ next_occ = next_occurrences.first()
90
+ nb_next_occurrences = next_occurrences.count()
91
+ assert nb_activities == 4
92
+ if only_next_occurrence:
93
+ occ.forward_deletion([next_occ.id])
94
+ assert Activity.objects.count() == nb_activities - 1 == 3
95
+ else:
96
+ occ.forward_deletion()
97
+ assert Activity.objects.count() == nb_activities - nb_next_occurrences
98
+
99
+ def test_claim_parent_hood(self, occurrences_fixture, activity_factory):
100
+ single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
101
+ single_activity.claim_parent_hood()
102
+ assert single_activity.is_root is False
103
+
104
+ for occurrence in occurrences_fixture:
105
+ occurrence.claim_parent_hood()
106
+ if occurrence.is_leaf:
107
+ assert occurrence.is_root is False
108
+ else:
109
+ assert occurrence.is_root is True
110
+
111
+ @pytest.mark.parametrize("position, propagation", [(0, False), (0, True), (1, False), (1, True)])
112
+ def test_delete(self, position, propagation, occurrences_fixture):
113
+ occ = occurrences_fixture[position]
114
+ next_occurrences = occurrences_fixture.filter(period__startswith__gt=occ.period.lower)
115
+ next_occ = next_occurrences.first()
116
+ nb_next_occurrences = next_occurrences.count()
117
+ assert Activity.objects.count() == len(occurrences_fixture) == 4
118
+ assert nb_next_occurrences == 3 - position
119
+ Activity.objects.filter(id=occ.id).update(propagate_for_all_children=propagation)
120
+ occ.refresh_from_db()
121
+ was_root = True if occ.is_root else False
122
+ occ.delete()
123
+ if propagation:
124
+ assert Activity.objects.count() == 3 - nb_next_occurrences
125
+ else:
126
+ assert Activity.objects.count() == 3
127
+ next_occ.refresh_from_db()
128
+ assert next_occ.is_root == was_root
129
+
130
+ @pytest.mark.parametrize("include_self", [False, True])
131
+ def test_get_occurrence_start_datetimes(self, occurrences_fixture, include_self, activity_factory):
132
+ single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
133
+ assert not single_activity._get_occurrence_start_datetimes(include_self)
134
+ assert single_activity.is_root is False
135
+
136
+ start_dates = occurrences_fixture[0]._get_occurrence_start_datetimes(include_self)
137
+ nb_occurrences = len(occurrences_fixture) if include_self else len(occurrences_fixture[1:])
138
+ assert len(start_dates) == nb_occurrences
139
+
140
+ def test_create_recurrence_child(self, occurrence_fixture):
141
+ nb_activities = Activity.objects.count()
142
+ start_dates = occurrence_fixture._get_occurrence_start_datetimes()
143
+ new_occurrence = occurrence_fixture._create_recurrence_child(start_dates[0])
144
+ assert Activity.objects.count() == nb_activities + 1
145
+ fields = [
146
+ "assigned_to",
147
+ "all_day",
148
+ "conference_room",
149
+ "creator",
150
+ "description",
151
+ "disable_participant_check",
152
+ "importance",
153
+ "visibility",
154
+ "location",
155
+ "location_longitude",
156
+ "location_latitude",
157
+ "recurrence_end",
158
+ "recurrence_count",
159
+ "reminder_choice",
160
+ "repeat_choice",
161
+ "title",
162
+ "type",
163
+ ]
164
+ assert new_occurrence.period.lower == start_dates[0]
165
+ assert occurrence_fixture.duration == new_occurrence.duration
166
+ assert new_occurrence.parent_occurrence == occurrence_fixture
167
+ assert model_to_dict(occurrence_fixture, fields=fields) == model_to_dict(new_occurrence, fields=fields)
168
+
169
+ def test_generate_occurrences(self, occurrence_fixture, activity_factory):
170
+ single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
171
+ assert len(single_activity.generate_occurrences()) == 0
172
+ children = occurrence_fixture.generate_occurrences().order_by("period__startswith")
173
+ assert (
174
+ len(children)
175
+ == len(occurrence_fixture._get_occurrence_start_datetimes())
176
+ == occurrence_fixture.recurrence_count
177
+ )
178
+ assert set(children) == set(occurrence_fixture.child_activities.all())
179
+ assert children[0].is_root is False
180
+ assert len(children[0].generate_occurrences(allow_reclaiming_root=False)) == 0
181
+ assert children[0].is_root is False
182
+ assert len(children[0].generate_occurrences()) == children[0].recurrence_count > 0
183
+ assert children[0].is_root is True
184
+ assert children[0].child_activities.all().count() == children[0].recurrence_count
185
+ assert occurrence_fixture.child_activities.all().count() == 0
186
+
187
+ @pytest.mark.parametrize("position_occurrence, propagation", [(0, False), (0, True), (1, False), (1, True)])
188
+ def test_forward_change(self, occurrences_fixture, propagation, position_occurrence, activity_factory):
189
+ activity = occurrences_fixture[position_occurrence]
190
+ nb_next_occurrences = len(occurrences_fixture[position_occurrence + 1 :])
191
+ if activity.is_root:
192
+ assert len(activity.get_recurrent_valid_children()) == nb_next_occurrences
193
+ else:
194
+ assert len(activity.get_recurrent_valid_children()) == 0
195
+ was_root = activity.is_root
196
+ other_activity = activity_factory(preceded_by=None)
197
+ Activity.objects.filter(id=activity.id).update(
198
+ title=other_activity.title, propagate_for_all_children=propagation
199
+ )
200
+ activity.refresh_from_db()
201
+ activity.forward_change()
202
+ activity.refresh_from_db()
203
+ assert activity.is_root == (propagation or was_root)
204
+ assert activity.propagate_for_all_children is False
205
+ if propagation or was_root:
206
+ assert len(activity.get_recurrent_valid_children()) == nb_next_occurrences
207
+ else:
208
+ assert len(activity.get_recurrent_valid_children()) == 0
209
+
210
+
211
+ @pytest.mark.django_db
212
+ class TestRecurrenceSerializers(RecurrenceFixture):
213
+ def test_next_occurrence_no_period(self, activity_factory):
214
+ activity = activity_factory(
215
+ period=None, repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None
216
+ )
217
+ request = APIRequestFactory().get("")
218
+ user = UserFactory()
219
+ request.user = user
220
+ request.parser_context = {}
221
+ serializer = ActivityModelSerializer(activity, context={"request": request})
222
+ assert serializer.next_occurrence(activity, request, user)
223
+
224
+ def test_previous_occurrence_no_period(self, activity_factory):
225
+ activity = activity_factory(
226
+ period=None, repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None
227
+ )
228
+ request = APIRequestFactory().get("")
229
+ user = UserFactory()
230
+ request.user = user
231
+ request.parser_context = {}
232
+ serializer = ActivityModelSerializer(activity, context={"request": request})
233
+ assert not serializer.previous_occurrence(activity, request, user)
234
+
235
+ def test_next_occurrence(self, occurrences_fixture):
236
+ request = APIRequestFactory().get("")
237
+ user = UserFactory()
238
+ request.user = user
239
+ request.parser_context = {}
240
+ last_row = len(occurrences_fixture) - 1
241
+ for row, occurrence in enumerate(occurrences_fixture):
242
+ serializer_occurrence = ActivityModelSerializer(occurrence, context={"request": request})
243
+ if row == last_row:
244
+ assert not serializer_occurrence.next_occurrence(occurrence, request, user)
245
+ else:
246
+ assert serializer_occurrence.next_occurrence(occurrence, request, user) == {
247
+ "next_occurrence": f"http://testserver/api/crm/activity/{occurrences_fixture[row+1].id}/"
248
+ }
249
+
250
+ def test_previous_occurrence(self, occurrences_fixture):
251
+ request = APIRequestFactory().get("")
252
+ user = UserFactory()
253
+ request.user = user
254
+ request.parser_context = {}
255
+ for row, occurrence in enumerate(occurrences_fixture):
256
+ serializer_occurrence = ActivityModelSerializer(occurrence, context={"request": request})
257
+ if row == 0:
258
+ assert not serializer_occurrence.previous_occurrence(occurrence, request, user)
259
+ else:
260
+ assert serializer_occurrence.previous_occurrence(occurrence, request, user) == {
261
+ "previous_occurrence": f"http://testserver/api/crm/activity/{occurrences_fixture[row-1].id}/"
262
+ }
263
+
264
+
265
+ @pytest.mark.django_db
266
+ class TestRecurrenceViewSets(RecurrenceFixture):
267
+ def test_next_and_previous_activity_button_present_for_recurring_activity(
268
+ self, occurrences_fixture, super_user_factory
269
+ ):
270
+ request = APIRequestFactory().get("")
271
+ request.user = super_user_factory()
272
+ request._messages = InMemoryMessageStorage(request)
273
+ view = ActivityViewSet.as_view({"get": "retrieve"})
274
+ for row, occurrence in enumerate(occurrences_fixture):
275
+ response = view(request, pk=occurrence.id).render()
276
+ if occurrence.is_root:
277
+ assert response.data["instance"]["_additional_resources"]["next_occurrence"].endswith(
278
+ str(occurrences_fixture[row + 1].id) + "/"
279
+ )
280
+ assert "previous_occurrence" not in response.data["instance"]["_additional_resources"]
281
+ elif occurrence.is_leaf:
282
+ assert "next_occurrence" not in response.data["instance"]["_additional_resources"]
283
+ assert response.data["instance"]["_additional_resources"]["previous_occurrence"].endswith(
284
+ str(occurrences_fixture[row - 1].id) + "/"
285
+ )
286
+ else:
287
+ assert response.data["instance"]["_additional_resources"]["next_occurrence"].endswith(
288
+ str(occurrences_fixture[row + 1].id) + "/"
289
+ )
290
+ assert response.data["instance"]["_additional_resources"]["previous_occurrence"].endswith(
291
+ str(occurrences_fixture[row - 1].id) + "/"
292
+ )
@@ -0,0 +1,21 @@
1
+ import pytest
2
+ from rest_framework.test import APIRequestFactory
3
+ from wbcore.contrib.authentication.factories import UserFactory
4
+
5
+ from wbcrm.report.activity_report import create_report, create_report_and_send
6
+
7
+
8
+ @pytest.mark.django_db
9
+ class TestSpecificReport:
10
+ def test_create_report(self, person_factory, activity_factory):
11
+ person = person_factory()
12
+ activity = activity_factory(participants=(person,))
13
+ report = create_report(person.id, activity.start, activity.end)
14
+ assert report
15
+ assert report.getvalue()
16
+
17
+ def test_create_report_and_send(self, person_factory):
18
+ request = APIRequestFactory().get("")
19
+ request.user = UserFactory(is_active=True, is_superuser=True)
20
+ person = person_factory()
21
+ create_report_and_send(request.user.profile.id, person.id)