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
wbcrm/tests/tests.py ADDED
@@ -0,0 +1,121 @@
1
+ import pytest
2
+ from django.dispatch import receiver
3
+ from rest_framework import status
4
+ from rest_framework.test import APIRequestFactory
5
+ from termcolor import colored
6
+ from wbcore.contrib.directory.factories import PersonFactory
7
+ from wbcore.test import GenerateTest, default_config
8
+ from wbcore.test.mixins import TestViewSet
9
+ from wbcore.test.signals import custom_update_kwargs
10
+ from wbcore.test.utils import get_data_from_factory, get_kwargs, get_or_create_superuser
11
+
12
+ from wbcrm.factories import AccountFactory
13
+ from wbcrm.viewsets import ActivityParticipantModelViewSet
14
+ from wbcrm.viewsets.accounts import (
15
+ ChildAccountAccountModelViewSet,
16
+ InheritedAccountRoleAccountModelViewSet,
17
+ )
18
+
19
+ config = {}
20
+ for key, value in default_config.items():
21
+ config[key] = list(
22
+ filter(
23
+ lambda x: x.__module__.startswith("wbcrm")
24
+ and x.__name__
25
+ not in [
26
+ "ActivityParticipantModelViewSet",
27
+ "InheritedAccountRoleAccountModelViewSet",
28
+ "AccountRoleAccountModelViewSet",
29
+ "ChildAccountAccountModelViewSet",
30
+ ],
31
+ value,
32
+ )
33
+ )
34
+
35
+
36
+ @pytest.mark.django_db
37
+ @GenerateTest(config)
38
+ class TestProject:
39
+ pass
40
+
41
+
42
+ # ActivityParticipantModelViewSet Test
43
+ class ActivityParticipantTestViewSet(TestViewSet):
44
+ def _get_mixins_update_data(self, type):
45
+ api_request = APIRequestFactory()
46
+ superuser = get_or_create_superuser()
47
+ obj = self.factory()
48
+ data = get_data_from_factory(obj, self.mvs, superuser=superuser, update=True)
49
+ data["participant"] = PersonFactory().id
50
+ if type == "PATCH":
51
+ request = api_request.patch("", data)
52
+ else: # "UPDATE"
53
+ request = api_request.put("", data)
54
+ request.user = superuser
55
+ kwargs = get_kwargs(obj, self.mvs, request=request, data=data)
56
+ return obj, request, kwargs, data
57
+
58
+ def test_patch_request(self):
59
+ obj, request, kwargs, data = self._get_mixins_update_data("PATCH")
60
+ vs = self.mvs.as_view({"patch": "partial_update"})
61
+ ep = self._get_endpoint_config(request, kwargs, obj)
62
+ ep_update = ep.get_instance_endpoint()
63
+ response = vs(request, **kwargs, data=data)
64
+ if ep_update:
65
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
66
+ assert response.data.get("instance"), str(response.data.get("instance")) + " should not be empty"
67
+ else:
68
+ assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
69
+ str(response.status_code) + f" == 405 ({response.data})"
70
+ )
71
+ print(f"- {self.__class__.__name__}:test_patchviewset", colored("PASSED", "green")) # noqa: T201
72
+
73
+ def test_update_request(self):
74
+ obj, request, kwargs, _ = self._get_mixins_update_data("UPDATE")
75
+ vs = self.mvs.as_view({"put": "update"})
76
+ ep = self._get_endpoint_config(request, kwargs, obj)
77
+ ep_update = ep.get_instance_endpoint()
78
+ response = vs(request, **kwargs)
79
+ if ep_update:
80
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
81
+ assert response.data.get("instance"), str(response.data.get("instance")) + " should not be empty"
82
+ else:
83
+ assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
84
+ str(response.status_code) + f" == 405 ({response.data})"
85
+ )
86
+ print(f"- {self.__class__.__name__}:test_update_request", colored("PASSED", "green")) # noqa: T201
87
+
88
+
89
+ class GenerateActivityParticipantTest(GenerateTest):
90
+ def test_modelviewsets(self, mvs, client):
91
+ my_test = ActivityParticipantTestViewSet(mvs)
92
+ my_test.execute_test_list_endpoint(client)
93
+ my_test.execute_test_detail_endpoint()
94
+
95
+
96
+ @pytest.mark.django_db
97
+ @GenerateActivityParticipantTest({"viewsets": [ActivityParticipantModelViewSet]})
98
+ class TestActivityParticipant:
99
+ pass
100
+
101
+
102
+ @receiver(custom_update_kwargs, sender=ChildAccountAccountModelViewSet)
103
+ def receive_kwargs_child_account(sender, *args, **kwargs):
104
+ if obj := kwargs.get("obj_factory"):
105
+ parent = AccountFactory.create()
106
+ obj.parent = parent
107
+ obj.save()
108
+ return {"account_id": parent.id}
109
+ return {}
110
+
111
+
112
+ @receiver(custom_update_kwargs, sender=InheritedAccountRoleAccountModelViewSet)
113
+ def receive_kwargs_inherited_account_role(sender, *args, **kwargs):
114
+ if obj := kwargs.get("obj_factory"):
115
+ parent_account = AccountFactory.create()
116
+ child_account = obj.account
117
+ child_account.parent = parent_account
118
+ child_account.save()
119
+ obj.account = parent_account
120
+ obj.save()
121
+ return {"account_id": child_account.id}
wbcrm/typings.py ADDED
@@ -0,0 +1,109 @@
1
+ import enum
2
+ from dataclasses import dataclass, field
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from psycopg.types.range import TimestamptzRange
7
+ from wbcore.contrib.agenda.typings import ConferenceRoom
8
+ from wbcore.contrib.directory.typings import Person
9
+
10
+
11
+ @dataclass
12
+ class User:
13
+ metadata: dict[str, Any]
14
+ id: str = None
15
+
16
+ def __eq__(self, other):
17
+ if other:
18
+ return self.id == other.id
19
+ return super().__eq__(other)
20
+
21
+
22
+ @dataclass
23
+ class ParticipantStatus:
24
+ class ParticipationStatus(enum.Enum):
25
+ CANCELLED = "CANCELLED"
26
+ MAYBE = "MAYBE"
27
+ ATTENDS = "ATTENDS"
28
+ NOTRESPONDED = "NOTRESPONDED"
29
+ ATTENDS_DIGITALLY = "ATTENDS_DIGITALLY"
30
+ PENDING_INVITATION = "PENDING_INVITATION"
31
+
32
+ person: Person
33
+ status_changed: datetime = None
34
+ status: str = ParticipationStatus.PENDING_INVITATION.name
35
+ activity: "Activity" = None
36
+ id: str = None
37
+
38
+ def __eq__(self, other):
39
+ if other and (
40
+ (self.id and other.id and self.id == other.id)
41
+ or (self.person == other.person and self.activity == other.activity)
42
+ ):
43
+ return True
44
+ return super().__eq__(other)
45
+
46
+
47
+ @dataclass
48
+ class Activity:
49
+ class ReoccuranceChoice(enum.Enum):
50
+ NEVER = "NEVER"
51
+ BUSINESS_DAILY = "RRULE:FREQ=DAILY;INTERVAL=1;WKST=MO;BYDAY=MO,TU,WE,TH,FR"
52
+ DAILY = "RRULE:FREQ=DAILY"
53
+ WEEKLY = "RRULE:FREQ=WEEKLY"
54
+ BIWEEKLY = "RRULE:FREQ=WEEKLY;INTERVAL=2"
55
+ MONTHLY = "RRULE:FREQ=MONTHLY"
56
+ QUARTERLY = "RRULE:FREQ=MONTHLY;INTERVAL=3"
57
+ YEARLY = "RRULE:FREQ=YEARLY"
58
+
59
+ class Visibility(enum.Enum):
60
+ PUBLIC = "PUBLIC"
61
+ PRIVATE = "PRIVATE"
62
+ CONFIDENTIAL = "CONFIDENTIAL"
63
+
64
+ class ReminderChoice(enum.Enum):
65
+ NEVER = "NEVER"
66
+ EVENT_TIME = "EVENT_TIME"
67
+ MINUTES_5 = "MINUTES_5"
68
+ MINUTES_15 = "MINUTES_15"
69
+ MINUTES_30 = "MINUTES_30"
70
+ HOURS_1 = "HOURS_1"
71
+ HOURS_2 = "HOURS_2"
72
+ HOURS_12 = "HOURS_12"
73
+ WEEKS_1 = "WEEKS_1"
74
+
75
+ @property
76
+ def is_recurrent(self):
77
+ return self.repeat_choice != self.ReoccuranceChoice.NEVER.name
78
+
79
+ metadata: dict[str, Any]
80
+ title: str
81
+ period: TimestamptzRange = None
82
+ description: str = ""
83
+ participants: list["ParticipantStatus"] = field(default_factory=list)
84
+ creator: Person = None
85
+ visibility: str = Visibility.PUBLIC
86
+ reminder_choice: str = ReminderChoice.MINUTES_15.name
87
+ is_cancelled: bool = False
88
+ all_day: bool = False
89
+ online_meeting: bool = False
90
+ location: str = None
91
+ conference_room: ConferenceRoom = None
92
+ id: str = None
93
+
94
+ # parent_occurrence: "Activity" = None
95
+ recurring_activities: list["Activity"] = field(default_factory=list)
96
+ invalid_recurring_activities: list["Activity"] = field(default_factory=list)
97
+ is_root: bool = False
98
+ is_leaf: bool = False
99
+ exclude_from_propagation: bool = False
100
+ propagate_for_all_children: bool = False
101
+ recurrence_end: datetime = None
102
+ recurrence_count: int = 0
103
+ repeat_choice: str = ReoccuranceChoice.NEVER.name
104
+ delete_notification: bool = False
105
+
106
+ def __eq__(self, other):
107
+ if other and (self.id and other.id and self.id == other.id):
108
+ return True
109
+ return super().__eq__(other)
wbcrm/urls.py ADDED
@@ -0,0 +1,67 @@
1
+ from django.urls import include, path
2
+ from wbcore.routers import WBCoreRouter
3
+
4
+ from . import viewsets
5
+
6
+ router = WBCoreRouter()
7
+
8
+ # Representations
9
+ router.register(r"activityrepresentation", viewsets.ActivityRepresentationViewSet, basename="activityrepresentation")
10
+ router.register(r"grouprepresentation", viewsets.GroupRepresentationViewSet, basename="grouprepresentation")
11
+ router.register(r"group", viewsets.GroupModelViewSet, basename="group")
12
+ router.register(r"activitytype", viewsets.ActivityTypeModelViewSet, basename="activitytype")
13
+ router.register(
14
+ r"activitytyperepresentation", viewsets.ActivityTypeRepresentationViewSet, basename="activitytyperepresentation"
15
+ )
16
+
17
+ router.register(r"product", viewsets.ProductModelViewSet, basename="product")
18
+ router.register(
19
+ r"productrepresentation",
20
+ viewsets.ProductRepresentationViewSet,
21
+ basename="productrepresentation",
22
+ )
23
+
24
+
25
+ # Activity
26
+ router.register(r"activity", viewsets.ActivityViewSet, basename="activity")
27
+ # used to create new activity instances
28
+
29
+ router.register(r"activitychart", viewsets.ActivityChartModelViewSet, basename="activitychart")
30
+
31
+
32
+ activity_router = WBCoreRouter()
33
+ activity_router.register(
34
+ r"activity-participant",
35
+ viewsets.ActivityParticipantModelViewSet,
36
+ basename="activity-participant",
37
+ )
38
+
39
+ company_router = WBCoreRouter()
40
+ company_router.register(
41
+ r"company-interestedproduct",
42
+ viewsets.ProductCompanyRelationshipCompanyModelViewSet,
43
+ basename="company-interestedproduct",
44
+ )
45
+
46
+ router.register(r"account", viewsets.AccountModelViewSet, basename="account")
47
+ router.register(r"accountrepresentation", viewsets.AccountRepresentationViewSet, basename="accountrepresentation")
48
+ router.register(
49
+ r"accountroletyperepresentation",
50
+ viewsets.AccountRoleTypeRepresentationViewSet,
51
+ basename="accountroletyperepresentation",
52
+ )
53
+
54
+ account_router = WBCoreRouter()
55
+ account_router.register(r"childaccount", viewsets.ChildAccountAccountModelViewSet, basename="account-childaccount")
56
+ account_router.register(r"accountrole", viewsets.AccountRoleAccountModelViewSet, basename="account-accountrole")
57
+ account_router.register(
58
+ r"inheritedrole", viewsets.InheritedAccountRoleAccountModelViewSet, basename="account-inheritedrole"
59
+ )
60
+
61
+ urlpatterns = [
62
+ path("", include(router.urls)),
63
+ path("activity/<int:activity_id>/", include(activity_router.urls)),
64
+ path("company/<int:company_id>/", include(company_router.urls)),
65
+ path("sync/", include(("wbcrm.synchronization.urls", "sync"), namespace="sync")),
66
+ path("account/<int:account_id>/", include(account_router.urls)),
67
+ ]
@@ -0,0 +1,22 @@
1
+ from .accounts import (
2
+ AccountModelViewSet,
3
+ AccountRepresentationViewSet,
4
+ AccountRoleAccountModelViewSet,
5
+ AccountRoleTypeRepresentationViewSet,
6
+ ChildAccountAccountModelViewSet,
7
+ InheritedAccountRoleAccountModelViewSet,
8
+ )
9
+ from .activities import (
10
+ ActivityChartModelViewSet,
11
+ ActivityParticipantModelViewSet,
12
+ ActivityRepresentationViewSet,
13
+ ActivityTypeModelViewSet,
14
+ ActivityTypeRepresentationViewSet,
15
+ ActivityViewSet,
16
+ )
17
+ from .groups import GroupModelViewSet, GroupRepresentationViewSet
18
+ from .products import (
19
+ ProductCompanyRelationshipCompanyModelViewSet,
20
+ ProductModelViewSet,
21
+ ProductRepresentationViewSet,
22
+ )
@@ -0,0 +1,122 @@
1
+ from django.db.models import Case, Exists, F, IntegerField, OuterRef, When
2
+ from django.shortcuts import get_object_or_404
3
+ from django.utils.functional import cached_property
4
+ from rest_framework.exceptions import AuthenticationFailed
5
+ from wbcore import viewsets
6
+ from wbcore.utils.views import MergeMixin
7
+
8
+ from wbcrm.filters import AccountFilter, AccountRoleFilterSet
9
+ from wbcrm.models.accounts import Account, AccountRole, AccountRoleType
10
+ from wbcrm.serializers.accounts import (
11
+ AccountModelSerializer,
12
+ AccountRepresentationSerializer,
13
+ AccountRoleModelSerializer,
14
+ AccountRoleTypeRepresentationSerializer,
15
+ )
16
+ from wbcrm.viewsets.buttons import AccountButtonConfig
17
+ from wbcrm.viewsets.display import (
18
+ AccountDisplayConfig,
19
+ AccountRoleAccountDisplayConfig,
20
+ InheritedAccountRoleAccountDisplayConfig,
21
+ )
22
+ from wbcrm.viewsets.endpoints import (
23
+ AccountRoleAccountEndpointConfig,
24
+ ChildAccountAccountEndpointConfig,
25
+ InheritedAccountRoleAccountEndpointConfig,
26
+ )
27
+ from wbcrm.viewsets.titles import (
28
+ AccountRoleAccountTitleConfig,
29
+ AccountTitleConfig,
30
+ ChildAccountAccountTitleConfig,
31
+ )
32
+
33
+ from .mixins import AccountPermissionMixin, AccountRolePermissionMixin
34
+
35
+
36
+ class AccountRoleTypeRepresentationViewSet(viewsets.RepresentationViewSet):
37
+ ordering_fields = search_fields = ordering = ("title",)
38
+ serializer_class = AccountRoleTypeRepresentationSerializer
39
+ queryset = AccountRoleType.objects.all()
40
+
41
+
42
+ class AccountRepresentationViewSet(AccountPermissionMixin, viewsets.RepresentationViewSet):
43
+ ordering_fields = ("title",)
44
+ search_fields = ["computed_str", "owner__computed_str"]
45
+ filterset_class = AccountFilter
46
+
47
+ serializer_class = AccountRepresentationSerializer
48
+ queryset = Account.all_objects.all()
49
+ ordering = ["title"]
50
+
51
+
52
+ class AccountModelViewSet(MergeMixin, AccountPermissionMixin, viewsets.ModelViewSet):
53
+ serializer_class = AccountModelSerializer
54
+ filterset_class = AccountFilter
55
+ queryset = Account.all_objects.select_related("owner", "parent").annotate(
56
+ has_children=Exists(Account.objects.filter(parent=OuterRef("pk"))),
57
+ _group_key=Case(When(has_children=True, then=F("id")), default=None, output_field=IntegerField()),
58
+ )
59
+
60
+ ordering = ordering_fields = [
61
+ "title",
62
+ "owner__computed_str",
63
+ "reference_id",
64
+ "is_terminal_account",
65
+ "is_public",
66
+ "is_active",
67
+ ]
68
+ search_fields = ["computed_str", "owner__computed_str"]
69
+
70
+ display_config_class = AccountDisplayConfig
71
+ title_config_class = AccountTitleConfig
72
+ button_config_class = AccountButtonConfig
73
+
74
+ def get_merged_object_representation_serializer(self):
75
+ return AccountRepresentationSerializer
76
+
77
+
78
+ class ChildAccountAccountModelViewSet(AccountModelViewSet):
79
+ title_config_class = ChildAccountAccountTitleConfig
80
+ endpoint_config_class = ChildAccountAccountEndpointConfig
81
+
82
+ def dispatch(self, *args, **kwargs):
83
+ kwargs["parent_id"] = kwargs.get("account_id", None)
84
+ return super().dispatch(*args, **kwargs)
85
+
86
+ def get_queryset(self):
87
+ return super().get_queryset().filter(parent_id=self.kwargs["account_id"])
88
+
89
+
90
+ class AccountRoleAccountModelViewSet(AccountRolePermissionMixin, viewsets.ModelViewSet):
91
+ display_config_class = AccountRoleAccountDisplayConfig
92
+ title_config_class = AccountRoleAccountTitleConfig
93
+ endpoint_config_class = AccountRoleAccountEndpointConfig
94
+ serializer_class = AccountRoleModelSerializer
95
+
96
+ filterset_class = AccountRoleFilterSet
97
+ queryset = AccountRole.objects.select_related(
98
+ "entry",
99
+ "account",
100
+ )
101
+ ordering = ["role_type", "id"]
102
+
103
+ @cached_property
104
+ def account(self):
105
+ account = get_object_or_404(Account, pk=self.kwargs["account_id"])
106
+ if not account.can_see_account(self.request.user):
107
+ raise AuthenticationFailed()
108
+ return account
109
+
110
+ def get_queryset(self):
111
+ return super().get_queryset().filter(account=self.account)
112
+
113
+
114
+ class InheritedAccountRoleAccountModelViewSet(AccountRoleAccountModelViewSet):
115
+ display_config_class = InheritedAccountRoleAccountDisplayConfig
116
+ endpoint_config_class = InheritedAccountRoleAccountEndpointConfig
117
+ READ_ONLY = True
118
+
119
+ def get_queryset(self):
120
+ return AccountRole.objects.filter(
121
+ id__in=self.account.get_inherited_roles_for_account().values("id")
122
+ ).filter_for_user(self.request.user, validity_date=self.validity_date)