wbcrm 2.2.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wbcrm might be problematic. Click here for more details.
- wbcrm/__init__.py +1 -0
- wbcrm/admin/__init__.py +4 -0
- wbcrm/admin/accounts.py +59 -0
- wbcrm/admin/activities.py +101 -0
- wbcrm/admin/groups.py +7 -0
- wbcrm/admin/products.py +8 -0
- wbcrm/apps.py +5 -0
- wbcrm/configurations/__init__.py +1 -0
- wbcrm/configurations/base.py +16 -0
- wbcrm/dynamic_preferences_registry.py +38 -0
- wbcrm/factories/__init__.py +14 -0
- wbcrm/factories/accounts.py +56 -0
- wbcrm/factories/activities.py +125 -0
- wbcrm/factories/groups.py +23 -0
- wbcrm/factories/products.py +10 -0
- wbcrm/filters/__init__.py +10 -0
- wbcrm/filters/accounts.py +67 -0
- wbcrm/filters/activities.py +181 -0
- wbcrm/filters/groups.py +20 -0
- wbcrm/filters/products.py +37 -0
- wbcrm/filters/signals.py +94 -0
- wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
- wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
- wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
- wbcrm/migrations/0004_alter_activity_status.py +28 -0
- wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
- wbcrm/migrations/0006_alter_activity_location.py +17 -0
- wbcrm/migrations/0007_alter_account_status.py +23 -0
- wbcrm/migrations/0008_alter_activity_options.py +16 -0
- wbcrm/migrations/0009_alter_account_is_public.py +19 -0
- wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
- wbcrm/migrations/0011_activity_summary.py +22 -0
- wbcrm/migrations/0012_alter_activity_summary.py +17 -0
- wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
- wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
- wbcrm/migrations/0015_alter_activity_type.py +23 -0
- wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
- wbcrm/migrations/__init__.py +0 -0
- wbcrm/models/__init__.py +4 -0
- wbcrm/models/accounts.py +637 -0
- wbcrm/models/activities.py +1335 -0
- wbcrm/models/groups.py +118 -0
- wbcrm/models/products.py +83 -0
- wbcrm/models/recurrence.py +279 -0
- wbcrm/preferences.py +14 -0
- wbcrm/serializers/__init__.py +23 -0
- wbcrm/serializers/accounts.py +126 -0
- wbcrm/serializers/activities.py +526 -0
- wbcrm/serializers/groups.py +30 -0
- wbcrm/serializers/products.py +57 -0
- wbcrm/serializers/recurrence.py +90 -0
- wbcrm/serializers/signals.py +70 -0
- wbcrm/synchronization/__init__.py +0 -0
- wbcrm/synchronization/activity/__init__.py +0 -0
- wbcrm/synchronization/activity/admin.py +72 -0
- wbcrm/synchronization/activity/backend.py +207 -0
- wbcrm/synchronization/activity/backends/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
- wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +399 -0
- wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
- wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
- wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
- wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
- wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
- wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
- wbcrm/synchronization/activity/backends/google/utils.py +216 -0
- wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/backend.py +576 -0
- wbcrm/synchronization/activity/backends/outlook/msgraph.py +438 -0
- wbcrm/synchronization/activity/backends/outlook/parser.py +423 -0
- wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +117 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +269 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +237 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +173 -0
- wbcrm/synchronization/activity/controller.py +545 -0
- wbcrm/synchronization/activity/dynamic_preferences_registry.py +107 -0
- wbcrm/synchronization/activity/preferences.py +21 -0
- wbcrm/synchronization/activity/shortcuts.py +9 -0
- wbcrm/synchronization/activity/signals.py +28 -0
- wbcrm/synchronization/activity/tasks.py +21 -0
- wbcrm/synchronization/activity/urls.py +6 -0
- wbcrm/synchronization/activity/utils.py +46 -0
- wbcrm/synchronization/activity/views.py +37 -0
- wbcrm/synchronization/admin.py +1 -0
- wbcrm/synchronization/apps.py +15 -0
- wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
- wbcrm/synchronization/management.py +36 -0
- wbcrm/synchronization/tasks.py +1 -0
- wbcrm/synchronization/urls.py +5 -0
- wbcrm/tasks.py +312 -0
- wbcrm/tests/__init__.py +0 -0
- wbcrm/tests/accounts/__init__.py +0 -0
- wbcrm/tests/accounts/test_models.py +380 -0
- wbcrm/tests/accounts/test_viewsets.py +87 -0
- wbcrm/tests/conftest.py +76 -0
- wbcrm/tests/disable_signals.py +52 -0
- wbcrm/tests/e2e/__init__.py +1 -0
- wbcrm/tests/e2e/e2e_wbcrm_utility.py +82 -0
- wbcrm/tests/e2e/test_e2e.py +369 -0
- wbcrm/tests/test_assignee_methods.py +39 -0
- wbcrm/tests/test_chartviewsets.py +111 -0
- wbcrm/tests/test_dto.py +63 -0
- wbcrm/tests/test_filters.py +51 -0
- wbcrm/tests/test_models.py +216 -0
- wbcrm/tests/test_recurrence.py +291 -0
- wbcrm/tests/test_report.py +20 -0
- wbcrm/tests/test_serializers.py +170 -0
- wbcrm/tests/test_tasks.py +94 -0
- wbcrm/tests/test_viewsets.py +967 -0
- wbcrm/tests/tests.py +120 -0
- wbcrm/typings.py +107 -0
- wbcrm/urls.py +67 -0
- wbcrm/viewsets/__init__.py +22 -0
- wbcrm/viewsets/accounts.py +121 -0
- wbcrm/viewsets/activities.py +315 -0
- wbcrm/viewsets/buttons/__init__.py +7 -0
- wbcrm/viewsets/buttons/accounts.py +27 -0
- wbcrm/viewsets/buttons/activities.py +68 -0
- wbcrm/viewsets/buttons/signals.py +17 -0
- wbcrm/viewsets/display/__init__.py +12 -0
- wbcrm/viewsets/display/accounts.py +110 -0
- wbcrm/viewsets/display/activities.py +443 -0
- wbcrm/viewsets/display/groups.py +22 -0
- wbcrm/viewsets/display/products.py +105 -0
- wbcrm/viewsets/endpoints/__init__.py +8 -0
- wbcrm/viewsets/endpoints/accounts.py +32 -0
- wbcrm/viewsets/endpoints/activities.py +30 -0
- wbcrm/viewsets/endpoints/groups.py +7 -0
- wbcrm/viewsets/endpoints/products.py +9 -0
- wbcrm/viewsets/groups.py +37 -0
- wbcrm/viewsets/menu/__init__.py +8 -0
- wbcrm/viewsets/menu/accounts.py +18 -0
- wbcrm/viewsets/menu/activities.py +61 -0
- wbcrm/viewsets/menu/groups.py +16 -0
- wbcrm/viewsets/menu/products.py +20 -0
- wbcrm/viewsets/mixins.py +34 -0
- wbcrm/viewsets/previews/__init__.py +1 -0
- wbcrm/viewsets/previews/activities.py +10 -0
- wbcrm/viewsets/products.py +56 -0
- wbcrm/viewsets/recurrence.py +26 -0
- wbcrm/viewsets/titles/__init__.py +13 -0
- wbcrm/viewsets/titles/accounts.py +22 -0
- wbcrm/viewsets/titles/activities.py +61 -0
- wbcrm/viewsets/titles/products.py +13 -0
- wbcrm/viewsets/titles/utils.py +46 -0
- wbcrm/workflows/__init__.py +1 -0
- wbcrm/workflows/assignee_methods.py +25 -0
- wbcrm-2.2.1.dist-info/METADATA +11 -0
- wbcrm-2.2.1.dist-info/RECORD +155 -0
- wbcrm-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
from wbcrm.models import Activity, add_employer_to_activities
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.django_db
|
|
20
|
+
class TestSpecificModelsActivities:
|
|
21
|
+
def test_get_color_map(self, activity_factory):
|
|
22
|
+
activity1 = activity_factory()
|
|
23
|
+
assert activity1.Status.get_color_map()
|
|
24
|
+
|
|
25
|
+
def test_get_minutes_correspondance(self, activity_factory):
|
|
26
|
+
activity1 = activity_factory()
|
|
27
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("NEVER")
|
|
28
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("EVENT_TIME") == 0
|
|
29
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_5")
|
|
30
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_15")
|
|
31
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_30")
|
|
32
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_1")
|
|
33
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_2")
|
|
34
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_12")
|
|
35
|
+
assert activity1.ReminderChoice.get_minutes_correspondance("WEEKS_1")
|
|
36
|
+
|
|
37
|
+
def test_str(self, activity_factory):
|
|
38
|
+
assert activity_factory(preceded_by=None).__str__()
|
|
39
|
+
|
|
40
|
+
def test_get_participants(self, activity_factory, person_factory, internal_user_factory, company_factory):
|
|
41
|
+
person1 = person_factory()
|
|
42
|
+
company1 = company_factory()
|
|
43
|
+
if apps.is_installed("wbhuman_resources"):
|
|
44
|
+
from wbhuman_resources.factories import EmployeeHumanResourceFactory
|
|
45
|
+
|
|
46
|
+
person2 = AuthenticatedPersonFactory(employers=(company1,))
|
|
47
|
+
EmployeeHumanResourceFactory(profile=person2)
|
|
48
|
+
else:
|
|
49
|
+
person2 = person_factory(employers=(company1,))
|
|
50
|
+
u = internal_user_factory.create()
|
|
51
|
+
u.profile = person2
|
|
52
|
+
u.save()
|
|
53
|
+
activity1 = activity_factory(participants=(person1,))
|
|
54
|
+
assert activity1.get_participants().count() == 1
|
|
55
|
+
assert activity1.get_participants()[0] == person1
|
|
56
|
+
|
|
57
|
+
def test_get_activities_for_user(self, activity_factory):
|
|
58
|
+
activity0 = activity_factory(preceded_by=None)
|
|
59
|
+
user = UserFactory(is_active=True, is_superuser=True)
|
|
60
|
+
qs = activity0.get_activities_for_user(user)
|
|
61
|
+
assert qs.count() == 1
|
|
62
|
+
|
|
63
|
+
user2 = UserFactory(is_active=True)
|
|
64
|
+
qs2 = activity0.get_activities_for_user(user2)
|
|
65
|
+
assert qs2.count() == 0
|
|
66
|
+
|
|
67
|
+
def test_add_group_to_activity(self, activity_factory, group_factory, person_factory, company_factory):
|
|
68
|
+
activity_participant_a = person_factory()
|
|
69
|
+
group_member_a = person_factory()
|
|
70
|
+
member_a_entry = Entry.objects.get(pk=group_member_a.pk)
|
|
71
|
+
group_member_b = company_factory()
|
|
72
|
+
member_b_entry = Entry.objects.get(pk=group_member_b.pk)
|
|
73
|
+
group_a = group_factory(members=(member_a_entry, member_b_entry))
|
|
74
|
+
|
|
75
|
+
activity = activity_factory(
|
|
76
|
+
participants=[
|
|
77
|
+
activity_participant_a,
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
assert activity.participants.filter(pk=activity_participant_a.pk).exists()
|
|
81
|
+
assert not activity.participants.filter(pk=group_member_a.pk).exists()
|
|
82
|
+
assert not activity.companies.filter(pk=group_member_b.pk).exists()
|
|
83
|
+
|
|
84
|
+
activity.groups.set((group_a,))
|
|
85
|
+
activity.save()
|
|
86
|
+
|
|
87
|
+
assert activity.participants.filter(pk=activity_participant_a.pk).exists()
|
|
88
|
+
assert activity.participants.filter(pk=group_member_a.pk).exists()
|
|
89
|
+
assert activity.companies.filter(pk=group_member_b.pk).exists()
|
|
90
|
+
|
|
91
|
+
def test_remove_group_to_activity(self, activity_factory, group_factory, person_factory, company_factory):
|
|
92
|
+
group_member_a = person_factory()
|
|
93
|
+
group_member_b = person_factory()
|
|
94
|
+
group_member_c = person_factory()
|
|
95
|
+
group_member_d = company_factory()
|
|
96
|
+
group_a = group_factory(members=(group_member_a.entry_ptr, group_member_c.entry_ptr, group_member_d.entry_ptr))
|
|
97
|
+
group_b = group_factory(members=(group_member_b.entry_ptr, group_member_c.entry_ptr))
|
|
98
|
+
|
|
99
|
+
activity = activity_factory(groups=[group_a, group_b])
|
|
100
|
+
|
|
101
|
+
assert activity.participants.filter(pk=group_member_a.pk).exists()
|
|
102
|
+
assert activity.participants.filter(pk=group_member_b.pk).exists()
|
|
103
|
+
assert activity.participants.filter(pk=group_member_c.pk).exists()
|
|
104
|
+
assert activity.companies.filter(pk=group_member_d.pk).exists()
|
|
105
|
+
|
|
106
|
+
activity.groups.remove(group_a.pk)
|
|
107
|
+
activity.save()
|
|
108
|
+
|
|
109
|
+
assert not activity.participants.filter(pk=group_member_a.pk).exists()
|
|
110
|
+
assert activity.participants.filter(pk=group_member_b.pk).exists()
|
|
111
|
+
assert activity.participants.filter(pk=group_member_c.pk).exists()
|
|
112
|
+
assert not activity.companies.filter(pk=group_member_d.pk).exists()
|
|
113
|
+
|
|
114
|
+
def test_add_participants_employer(self, activity_factory, person_factory):
|
|
115
|
+
person_a = PersonWithEmployerFactory()
|
|
116
|
+
person_b = person_factory()
|
|
117
|
+
employer_a = person_a.employer.get(primary=True).employer
|
|
118
|
+
activity_a = activity_factory(participants=(person_a, person_b))
|
|
119
|
+
assert person_a in activity_a.participants.all()
|
|
120
|
+
assert person_b in activity_a.participants.all()
|
|
121
|
+
assert employer_a in activity_a.companies.all()
|
|
122
|
+
|
|
123
|
+
def test_add_participants_employer_on_update(self, activity_factory, person_factory):
|
|
124
|
+
person_a = PersonWithEmployerFactory()
|
|
125
|
+
person_b = person_factory()
|
|
126
|
+
employer_a = person_a.employer.get(primary=True).employer
|
|
127
|
+
activity_a = activity_factory(participants=(person_b,))
|
|
128
|
+
assert person_a not in activity_a.participants.all()
|
|
129
|
+
assert person_b in activity_a.participants.all()
|
|
130
|
+
assert employer_a not in activity_a.companies.all()
|
|
131
|
+
activity_a.participants.set([person_a, person_b])
|
|
132
|
+
assert person_a in activity_a.participants.all()
|
|
133
|
+
assert person_b in activity_a.participants.all()
|
|
134
|
+
assert employer_a in activity_a.companies.all()
|
|
135
|
+
|
|
136
|
+
def test_add_participants_employer_to_canceled_activity(self, activity_factory, person_factory):
|
|
137
|
+
person_a = PersonWithEmployerFactory()
|
|
138
|
+
person_b = person_factory()
|
|
139
|
+
activity_a = activity_factory(participants=(person_a, person_b), status=Activity.Status.CANCELLED)
|
|
140
|
+
assert set(activity_a.participants.all()) == {person_a, person_b}
|
|
141
|
+
|
|
142
|
+
def test_participant_cannot_attend_digitally(
|
|
143
|
+
self, activity_participant_factory, activity_factory, conference_room_factory
|
|
144
|
+
):
|
|
145
|
+
room = conference_room_factory(is_videoconference_capable=False)
|
|
146
|
+
activity = activity_factory(conference_room=room)
|
|
147
|
+
obj = activity_participant_factory(activity=activity)
|
|
148
|
+
with pytest.raises(TransitionNotAllowed):
|
|
149
|
+
obj.attendsdigitally()
|
|
150
|
+
|
|
151
|
+
def test_participant_can_attend_digitally(self, activity_participant_factory, activity_factory):
|
|
152
|
+
activity = activity_factory()
|
|
153
|
+
obj = activity_participant_factory(activity=activity)
|
|
154
|
+
obj.attendsdigitally()
|
|
155
|
+
assert True
|
|
156
|
+
|
|
157
|
+
def test_get_casted_calendar_item(self, activity_factory):
|
|
158
|
+
activity = activity_factory()
|
|
159
|
+
calendar_item = CalendarItem.objects.get(id=activity.id)
|
|
160
|
+
assert calendar_item.get_casted_calendar_item() == activity
|
|
161
|
+
|
|
162
|
+
def test_add_employer_to_activities(
|
|
163
|
+
self, activity_factory, employer_employee_relationship_factory, person_factory
|
|
164
|
+
):
|
|
165
|
+
employee = person_factory()
|
|
166
|
+
act1 = activity_factory(
|
|
167
|
+
status=Activity.Status.PLANNED, start=timezone.now() + timedelta(1), participants=[employee]
|
|
168
|
+
)
|
|
169
|
+
act2 = activity_factory(
|
|
170
|
+
status=Activity.Status.REVIEWED, start=timezone.now() + timedelta(1), participants=[employee]
|
|
171
|
+
)
|
|
172
|
+
act3 = activity_factory(
|
|
173
|
+
status=Activity.Status.PLANNED, start=timezone.now() - timedelta(1), participants=[employee]
|
|
174
|
+
)
|
|
175
|
+
eer = employer_employee_relationship_factory(employee=employee)
|
|
176
|
+
add_employer_to_activities(eer.id)
|
|
177
|
+
act1.refresh_from_db()
|
|
178
|
+
act2.refresh_from_db()
|
|
179
|
+
act3.refresh_from_db()
|
|
180
|
+
assert eer.employer in act1.companies.all()
|
|
181
|
+
assert eer.employer not in act2.companies.all()
|
|
182
|
+
assert eer.employer not in act3.companies.all()
|
|
183
|
+
assert eer.employer.entry_ptr in act1.entities.all()
|
|
184
|
+
assert eer.employer.entry_ptr not in act2.entities.all()
|
|
185
|
+
assert eer.employer.entry_ptr not in act3.entities.all()
|
|
186
|
+
|
|
187
|
+
def test_set_color_icons(self, activity_factory, calendar_item_factory):
|
|
188
|
+
activity = activity_factory(type__icon=WBIcon.EURO.icon)
|
|
189
|
+
assert activity.color == activity.type.color
|
|
190
|
+
assert activity.icon == activity.type.icon
|
|
191
|
+
|
|
192
|
+
activity_type = activity.type
|
|
193
|
+
activity_type.icon = WBIcon.LOCATION.icon
|
|
194
|
+
activity_type.color = "#42f444"
|
|
195
|
+
activity_type.save()
|
|
196
|
+
activity.refresh_from_db()
|
|
197
|
+
assert activity.color == activity_type.color
|
|
198
|
+
assert activity.icon == activity_type.icon
|
|
199
|
+
|
|
200
|
+
calendar_item = calendar_item_factory()
|
|
201
|
+
assert calendar_item.color
|
|
202
|
+
assert calendar_item.icon
|
|
203
|
+
|
|
204
|
+
def test_main_company_removed_from_m2m(self, activity, company_factory):
|
|
205
|
+
external_company = company_factory.create()
|
|
206
|
+
activity.companies.add(external_company)
|
|
207
|
+
assert set(activity.companies.all()) == {
|
|
208
|
+
external_company,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
main_company = company_factory.create()
|
|
212
|
+
global_preferences_registry.manager()["directory__main_company"] = main_company.id
|
|
213
|
+
activity.companies.add(main_company)
|
|
214
|
+
assert set(activity.companies.all()) == {
|
|
215
|
+
external_company,
|
|
216
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
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
|
+
from wbcrm.models.activities import Activity
|
|
8
|
+
from wbcrm.models.recurrence import Recurrence
|
|
9
|
+
from wbcrm.preferences import (
|
|
10
|
+
get_maximum_allowed_recurrent_date,
|
|
11
|
+
get_recurrence_maximum_count,
|
|
12
|
+
)
|
|
13
|
+
from wbcrm.serializers import ActivityModelSerializer
|
|
14
|
+
from wbcrm.viewsets import ActivityViewSet
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.django_db
|
|
18
|
+
class RecurrenceFixture:
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def occurrence_fixture(self, activity_factory):
|
|
21
|
+
return activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None)
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def occurrences_fixture(self, occurrence_fixture):
|
|
25
|
+
return Activity.objects.filter(Q(id=occurrence_fixture.id) | Q(parent_occurrence=occurrence_fixture)).order_by(
|
|
26
|
+
"start"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.django_db
|
|
31
|
+
class TestRecurrenceModel(RecurrenceFixture):
|
|
32
|
+
@pytest.mark.parametrize("repeat_choice", [*list(Recurrence.ReoccuranceChoice)])
|
|
33
|
+
def test_is_recurrent(self, repeat_choice, recurring_activity_factory):
|
|
34
|
+
act = recurring_activity_factory(repeat_choice=repeat_choice)
|
|
35
|
+
assert act.is_recurrent == (act.repeat_choice != Recurrence.ReoccuranceChoice.NEVER)
|
|
36
|
+
|
|
37
|
+
def test_recurring_activity_without_end(self, recurring_activity_factory):
|
|
38
|
+
act = recurring_activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=None)
|
|
39
|
+
occurrences = Activity.objects.filter(parent_occurrence=act)
|
|
40
|
+
assert occurrences.count() <= get_recurrence_maximum_count()
|
|
41
|
+
assert (
|
|
42
|
+
occurrences.order_by("period__startswith").last().period.lower.date()
|
|
43
|
+
<= get_maximum_allowed_recurrent_date()
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def test_is_root(self, occurrences_fixture):
|
|
47
|
+
for occurrence in occurrences_fixture:
|
|
48
|
+
assert occurrence.is_root == (occurrence.is_recurrent and not occurrence.parent_occurrence)
|
|
49
|
+
|
|
50
|
+
def test_is_leaf(self, occurrences_fixture):
|
|
51
|
+
for occurrence in occurrences_fixture:
|
|
52
|
+
assert occurrence.is_leaf == (occurrence.is_recurrent and not occurrence.next_occurrence)
|
|
53
|
+
|
|
54
|
+
def test_next_occurrence(self, occurrences_fixture):
|
|
55
|
+
last_row = len(occurrences_fixture) - 1
|
|
56
|
+
for row, occurrence in enumerate(occurrences_fixture):
|
|
57
|
+
if row == last_row:
|
|
58
|
+
assert occurrence.next_occurrence is None
|
|
59
|
+
else:
|
|
60
|
+
assert occurrence.next_occurrence == occurrences_fixture[row + 1]
|
|
61
|
+
|
|
62
|
+
def test_previous_occurrence(self, occurrences_fixture):
|
|
63
|
+
for row, occurrence in enumerate(occurrences_fixture):
|
|
64
|
+
if row == 0:
|
|
65
|
+
assert occurrence.previous_occurrence is None
|
|
66
|
+
else:
|
|
67
|
+
assert occurrence.previous_occurrence == occurrences_fixture[row - 1]
|
|
68
|
+
|
|
69
|
+
# def test_get_recurrent_valid_children(self, occurrences_fixture):
|
|
70
|
+
# parent = occurrences_fixture[0]
|
|
71
|
+
# assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:])
|
|
72
|
+
# assert parent.get_recurrent_invalid_children().count() == 0
|
|
73
|
+
# occurrences_fixture[1].cancel()
|
|
74
|
+
# assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 1
|
|
75
|
+
# assert parent.get_recurrent_invalid_children().count() == 1
|
|
76
|
+
# occurrences_fixture[2].review()
|
|
77
|
+
# assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 2
|
|
78
|
+
# assert parent.get_recurrent_invalid_children().count() == 2
|
|
79
|
+
# occurrences_fixture[3].finish()
|
|
80
|
+
# assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 3
|
|
81
|
+
# assert parent.get_recurrent_invalid_children().count() == 3
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize("position, only_next_occurrence", [(0, False), (0, True), (1, False), (1, True)])
|
|
84
|
+
def test_forward_deletion(self, occurrences_fixture, only_next_occurrence, position):
|
|
85
|
+
nb_activities = Activity.objects.count()
|
|
86
|
+
occ = occurrences_fixture[position]
|
|
87
|
+
next_occurrences = occurrences_fixture.filter(period__startswith__gt=occ.period.lower)
|
|
88
|
+
next_occ = next_occurrences.first()
|
|
89
|
+
nb_next_occurrences = next_occurrences.count()
|
|
90
|
+
assert nb_activities == 4
|
|
91
|
+
if only_next_occurrence:
|
|
92
|
+
occ.forward_deletion([next_occ.id])
|
|
93
|
+
assert Activity.objects.count() == nb_activities - 1 == 3
|
|
94
|
+
else:
|
|
95
|
+
occ.forward_deletion()
|
|
96
|
+
assert Activity.objects.count() == nb_activities - nb_next_occurrences
|
|
97
|
+
|
|
98
|
+
def test_claim_parent_hood(self, occurrences_fixture, activity_factory):
|
|
99
|
+
single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
|
|
100
|
+
single_activity.claim_parent_hood()
|
|
101
|
+
assert single_activity.is_root is False
|
|
102
|
+
|
|
103
|
+
for occurrence in occurrences_fixture:
|
|
104
|
+
occurrence.claim_parent_hood()
|
|
105
|
+
if occurrence.is_leaf:
|
|
106
|
+
assert occurrence.is_root is False
|
|
107
|
+
else:
|
|
108
|
+
assert occurrence.is_root is True
|
|
109
|
+
|
|
110
|
+
@pytest.mark.parametrize("position, propagation", [(0, False), (0, True), (1, False), (1, True)])
|
|
111
|
+
def test_delete(self, position, propagation, occurrences_fixture):
|
|
112
|
+
occ = occurrences_fixture[position]
|
|
113
|
+
next_occurrences = occurrences_fixture.filter(period__startswith__gt=occ.period.lower)
|
|
114
|
+
next_occ = next_occurrences.first()
|
|
115
|
+
nb_next_occurrences = next_occurrences.count()
|
|
116
|
+
assert Activity.objects.count() == len(occurrences_fixture) == 4
|
|
117
|
+
assert nb_next_occurrences == 3 - position
|
|
118
|
+
Activity.objects.filter(id=occ.id).update(propagate_for_all_children=propagation)
|
|
119
|
+
occ.refresh_from_db()
|
|
120
|
+
was_root = True if occ.is_root else False
|
|
121
|
+
occ.delete()
|
|
122
|
+
if propagation:
|
|
123
|
+
assert Activity.objects.count() == 3 - nb_next_occurrences
|
|
124
|
+
else:
|
|
125
|
+
assert Activity.objects.count() == 3
|
|
126
|
+
next_occ.refresh_from_db()
|
|
127
|
+
next_occ.is_root == was_root
|
|
128
|
+
|
|
129
|
+
@pytest.mark.parametrize("include_self", [False, True])
|
|
130
|
+
def test_get_occurrence_start_datetimes(self, occurrences_fixture, include_self, activity_factory):
|
|
131
|
+
single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
|
|
132
|
+
assert not single_activity._get_occurrence_start_datetimes(include_self)
|
|
133
|
+
assert single_activity.is_root is False
|
|
134
|
+
|
|
135
|
+
start_dates = occurrences_fixture[0]._get_occurrence_start_datetimes(include_self)
|
|
136
|
+
nb_occurrences = len(occurrences_fixture) if include_self else len(occurrences_fixture[1:])
|
|
137
|
+
assert len(start_dates) == nb_occurrences
|
|
138
|
+
|
|
139
|
+
def test_create_recurrence_child(self, occurrence_fixture):
|
|
140
|
+
nb_activities = Activity.objects.count()
|
|
141
|
+
start_dates = occurrence_fixture._get_occurrence_start_datetimes()
|
|
142
|
+
new_occurrence = occurrence_fixture._create_recurrence_child(start_dates[0])
|
|
143
|
+
assert Activity.objects.count() == nb_activities + 1
|
|
144
|
+
fields = [
|
|
145
|
+
"assigned_to",
|
|
146
|
+
"all_day",
|
|
147
|
+
"conference_room",
|
|
148
|
+
"creator",
|
|
149
|
+
"description",
|
|
150
|
+
"disable_participant_check",
|
|
151
|
+
"importance",
|
|
152
|
+
"visibility",
|
|
153
|
+
"location",
|
|
154
|
+
"location_longitude",
|
|
155
|
+
"location_latitude",
|
|
156
|
+
"recurrence_end",
|
|
157
|
+
"recurrence_count",
|
|
158
|
+
"reminder_choice",
|
|
159
|
+
"repeat_choice",
|
|
160
|
+
"title",
|
|
161
|
+
"type",
|
|
162
|
+
]
|
|
163
|
+
assert new_occurrence.period.lower == start_dates[0]
|
|
164
|
+
assert occurrence_fixture.duration == new_occurrence.duration
|
|
165
|
+
assert new_occurrence.parent_occurrence == occurrence_fixture
|
|
166
|
+
assert model_to_dict(occurrence_fixture, fields=fields) == model_to_dict(new_occurrence, fields=fields)
|
|
167
|
+
|
|
168
|
+
def test_generate_occurrences(self, occurrence_fixture, activity_factory):
|
|
169
|
+
single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
|
|
170
|
+
assert len(single_activity.generate_occurrences()) == 0
|
|
171
|
+
children = occurrence_fixture.generate_occurrences().order_by("period__startswith")
|
|
172
|
+
assert (
|
|
173
|
+
len(children)
|
|
174
|
+
== len(occurrence_fixture._get_occurrence_start_datetimes())
|
|
175
|
+
== occurrence_fixture.recurrence_count
|
|
176
|
+
)
|
|
177
|
+
assert set(children) == set(occurrence_fixture.child_activities.all())
|
|
178
|
+
assert children[0].is_root is False
|
|
179
|
+
assert len(children[0].generate_occurrences(allow_reclaiming_root=False)) == 0
|
|
180
|
+
assert children[0].is_root is False
|
|
181
|
+
assert len(children[0].generate_occurrences()) == children[0].recurrence_count > 0
|
|
182
|
+
assert children[0].is_root is True
|
|
183
|
+
assert children[0].child_activities.all().count() == children[0].recurrence_count
|
|
184
|
+
assert occurrence_fixture.child_activities.all().count() == 0
|
|
185
|
+
|
|
186
|
+
@pytest.mark.parametrize("position_occurrence, propagation", [(0, False), (0, True), (1, False), (1, True)])
|
|
187
|
+
def test_forward_change(self, occurrences_fixture, propagation, position_occurrence, activity_factory):
|
|
188
|
+
activity = occurrences_fixture[position_occurrence]
|
|
189
|
+
nb_next_occurrences = len(occurrences_fixture[position_occurrence + 1 :])
|
|
190
|
+
if activity.is_root:
|
|
191
|
+
assert len(activity.get_recurrent_valid_children()) == nb_next_occurrences
|
|
192
|
+
else:
|
|
193
|
+
assert len(activity.get_recurrent_valid_children()) == 0
|
|
194
|
+
was_root = activity.is_root
|
|
195
|
+
other_activity = activity_factory(preceded_by=None)
|
|
196
|
+
Activity.objects.filter(id=activity.id).update(
|
|
197
|
+
title=other_activity.title, propagate_for_all_children=propagation
|
|
198
|
+
)
|
|
199
|
+
activity.refresh_from_db()
|
|
200
|
+
activity.forward_change()
|
|
201
|
+
activity.refresh_from_db()
|
|
202
|
+
assert activity.is_root == (propagation or was_root)
|
|
203
|
+
assert activity.propagate_for_all_children is False
|
|
204
|
+
if propagation or was_root:
|
|
205
|
+
assert len(activity.get_recurrent_valid_children()) == nb_next_occurrences
|
|
206
|
+
else:
|
|
207
|
+
assert len(activity.get_recurrent_valid_children()) == 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@pytest.mark.django_db
|
|
211
|
+
class TestRecurrenceSerializers(RecurrenceFixture):
|
|
212
|
+
def test_next_occurrence_no_period(self, activity_factory):
|
|
213
|
+
activity = activity_factory(
|
|
214
|
+
period=None, repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None
|
|
215
|
+
)
|
|
216
|
+
request = APIRequestFactory().get("")
|
|
217
|
+
user = UserFactory()
|
|
218
|
+
request.user = user
|
|
219
|
+
request.parser_context = {}
|
|
220
|
+
serializer = ActivityModelSerializer(activity, context={"request": request})
|
|
221
|
+
assert serializer.next_occurrence(activity, request, user)
|
|
222
|
+
|
|
223
|
+
def test_previous_occurrence_no_period(self, activity_factory):
|
|
224
|
+
activity = activity_factory(
|
|
225
|
+
period=None, repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None
|
|
226
|
+
)
|
|
227
|
+
request = APIRequestFactory().get("")
|
|
228
|
+
user = UserFactory()
|
|
229
|
+
request.user = user
|
|
230
|
+
request.parser_context = {}
|
|
231
|
+
serializer = ActivityModelSerializer(activity, context={"request": request})
|
|
232
|
+
assert not serializer.previous_occurrence(activity, request, user)
|
|
233
|
+
|
|
234
|
+
def test_next_occurrence(self, occurrences_fixture):
|
|
235
|
+
request = APIRequestFactory().get("")
|
|
236
|
+
user = UserFactory()
|
|
237
|
+
request.user = user
|
|
238
|
+
request.parser_context = {}
|
|
239
|
+
last_row = len(occurrences_fixture) - 1
|
|
240
|
+
for row, occurrence in enumerate(occurrences_fixture):
|
|
241
|
+
serializer_occurrence = ActivityModelSerializer(occurrence, context={"request": request})
|
|
242
|
+
if row == last_row:
|
|
243
|
+
assert not serializer_occurrence.next_occurrence(occurrence, request, user)
|
|
244
|
+
else:
|
|
245
|
+
assert serializer_occurrence.next_occurrence(occurrence, request, user) == {
|
|
246
|
+
"next_occurrence": f"http://testserver/api/crm/activity/{occurrences_fixture[row+1].id}/"
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
def test_previous_occurrence(self, occurrences_fixture):
|
|
250
|
+
request = APIRequestFactory().get("")
|
|
251
|
+
user = UserFactory()
|
|
252
|
+
request.user = user
|
|
253
|
+
request.parser_context = {}
|
|
254
|
+
for row, occurrence in enumerate(occurrences_fixture):
|
|
255
|
+
serializer_occurrence = ActivityModelSerializer(occurrence, context={"request": request})
|
|
256
|
+
if row == 0:
|
|
257
|
+
assert not serializer_occurrence.previous_occurrence(occurrence, request, user)
|
|
258
|
+
else:
|
|
259
|
+
assert serializer_occurrence.previous_occurrence(occurrence, request, user) == {
|
|
260
|
+
"previous_occurrence": f"http://testserver/api/crm/activity/{occurrences_fixture[row-1].id}/"
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@pytest.mark.django_db
|
|
265
|
+
class TestRecurrenceViewSets(RecurrenceFixture):
|
|
266
|
+
def test_next_and_previous_activity_button_present_for_recurring_activity(
|
|
267
|
+
self, occurrences_fixture, super_user_factory
|
|
268
|
+
):
|
|
269
|
+
request = APIRequestFactory().get("")
|
|
270
|
+
request.user = super_user_factory()
|
|
271
|
+
request._messages = InMemoryMessageStorage(request)
|
|
272
|
+
view = ActivityViewSet.as_view({"get": "retrieve"})
|
|
273
|
+
for row, occurrence in enumerate(occurrences_fixture):
|
|
274
|
+
response = view(request, pk=occurrence.id).render()
|
|
275
|
+
if occurrence.is_root:
|
|
276
|
+
assert response.data["instance"]["_additional_resources"]["next_occurrence"].endswith(
|
|
277
|
+
str(occurrences_fixture[row + 1].id) + "/"
|
|
278
|
+
)
|
|
279
|
+
assert "previous_occurrence" not in response.data["instance"]["_additional_resources"]
|
|
280
|
+
elif occurrence.is_leaf:
|
|
281
|
+
assert "next_occurrence" not in response.data["instance"]["_additional_resources"]
|
|
282
|
+
assert response.data["instance"]["_additional_resources"]["previous_occurrence"].endswith(
|
|
283
|
+
str(occurrences_fixture[row - 1].id) + "/"
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
assert response.data["instance"]["_additional_resources"]["next_occurrence"].endswith(
|
|
287
|
+
str(occurrences_fixture[row + 1].id) + "/"
|
|
288
|
+
)
|
|
289
|
+
assert response.data["instance"]["_additional_resources"]["previous_occurrence"].endswith(
|
|
290
|
+
str(occurrences_fixture[row - 1].id) + "/"
|
|
291
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from rest_framework.test import APIRequestFactory
|
|
3
|
+
from wbcore.contrib.authentication.factories import UserFactory
|
|
4
|
+
from wbcrm.report.activity_report import create_report, create_report_and_send
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.django_db
|
|
8
|
+
class TestSpecificReport:
|
|
9
|
+
def test_create_report(self, person_factory, activity_factory):
|
|
10
|
+
person = person_factory()
|
|
11
|
+
activity = activity_factory(participants=(person,))
|
|
12
|
+
report = create_report(person.id, activity.start, activity.end)
|
|
13
|
+
assert report
|
|
14
|
+
assert report.getvalue()
|
|
15
|
+
|
|
16
|
+
def test_create_report_and_send(self, person_factory):
|
|
17
|
+
request = APIRequestFactory().get("")
|
|
18
|
+
request.user = UserFactory(is_active=True, is_superuser=True)
|
|
19
|
+
person = person_factory()
|
|
20
|
+
create_report_and_send(request.user.profile.id, person.id)
|