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,83 @@
1
+ from selenium.webdriver.remote.webdriver import WebDriver
2
+ from wbcore.contrib.directory.factories import PersonFactory
3
+ from wbcore.contrib.directory.models import Person
4
+ from wbcore.test import (
5
+ click_element_by_path,
6
+ fill_out_form_fields,
7
+ open_create_instance,
8
+ )
9
+
10
+ from wbcrm.factories import ActivityFactory, ActivityTypeFactory
11
+ from wbcrm.models import Activity, ActivityType
12
+ from wbcrm.serializers import ActivityModelSerializer
13
+
14
+
15
+ def set_up_activities(creator: Person = None):
16
+ """Sets up three activities for testing purposes with fixed participants, types and titles.
17
+
18
+ Args:
19
+ creator (Person | None, optional): The person that is used as the activities creator. Defaults to None.
20
+ """
21
+ type_a = ActivityTypeFactory(title="Meeting")
22
+ type_b = ActivityTypeFactory(title="Call")
23
+ participant_a = PersonFactory(last_name="Turing")
24
+ participant_b = PersonFactory(last_name="Zuse")
25
+ participant_c = PersonFactory(last_name="Lovelace")
26
+ activity_creator = creator if creator else PersonFactory()
27
+ ActivityFactory(
28
+ title="Activity A",
29
+ type=type_a,
30
+ creator=activity_creator,
31
+ preceded_by=None,
32
+ participants=[participant_a, participant_c],
33
+ status=Activity.Status.PLANNED,
34
+ )
35
+ ActivityFactory(
36
+ title="Activity B",
37
+ type=type_a,
38
+ creator=activity_creator,
39
+ preceded_by=None,
40
+ participants=[participant_a, participant_b],
41
+ status=Activity.Status.FINISHED,
42
+ )
43
+ ActivityFactory(
44
+ title="Activity C",
45
+ type=type_b,
46
+ creator=activity_creator,
47
+ preceded_by=None,
48
+ participants=[participant_b, participant_c],
49
+ status=Activity.Status.PLANNED,
50
+ )
51
+
52
+
53
+ def create_new_activity_instance(
54
+ driver: WebDriver,
55
+ field_list: list[str],
56
+ type_title="",
57
+ activity_title="",
58
+ is_create_instance_open=True,
59
+ ) -> Activity:
60
+ """A function that automatically creates a new activity for selenium e2e-tests.
61
+ After creating the instance this function will close the create-widget.
62
+
63
+ Args:
64
+ driver (WebDriver): The Selenium webdriver.
65
+ field_list (list[str]): List of fields to be filled in the creation mask. The field names must match the names in the ActivitySerializer.
66
+ type_title (str, optional): The title of the activity type. Defaults to "".
67
+ activity_title (str, optional): The title of the activity. Defaults to "".
68
+ is_create_instance_open (bool, optional): Should be true if the create-widget is already open. Defaults to True.
69
+ """
70
+ if not is_create_instance_open:
71
+ open_create_instance(driver, "CRM", "Create Activity")
72
+
73
+ type_title = type_title if type_title else "Test Activity Type"
74
+ activity_title = activity_title if activity_title else "Test Activity"
75
+ if ActivityType.objects.filter(title=activity_title).exists():
76
+ activity_type = ActivityType.objects.get(title=activity_title)
77
+ else:
78
+ activity_type = ActivityTypeFactory(title=type_title)
79
+ activity = ActivityFactory.build(title=activity_title, type=activity_type)
80
+ serializer = ActivityModelSerializer(activity)
81
+ fill_out_form_fields(driver, serializer, field_list, activity)
82
+ click_element_by_path(driver, "//button[@label='Save and close']")
83
+ return activity
@@ -0,0 +1,370 @@
1
+ import datetime
2
+
3
+ import pytest
4
+ from django.utils import timezone
5
+ from psycopg.types.range import DateRange
6
+ from selenium.webdriver.common.action_chains import ActionChains
7
+ from wbcore.contrib.authentication.factories import SuperUserFactory
8
+ from wbcore.contrib.authentication.models import User
9
+ from wbcore.contrib.authentication.tests.e2e import create_main_company_user
10
+ from wbcore.contrib.directory.models import (
11
+ Company,
12
+ EmployerEmployeeRelationship,
13
+ Person,
14
+ )
15
+ from wbcore.contrib.directory.serializers import (
16
+ ClientManagerModelSerializer,
17
+ CompanyModelSerializer,
18
+ PersonModelSerializer,
19
+ )
20
+ from wbcore.contrib.directory.tests.e2e import (
21
+ create_new_cmr_instance,
22
+ create_new_company_instance,
23
+ create_new_person_instance,
24
+ set_up_companies,
25
+ set_up_persons,
26
+ )
27
+ from wbcore.contrib.directory.viewsets.display import ClientManagerRelationshipColor
28
+ from wbcore.test import (
29
+ click_button_by_label,
30
+ click_close_all_widgets,
31
+ click_new_button,
32
+ delete_list_entry,
33
+ does_background_color_match,
34
+ edit_list_instance,
35
+ find_element_by_text,
36
+ is_counter_as_expected,
37
+ is_error_visible,
38
+ is_string_not_visible,
39
+ is_tag_not_visible,
40
+ is_tag_visible,
41
+ is_text_visible,
42
+ navigate_to_filter,
43
+ open_menu_item,
44
+ select_async_filter,
45
+ select_column_filter,
46
+ select_filter,
47
+ set_up,
48
+ )
49
+
50
+ from wbcrm.factories import ActivityFactory
51
+ from wbcrm.models import Activity
52
+ from wbcrm.serializers import ActivityModelSerializer
53
+ from wbcrm.tests.e2e import create_new_activity_instance, set_up_activities
54
+
55
+ USER_PASSWORD = "User_Password"
56
+
57
+
58
+ @pytest.mark.skip(reason="no way of currently testing this")
59
+ @pytest.mark.django_db
60
+ class TestCRMFilters:
61
+ def test_entry_filter(self, live_server, selenium):
62
+ """
63
+ Testing the "Entry"-Filter.
64
+ """
65
+
66
+ # Creating a test user
67
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD) # noqa
68
+ # Creating three test companies with different last activities.
69
+ companies = set_up_companies()
70
+ now = timezone.now()
71
+ period_a = DateRange(now - datetime.timedelta(days=15, hours=2), now - datetime.timedelta(days=15))
72
+ period_b = DateRange(now - datetime.timedelta(days=60, hours=2), now - datetime.timedelta(days=60))
73
+ ActivityFactory(period=period_a, companies=[companies[0]])
74
+ ActivityFactory(period=period_b, companies=[companies[1], companies[2]])
75
+
76
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
77
+ navigate_to_filter(selenium, "CRM", "Companies")
78
+
79
+ # And the beginning all three companies should be visible, since no filter is set.
80
+ assert is_counter_as_expected(selenium, Company.objects.count())
81
+
82
+ # With no filters selected the there should be a message with the hint "No filter criteria yet"
83
+ assert is_text_visible(selenium, "No filter criteria yet")
84
+
85
+ # Select a "No Activity" - filter. Filtering for all companies, that had no activity in the last month.
86
+ select_filter(selenium, "No Activity:", "Last Month")
87
+
88
+ # The "No filter criteria yet" text should now be gone and instead there should be a "filter-tag" with "Last Month".
89
+ assert is_string_not_visible(selenium, "No filter criteria yet")
90
+ assert is_tag_visible(selenium, "Last Month")
91
+
92
+ # Only two companies should be listed, since one company had an activity withing the last month.
93
+ assert is_text_visible(selenium, "Company B")
94
+ assert is_text_visible(selenium, "Company C")
95
+ assert is_string_not_visible(selenium, "Company A")
96
+ assert is_counter_as_expected(selenium, 2)
97
+
98
+ # Selecting an additional column filter to filter by company type.
99
+ select_column_filter(selenium, "Type", "Type A")
100
+
101
+ # After selecting the additional filter there should also be an additional filter tag corresponding to the filter and the number of companies should be one.
102
+ assert is_tag_visible(selenium, "Last Month")
103
+ assert is_tag_visible(selenium, "Type A")
104
+ assert is_counter_as_expected(selenium, 1)
105
+ assert is_text_visible(selenium, "Company B")
106
+ assert is_string_not_visible(selenium, "Company C")
107
+
108
+ # Resetting the filters
109
+ click_button_by_label(selenium, "Clear all filters")
110
+ assert is_text_visible(selenium, "No filter criteria yet")
111
+ assert is_tag_not_visible(selenium, "Type A")
112
+ assert is_counter_as_expected(selenium, Company.objects.count())
113
+
114
+ # Close all widgets and switch to the person list
115
+ click_close_all_widgets(selenium)
116
+ set_up_persons()
117
+ navigate_to_filter(selenium, "Persons")
118
+
119
+ assert is_counter_as_expected(selenium, Person.objects.count())
120
+ assert is_text_visible(selenium, "No filter criteria yet")
121
+
122
+ # Select a "Specializations" - filter. Filtering for all persons with "Specialization C".
123
+ select_async_filter(selenium, "Specializations:", "Specialization C")
124
+ assert is_tag_visible(selenium, "Specialization C")
125
+ assert is_string_not_visible(selenium, "No filter criteria yet")
126
+
127
+ # Only two persons should be listed
128
+ assert is_string_not_visible(selenium, "Henry Kalb")
129
+ assert is_text_visible(selenium, "Konrad Zuse")
130
+ assert is_text_visible(selenium, "Ada Lovelace")
131
+ filter_count = Person.objects.filter(specializations__title="Specialization C").count()
132
+ assert is_counter_as_expected(selenium, filter_count)
133
+
134
+ # Selecting an additional column filter to filter by person status.
135
+ select_column_filter(selenium, "Status", "Status C")
136
+
137
+ # After selecting the additional filter there should also be an additional filter tag corresponding to the filter and the number of persons should be one.
138
+ assert is_tag_visible(selenium, "Specialization C")
139
+ assert is_tag_visible(selenium, "Status C")
140
+ assert is_string_not_visible(selenium, "Konrad Zuse")
141
+ assert is_text_visible(selenium, "Ada Lovelace")
142
+ filter_count = EmployerEmployeeRelationship.objects.filter(
143
+ employer__in=Company.objects.filter(customer_status__title="Status C"),
144
+ employee__in=Person.objects.filter(specializations__title="Specialization C"),
145
+ primary=True,
146
+ )
147
+ assert is_counter_as_expected(selenium, filter_count)
148
+
149
+ def test_activity_filter(self, live_server, selenium):
150
+ """
151
+ Testing the "Type" and "Status" - Filter
152
+ """
153
+ # Creating a test user and setting up selenium
154
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
155
+ set_up_activities(user.profile)
156
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
157
+
158
+ navigate_to_filter(selenium, "CRM", "Activities")
159
+
160
+ # And the beginning all three activities should be visible, since no filter is set.
161
+ filter_count = Activity.objects.all().count()
162
+ assert is_counter_as_expected(selenium, filter_count)
163
+
164
+ # With no filters selected the there should be a message with the hint "No filter criteria yet"
165
+ assert is_text_visible(selenium, "No filter criteria yet")
166
+
167
+ # Selecting a column filter to filter by activity type.
168
+ select_column_filter(selenium, "Type", "Meeting")
169
+
170
+ assert is_string_not_visible(selenium, "No filter criteria yet")
171
+ assert is_tag_visible(selenium, "Meeting")
172
+
173
+ # After filtering for the type there should be only two activities displayed
174
+ filter_count = Activity.objects.filter(type__title="Meeting").count()
175
+ assert is_counter_as_expected(selenium, filter_count)
176
+ assert is_text_visible(selenium, "Activity A")
177
+ assert is_text_visible(selenium, "Activity B")
178
+ assert is_string_not_visible(selenium, "Activity C")
179
+
180
+ # Select a "Status" - filter. Filtering for all activities, that have the status "Finished".
181
+ select_filter(selenium, "Status:", "Finished")
182
+
183
+ assert is_string_not_visible(selenium, "Activity A")
184
+ assert is_text_visible(selenium, "Activity B")
185
+ filter_count = Activity.objects.filter(type__title="Meeting", status=Activity.Status.FINISHED).count()
186
+ assert is_counter_as_expected(selenium, filter_count)
187
+
188
+
189
+ @pytest.mark.skip(reason="no way of currently testing this")
190
+ @pytest.mark.django_db
191
+ class TestCRMActivities:
192
+ def test_create_edit_delete_activity(self, live_server, selenium):
193
+ """
194
+ Creates, edits and deletes an activity.
195
+ """
196
+ # Creating a test user and setting up selenium
197
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
198
+ actions = ActionChains(selenium, 1000)
199
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
200
+
201
+ # # -----> CREATE <----- #
202
+ activity_a = create_new_activity_instance(selenium, ["title", "type"], "Type A", "Activity A", False)
203
+ open_menu_item(selenium, "Activities", perform_mouse_move=True)
204
+ assert is_text_visible(selenium, activity_a.title)
205
+ assert is_counter_as_expected(selenium, Activity.objects.count())
206
+
207
+ click_new_button(selenium)
208
+ assert is_text_visible(selenium, "New Activity")
209
+
210
+ # Trying to create an activity without filling out anything -> We expect an error to be thrown.
211
+ click_button_by_label(selenium, "Save and close")
212
+ assert is_error_visible(selenium)
213
+
214
+ activity_b = create_new_activity_instance(selenium, ["title", "type"], None, "Activity B")
215
+ assert is_text_visible(selenium, activity_b.title)
216
+ assert is_counter_as_expected(selenium, Activity.objects.count())
217
+
218
+ # -----> Edit <----- #
219
+
220
+ edit_list_instance(selenium, actions, activity_a, ActivityModelSerializer(activity_a), {"title": "A Activity"})
221
+
222
+ assert is_string_not_visible(selenium, "Activity A")
223
+ assert is_text_visible(selenium, "A Activity")
224
+ assert is_counter_as_expected(selenium, Activity.objects.count())
225
+
226
+ # -----> Delete <----- #
227
+ delete_list_entry(selenium, actions, activity_b.title)
228
+ assert is_counter_as_expected(selenium, Activity.objects.count())
229
+ assert is_string_not_visible(selenium, "Activity A")
230
+ assert is_string_not_visible(selenium, "Activity B")
231
+
232
+
233
+ @pytest.mark.skip(reason="no way of currently testing this")
234
+ @pytest.mark.django_db
235
+ class TestCRMEntries:
236
+ def test_create_edit_delete_company(self, live_server, selenium):
237
+ """
238
+ Creating a new company from the company list view. After creating the company it should be displayed in the company list view.
239
+ """
240
+ # Creating a test user
241
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
242
+ actions = ActionChains(selenium, 1000)
243
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
244
+
245
+ # -----> CREATE <----- #
246
+ company_a = create_new_company_instance(
247
+ selenium, ["name", "customer_status"], "Company A", "Test Status", False
248
+ )
249
+ open_menu_item(selenium, "Companies", perform_mouse_move=True)
250
+ assert is_text_visible(selenium, company_a.name)
251
+ assert is_counter_as_expected(selenium, Company.objects.count())
252
+
253
+ click_new_button(selenium)
254
+ assert is_text_visible(selenium, "New Company")
255
+
256
+ # Trying to create an activity without filling out anything -> We expect an error to be thrown.
257
+ click_button_by_label(selenium, "Save and close")
258
+ assert is_error_visible(selenium)
259
+ company_b = create_new_company_instance(selenium, ["name", "customer_status"], "Company B")
260
+
261
+ assert is_text_visible(selenium, company_b.name)
262
+ assert is_counter_as_expected(selenium, Company.objects.count())
263
+
264
+ # -----> Edit <----- #
265
+
266
+ edit_list_instance(selenium, actions, company_a.name, CompanyModelSerializer(company_a), {"name": "A Company"})
267
+ assert is_string_not_visible(selenium, "Company A")
268
+ assert is_text_visible(selenium, "A Company")
269
+ assert is_counter_as_expected(selenium, Company.objects.count())
270
+
271
+ # -----> Delete <----- #
272
+
273
+ delete_list_entry(selenium, actions, company_b.name)
274
+ assert is_counter_as_expected(selenium, Company.objects.count())
275
+ assert is_text_visible(selenium, "A Company")
276
+ assert is_string_not_visible(selenium, "Company A")
277
+ assert is_string_not_visible(selenium, "Company B")
278
+
279
+ def test_create_edit_delete_person(self, live_server, selenium):
280
+ """
281
+ Creating a new person from the person list view. After creating the person it should be displayed in the person list view.
282
+ """
283
+ # Creating a test user
284
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
285
+ actions = ActionChains(selenium, 1000)
286
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
287
+
288
+ # -----> CREATE <----- #
289
+ person_a = create_new_person_instance(
290
+ selenium, ["first_name", "last_name", "prefix"], "Max", "Mustermann", "Mr.", False
291
+ )
292
+ open_menu_item(selenium, "Persons", perform_mouse_move=True)
293
+ assert is_text_visible(selenium, f"{person_a.first_name} {person_a.last_name}")
294
+ assert is_counter_as_expected(selenium, Person.objects.count())
295
+
296
+ click_new_button(selenium)
297
+ assert is_text_visible(selenium, "New Person")
298
+
299
+ # Trying to create an activity without filling out anything -> We expect an error to be thrown.
300
+ click_button_by_label(selenium, "Save and close")
301
+ assert is_error_visible(selenium)
302
+ person_b = create_new_person_instance(
303
+ selenium, ["first_name", "last_name", "prefix"], "Maike", "Musterfrau", "Mrs.", False
304
+ )
305
+
306
+ assert is_text_visible(selenium, f"{person_b.first_name} {person_b.last_name}")
307
+ assert is_counter_as_expected(selenium, Person.objects.count())
308
+
309
+ # -----> Edit <----- #
310
+
311
+ edit_list_instance(
312
+ selenium,
313
+ actions,
314
+ f"{person_a.first_name} {person_a.last_name}",
315
+ PersonModelSerializer(person_a),
316
+ {"first_name": "Maxissimus"},
317
+ )
318
+ assert is_string_not_visible(selenium, "Max Mustermann")
319
+ assert is_text_visible(selenium, "Maxissimus Mustermann")
320
+ assert is_counter_as_expected(selenium, Person.objects.count())
321
+
322
+ # -----> Delete <----- #
323
+
324
+ delete_list_entry(selenium, actions, f"{person_b.first_name} {person_b.last_name}")
325
+ assert is_counter_as_expected(selenium, Person.objects.count())
326
+ assert is_text_visible(selenium, "Maxissimus Mustermann")
327
+ assert is_string_not_visible(selenium, "Max Mustermann")
328
+ assert is_string_not_visible(selenium, "Maike Mustermann")
329
+
330
+
331
+ @pytest.mark.skip(reason="no way of currently testing this")
332
+ @pytest.mark.django_db
333
+ # This test class is currently bugged because of frontend issues.
334
+ class TestCRMRelationShipManagement:
335
+ def test_create_edit_and_delete_relationship(self, live_server, selenium):
336
+ """
337
+ Creates, edits and deletes a Customer-Employer-Relationship.
338
+ """
339
+ user: User = create_main_company_user(USER_PASSWORD)
340
+ actions = ActionChains(selenium, 1000)
341
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
342
+ set_up_persons()
343
+
344
+ # -----> CREATE <----- #
345
+ cmr = create_new_cmr_instance(selenium, ["client", "relationship_manager", "primary"], True, False)
346
+ open_menu_item(selenium, "Client Manager Relationships", perform_mouse_move=True)
347
+ cmr_element = find_element_by_text(selenium, cmr.client.computed_str)
348
+
349
+ assert is_counter_as_expected(selenium, 1)
350
+ assert is_text_visible(selenium, cmr.client.computed_str)
351
+ assert is_text_visible(selenium, cmr.relationship_manager.computed_str)
352
+ assert does_background_color_match(cmr_element, ClientManagerRelationshipColor.DRAFT.value)
353
+
354
+ # # -----> Edit <----- #
355
+
356
+ edit_list_instance(
357
+ selenium,
358
+ actions,
359
+ cmr.client.computed_str,
360
+ ClientManagerModelSerializer(cmr),
361
+ {"relationship_manager": user.profile.computed_str},
362
+ )
363
+ assert is_string_not_visible(selenium, cmr.relationship_manager.computed_str)
364
+ assert is_text_visible(selenium, user.profile.computed_str)
365
+ assert is_counter_as_expected(selenium, Person.objects.count())
366
+
367
+ # # -----> Approve <----- #
368
+ # TODO Implement approve
369
+ # -----> Delete <----- #
370
+ # TODO Implement delete
@@ -0,0 +1,40 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+ from rest_framework.test import APIRequestFactory
5
+ from wbcore.contrib.authentication.factories import UserFactory
6
+ from wbcore.contrib.directory.factories import PersonFactory
7
+ from wbcore.contrib.workflow.factories import (
8
+ ProcessStepFactory,
9
+ RandomChildStepFactory,
10
+ UserStepFactory,
11
+ )
12
+
13
+ from wbcrm.workflows.assignee_methods import activity_assignee
14
+
15
+
16
+ @pytest.mark.django_db
17
+ class TestAssigneeMethods:
18
+ api_factory = APIRequestFactory()
19
+
20
+ def test_activity_assignee(self, activity_factory):
21
+ user = UserFactory()
22
+ activity = activity_factory(assigned_to=user.profile)
23
+ process_step = ProcessStepFactory(process__instance=activity)
24
+ assert activity_assignee(process_step) == user
25
+
26
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
27
+ def test_activity_assignee_no_activity(self, mock_failed):
28
+ step = RandomChildStepFactory(exclude_factories=[UserStepFactory])
29
+ process_step = ProcessStepFactory(step=step)
30
+ assert activity_assignee(process_step) is None
31
+ assert mock_failed.call_args.args[0] == process_step
32
+
33
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
34
+ def test_activity_assignee_no_user_account(self, mock_failed, activity_factory):
35
+ assignee = PersonFactory()
36
+ activity = activity_factory(assigned_to=assignee)
37
+ step = RandomChildStepFactory(exclude_factories=[UserStepFactory])
38
+ process_step = ProcessStepFactory(step=step, process__instance=activity)
39
+ assert activity_assignee(process_step) is None
40
+ assert mock_failed.call_args.args[0] == process_step
@@ -0,0 +1,112 @@
1
+ import pytest
2
+ from rest_framework import status
3
+ from rest_framework.test import APIRequestFactory
4
+ from wbcore.test.utils import get_kwargs
5
+
6
+ from wbcrm.factories import ActivityFactory
7
+ from wbcrm.viewsets import ActivityChartModelViewSet, ActivityViewSet
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestSpecificsChartViewsets:
12
+ @pytest.mark.parametrize(
13
+ "mvs, factory",
14
+ [
15
+ (ActivityChartModelViewSet, ActivityFactory),
16
+ ],
17
+ )
18
+ def test_option_request(self, mvs, factory, superuser):
19
+ request = APIRequestFactory().options("")
20
+ request.user = superuser
21
+ factory()
22
+ kwargs = {"user_id": request.user.id}
23
+ vs = mvs.as_view({"options": "options"})
24
+ response = vs(request, **kwargs)
25
+ assert response.status_code == status.HTTP_200_OK
26
+ assert response.data
27
+
28
+ @pytest.mark.parametrize(
29
+ "mvs, factory",
30
+ [
31
+ (ActivityChartModelViewSet, ActivityFactory),
32
+ ],
33
+ )
34
+ def test_viewsets(self, mvs, factory, superuser):
35
+ request = APIRequestFactory().get("")
36
+ request.user = superuser
37
+ factory()
38
+ kwargs = {"user_id": request.user.id}
39
+ vs = mvs.as_view({"get": "list"})
40
+ response = vs(request, **kwargs)
41
+ assert response.status_code == status.HTTP_200_OK
42
+ assert response.data
43
+
44
+
45
+ @pytest.mark.django_db
46
+ class TestSpecificsInfiniteViewsets:
47
+ @pytest.mark.parametrize(
48
+ "mvs, factory",
49
+ [
50
+ (
51
+ ActivityViewSet,
52
+ ActivityFactory,
53
+ ),
54
+ ],
55
+ )
56
+ def test_option_request(self, mvs, factory, superuser):
57
+ request = APIRequestFactory().options("")
58
+ request.user = superuser
59
+ obj = factory(participants=(request.user.profile,))
60
+ request.GET = request.GET.copy()
61
+ request.GET["date_gte"] = str(obj.start.date())
62
+ kwargs = {}
63
+ mvs.request = request
64
+ vs = mvs.as_view({"options": "options"})
65
+ kwargs = get_kwargs(obj, mvs, request=request)
66
+ response = vs(request, **kwargs).render()
67
+ assert response.status_code == status.HTTP_200_OK
68
+ assert response.data
69
+
70
+ @pytest.mark.parametrize(
71
+ "mvs, factory",
72
+ [
73
+ (
74
+ ActivityViewSet,
75
+ ActivityFactory,
76
+ ),
77
+ ],
78
+ )
79
+ def test_viewsets(self, mvs, factory, superuser):
80
+ request = APIRequestFactory().get("")
81
+ request.user = superuser
82
+ obj = factory(participants=(request.user.profile,))
83
+ request.GET = request.GET.copy()
84
+ request.GET["date_gte"] = str(obj.start.date())
85
+ kwargs = {}
86
+ mvs.request = request
87
+ vs = mvs.as_view({"get": "list"})
88
+ response = vs(request, **kwargs).render()
89
+ assert response.status_code == status.HTTP_200_OK
90
+ assert response.data
91
+
92
+ @pytest.mark.parametrize(
93
+ "mvs, factory",
94
+ [
95
+ (
96
+ ActivityViewSet,
97
+ ActivityFactory,
98
+ ),
99
+ ],
100
+ )
101
+ def test_viewsets_without_date_gte(self, mvs, factory, superuser):
102
+ request = APIRequestFactory().get("")
103
+ request.user = superuser
104
+ factory(participants=(request.user.profile,))
105
+ request.GET = request.GET.copy()
106
+ request.GET["date_gte"] = None
107
+ kwargs = {}
108
+ mvs.request = request
109
+ vs = mvs.as_view({"get": "list"})
110
+ response = vs(request, **kwargs).render()
111
+ assert response.status_code == status.HTTP_200_OK
112
+ assert response.data
@@ -0,0 +1,64 @@
1
+ import pytest
2
+ from wbcore.contrib.directory.typings import Person as PersonDTO
3
+
4
+ from wbcrm.typings import Activity as ActivityDTO
5
+ from wbcrm.typings import ParticipantStatus as ParticipantStatusDTO
6
+ from wbcrm.typings import User as UserDTO
7
+
8
+
9
+ @pytest.mark.django_db
10
+ class TestDTO:
11
+ @pytest.mark.parametrize("user_id", [None, 1])
12
+ def test_user_comparison(self, user_id):
13
+ user1 = UserDTO(1, {})
14
+ user2 = UserDTO(id=user_id, metadata={})
15
+ if user1.id == user2.id is not None:
16
+ assert user1 == user2
17
+ else:
18
+ assert user1 != user2
19
+ assert user2 is not None
20
+
21
+ @pytest.mark.parametrize(
22
+ "id, name, email",
23
+ [
24
+ (None, None, None),
25
+ (None, "test", None),
26
+ (None, "test", "test@test.com"),
27
+ (1, None, None),
28
+ (1, "test", None),
29
+ (1, "test", "test@test.com"),
30
+ ],
31
+ )
32
+ def test_participation_status(self, id, name, email):
33
+ person1 = PersonDTO(1, "test", "test", "test@test.com")
34
+ person2 = PersonDTO(id=id, first_name=name, last_name=name, email=email)
35
+
36
+ participant1 = ParticipantStatusDTO(person=person1)
37
+ participant2 = ParticipantStatusDTO(person=person2)
38
+ if (participant1.id == participant2.id is not None) or (
39
+ participant1.person == participant2.person and participant1.activity == participant2.activity
40
+ ):
41
+ assert participant1 == participant2
42
+ else:
43
+ assert participant1 != participant2
44
+ assert participant2 is not None
45
+
46
+ @pytest.mark.parametrize(
47
+ "id, title, email",
48
+ [
49
+ (None, None, None),
50
+ (None, "test", None),
51
+ (None, "test", "test@test.com"),
52
+ (1, None, None),
53
+ (1, "test", None),
54
+ (1, "test", "test@test.com"),
55
+ ],
56
+ )
57
+ def test_activity(self, id, title, email):
58
+ activity1 = ActivityDTO(id=1, title="test", metadata={})
59
+ activity2 = ActivityDTO(id=id, title=title, metadata={})
60
+ if activity1.id == activity2.id is not None:
61
+ assert activity1 == activity2
62
+ else:
63
+ assert activity1 != activity2
64
+ assert activity2 is not None