wbcrm 1.56.0__tar.gz → 1.56.2__tar.gz

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.

Files changed (184) hide show
  1. {wbcrm-1.56.0 → wbcrm-1.56.2}/PKG-INFO +1 -1
  2. wbcrm-1.56.2/wbcrm/migrations/0018_activity_search_vector.py +24 -0
  3. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/activities.py +79 -4
  4. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/activities.py +1 -1
  5. wbcrm-1.56.2/wbcrm/signals.py +4 -0
  6. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/controller.py +0 -3
  7. wbcrm-1.56.2/wbcrm/synchronization/activity/shortcuts.py +16 -0
  8. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tasks.py +22 -43
  9. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/activities.py +2 -6
  10. wbcrm-1.56.0/wbcrm/synchronization/activity/shortcuts.py +0 -14
  11. {wbcrm-1.56.0 → wbcrm-1.56.2}/.gitignore +0 -0
  12. {wbcrm-1.56.0 → wbcrm-1.56.2}/pyproject.toml +0 -0
  13. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/__init__.py +0 -0
  14. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/admin/__init__.py +0 -0
  15. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/admin/accounts.py +0 -0
  16. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/admin/activities.py +0 -0
  17. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/admin/events.py +0 -0
  18. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/admin/groups.py +0 -0
  19. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/admin/products.py +0 -0
  20. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/apps.py +0 -0
  21. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/configurations/__init__.py +0 -0
  22. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/configurations/base.py +0 -0
  23. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/dynamic_preferences_registry.py +0 -0
  24. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/factories/__init__.py +0 -0
  25. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/factories/accounts.py +0 -0
  26. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/factories/activities.py +0 -0
  27. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/factories/groups.py +0 -0
  28. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/factories/products.py +0 -0
  29. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/filters/__init__.py +0 -0
  30. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/filters/accounts.py +0 -0
  31. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/filters/activities.py +0 -0
  32. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/filters/groups.py +0 -0
  33. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/filters/products.py +0 -0
  34. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/filters/signals.py +0 -0
  35. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/fixtures/wbcrm.json +0 -0
  36. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/kpi_handlers/activities.py +0 -0
  37. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/de/LC_MESSAGES/django.mo +0 -0
  38. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/de/LC_MESSAGES/django.po +0 -0
  39. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/de/LC_MESSAGES/django.po.translated +0 -0
  40. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/en/LC_MESSAGES/django.mo +0 -0
  41. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/en/LC_MESSAGES/django.po +0 -0
  42. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/fr/LC_MESSAGES/django.mo +0 -0
  43. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/locale/fr/LC_MESSAGES/django.po +0 -0
  44. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +0 -0
  45. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0002_alter_activity_repeat_choice.py +0 -0
  46. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0003_remove_activity_external_id_and_more.py +0 -0
  47. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0004_alter_activity_status.py +0 -0
  48. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +0 -0
  49. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0006_alter_activity_location.py +0 -0
  50. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0007_alter_account_status.py +0 -0
  51. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0008_alter_activity_options.py +0 -0
  52. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0009_alter_account_is_public.py +0 -0
  53. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0010_alter_account_reference_id.py +0 -0
  54. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0011_activity_summary.py +0 -0
  55. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0012_alter_activity_summary.py +0 -0
  56. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +0 -0
  57. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0014_alter_account_relationship_status.py +0 -0
  58. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0015_alter_activity_type.py +0 -0
  59. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0016_auto_20241205_1015.py +0 -0
  60. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/0017_event.py +0 -0
  61. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/migrations/__init__.py +0 -0
  62. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/__init__.py +0 -0
  63. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/accounts.py +0 -0
  64. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/events.py +0 -0
  65. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/groups.py +0 -0
  66. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/llm/activity_summaries.py +0 -0
  67. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/llm/analyze_relationship.py +0 -0
  68. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/products.py +0 -0
  69. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/models/recurrence.py +0 -0
  70. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/preferences.py +0 -0
  71. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/report/activity_report.py +0 -0
  72. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/__init__.py +0 -0
  73. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/accounts.py +0 -0
  74. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/groups.py +0 -0
  75. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/products.py +0 -0
  76. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/recurrence.py +0 -0
  77. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/serializers/signals.py +0 -0
  78. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/static/wbcrm/markdown/documentation/activity.md +0 -0
  79. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/static/wbcrm/markdown/documentation/activitytype.md +0 -0
  80. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/static/wbcrm/markdown/documentation/group.md +0 -0
  81. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/static/wbcrm/markdown/documentation/product.md +0 -0
  82. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/__init__.py +0 -0
  83. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/__init__.py +0 -0
  84. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/admin.py +0 -0
  85. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backend.py +0 -0
  86. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/__init__.py +0 -0
  87. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/__init__.py +0 -0
  88. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +0 -0
  89. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +0 -0
  90. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/create.py +0 -0
  91. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/delete.py +0 -0
  92. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/update.py +0 -0
  93. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/request_utils/internal_to_external/update.py +0 -0
  94. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/tasks.py +0 -0
  95. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
  96. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/tests/conftest.py +0 -0
  97. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/tests/test_data.py +0 -0
  98. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +0 -0
  99. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/tests/test_utils.py +0 -0
  100. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/typing_informations.py +0 -0
  101. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/google/utils.py +0 -0
  102. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
  103. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/backend.py +0 -0
  104. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/msgraph.py +0 -0
  105. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/parser.py +0 -0
  106. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
  107. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +0 -0
  108. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +0 -0
  109. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +0 -0
  110. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +0 -0
  111. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +0 -0
  112. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +0 -0
  113. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/dynamic_preferences_registry.py +0 -0
  114. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/preferences.py +0 -0
  115. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/tasks.py +0 -0
  116. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/urls.py +0 -0
  117. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/utils.py +0 -0
  118. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/activity/views.py +0 -0
  119. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/admin.py +0 -0
  120. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/apps.py +0 -0
  121. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/dynamic_preferences_registry.py +0 -0
  122. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/management.py +0 -0
  123. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/tasks.py +0 -0
  124. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/synchronization/urls.py +0 -0
  125. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/templates/email/activity.html +0 -0
  126. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/templates/email/activity_report.html +0 -0
  127. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/templates/email/daily_summary.html +0 -0
  128. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/templates/email/global_daily_summary.html +0 -0
  129. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/__init__.py +0 -0
  130. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/accounts/__init__.py +0 -0
  131. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/accounts/test_models.py +0 -0
  132. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/accounts/test_viewsets.py +0 -0
  133. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/conftest.py +0 -0
  134. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/disable_signals.py +0 -0
  135. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/e2e/__init__.py +0 -0
  136. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/e2e/e2e_wbcrm_utility.py +0 -0
  137. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/e2e/test_e2e.py +0 -0
  138. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_assignee_methods.py +0 -0
  139. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_chartviewsets.py +0 -0
  140. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_dto.py +0 -0
  141. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_filters.py +0 -0
  142. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_models.py +0 -0
  143. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_recurrence.py +0 -0
  144. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_report.py +0 -0
  145. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_serializers.py +0 -0
  146. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_tasks.py +0 -0
  147. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/test_viewsets.py +0 -0
  148. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/tests/tests.py +0 -0
  149. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/typings.py +0 -0
  150. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/urls.py +0 -0
  151. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/__init__.py +0 -0
  152. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/accounts.py +0 -0
  153. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/buttons/__init__.py +0 -0
  154. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/buttons/accounts.py +0 -0
  155. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/buttons/activities.py +0 -0
  156. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/buttons/signals.py +0 -0
  157. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/display/__init__.py +0 -0
  158. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/display/accounts.py +0 -0
  159. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/display/activities.py +0 -0
  160. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/display/groups.py +0 -0
  161. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/display/products.py +0 -0
  162. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/endpoints/__init__.py +0 -0
  163. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/endpoints/accounts.py +0 -0
  164. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/endpoints/activities.py +0 -0
  165. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/endpoints/groups.py +0 -0
  166. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/endpoints/products.py +0 -0
  167. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/groups.py +0 -0
  168. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/menu/__init__.py +0 -0
  169. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/menu/accounts.py +0 -0
  170. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/menu/activities.py +0 -0
  171. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/menu/groups.py +0 -0
  172. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/menu/products.py +0 -0
  173. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/mixins.py +0 -0
  174. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/previews/__init__.py +0 -0
  175. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/previews/activities.py +0 -0
  176. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/products.py +0 -0
  177. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/recurrence.py +0 -0
  178. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/titles/__init__.py +0 -0
  179. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/titles/accounts.py +0 -0
  180. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/titles/activities.py +0 -0
  181. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/titles/products.py +0 -0
  182. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/viewsets/titles/utils.py +0 -0
  183. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/workflows/__init__.py +0 -0
  184. {wbcrm-1.56.0 → wbcrm-1.56.2}/wbcrm/workflows/assignee_methods.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbcrm
3
- Version: 1.56.0
3
+ Version: 1.56.2
4
4
  Summary: A workbench module that contains all the functionality related to a customer relationship management.
5
5
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
6
6
  Requires-Dist: django-eventtools==1.*
@@ -0,0 +1,24 @@
1
+ # Generated by Django 5.0.12 on 2025-10-15 13:24
2
+
3
+ import django.contrib.postgres.search
4
+ import django.contrib.postgres.indexes
5
+ from django.db import migrations
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('wbcrm', '0017_event'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='activity',
17
+ name='search_vector',
18
+ field=django.contrib.postgres.search.SearchVectorField(null=True),
19
+ ),
20
+ migrations.AddIndex(
21
+ model_name='activity',
22
+ index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='activity_sv_gin_idx'),
23
+ ),
24
+ ]
@@ -1,6 +1,6 @@
1
1
  import zoneinfo
2
2
  from contextlib import suppress
3
- from datetime import date, datetime, timedelta
3
+ from datetime import date, datetime, time, timedelta
4
4
  from typing import Any
5
5
 
6
6
  import arrow
@@ -8,8 +8,12 @@ import numpy as np
8
8
  from celery import shared_task
9
9
  from dateutil.rrule import rrulestr
10
10
  from django.conf import settings
11
+ from django.contrib.postgres.aggregates import StringAgg
12
+ from django.contrib.postgres.indexes import GinIndex
13
+ from django.contrib.postgres.search import SearchVector, SearchVectorField
11
14
  from django.db import models, transaction
12
- from django.db.models import Exists, OuterRef, Q
15
+ from django.db.backends.postgresql.psycopg_any import DateTimeTZRange
16
+ from django.db.models import Exists, OuterRef, Q, Value
13
17
  from django.db.models.query import QuerySet
14
18
  from django.db.models.signals import m2m_changed, post_delete, post_save
15
19
  from django.dispatch import receiver
@@ -21,6 +25,7 @@ from django_fsm import FSMField, transition
21
25
  from dynamic_preferences.registries import global_preferences_registry
22
26
  from ics.alarm import DisplayAlarm
23
27
  from psycopg.types.range import TimestamptzRange
28
+ from rest_framework.reverse import reverse
24
29
  from slugify import slugify
25
30
  from wbcore.contrib import workflow
26
31
  from wbcore.contrib.agenda.models import CalendarItem
@@ -51,6 +56,7 @@ from wbcore.utils.models import (
51
56
 
52
57
  from wbcrm.models.llm.activity_summaries import analyze_activity
53
58
  from wbcrm.models.recurrence import Recurrence
59
+ from wbcrm.signals import add_employee_activity_to_daily_brief
54
60
  from wbcrm.synchronization.activity.shortcuts import get_backend
55
61
  from wbcrm.typings import Activity as ActivityDTO
56
62
  from wbcrm.typings import ParticipantStatus as ParticipantStatusDTO
@@ -216,6 +222,9 @@ class Activity(Recurrence):
216
222
  class Meta:
217
223
  verbose_name = _("Activity")
218
224
  verbose_name_plural = _("Activities")
225
+ indexes = [
226
+ GinIndex(fields=["search_vector"], name="activity_sv_gin_idx"), # type: ignore
227
+ ]
219
228
  notification_types = [
220
229
  create_notification_type(
221
230
  "wbcrm.activity.participant",
@@ -233,9 +242,18 @@ class Activity(Recurrence):
233
242
  gettext("Notifies a user of a finished activity that can be reviewed."),
234
243
  ),
235
244
  create_notification_type(
236
- "wbcrm.activity.daily_summary",
245
+ "wbcrm.activity.global_daily_summary",
237
246
  gettext("Daily Summary"),
238
- gettext("Sends out a daily summary of a user's upcoming day."),
247
+ gettext("Sends out a the global employees daily activities report"),
248
+ web=False,
249
+ mobile=False,
250
+ email=True,
251
+ is_lock=True,
252
+ ),
253
+ create_notification_type(
254
+ "wbcrm.activity.daily_brief",
255
+ gettext("Daily Brief"),
256
+ gettext("Sends out a daily brief for the user's upcoming day."),
239
257
  web=False,
240
258
  mobile=False,
241
259
  email=True,
@@ -447,10 +465,25 @@ class Activity(Recurrence):
447
465
  verbose_name=_("Without Participating Company"),
448
466
  )
449
467
  metadata = models.JSONField(default=dict, blank=True)
468
+ search_vector = SearchVectorField(null=True)
450
469
 
451
470
  def __str__(self):
452
471
  return "%s" % (self.title,)
453
472
 
473
+ def update_search_vectors(self):
474
+ # Create the combined search vector manually
475
+ vector = (
476
+ SearchVector(Value(self.title), weight="A", config="english")
477
+ + SearchVector(Value(self.description), weight="B", config="english")
478
+ + SearchVector(Value(self.result), weight="B", config="english")
479
+ )
480
+ if self.id:
481
+ if participants_str := self.participants.aggregate(agg=StringAgg("computed_str", delimiter=" "))["agg"]:
482
+ vector += SearchVector(Value(participants_str), weight="C", config="english")
483
+ if companies_str := self.companies.aggregate(agg=StringAgg("computed_str", delimiter=" "))["agg"]:
484
+ vector += SearchVector(Value(companies_str), weight="C", config="english")
485
+ self.search_vector = vector
486
+
454
487
  def is_private_for_user(self, user) -> bool:
455
488
  return (
456
489
  self.visibility == CalendarItem.Visibility.PRIVATE
@@ -520,6 +553,7 @@ class Activity(Recurrence):
520
553
  ) # type
521
554
  self.is_cancelled = self.status == self.Status.CANCELLED
522
555
 
556
+ self.update_search_vectors()
523
557
  # Logic to be called after a save happens (e.g synchronization). We get the activity DTO before saving that we passed around in the signal
524
558
  super().save(*args, **kwargs)
525
559
 
@@ -851,6 +885,9 @@ def m2m_changed_participants(sender, instance: Activity, action: str, pk_set: se
851
885
  send_employee_notification.delay(instance.id, participant_id)
852
886
  else:
853
887
  rel.delete()
888
+ if action == "post_add":
889
+ instance.update_search_vectors()
890
+ Activity.objects.filter(id=instance.id).update(search_vector=instance.search_vector)
854
891
 
855
892
 
856
893
  @receiver(m2m_changed, sender=Activity.companies.through)
@@ -879,6 +916,10 @@ def m2m_changed_companies(sender, instance: Activity, action: str, pk_set: set[i
879
916
  activity=instance,
880
917
  ).save()
881
918
 
919
+ if action == "post_add":
920
+ instance.update_search_vectors()
921
+ Activity.objects.filter(id=instance.id).update(search_vector=instance.search_vector)
922
+
882
923
 
883
924
  @receiver(m2m_changed, sender=Activity.groups.through) # type: ignore
884
925
  def m2m_changed_groups(sender, instance: Activity, action, pk_set, **kwargs):
@@ -1345,3 +1386,37 @@ def add_employer_to_activities(eer_id: int):
1345
1386
  activity.entities.add(eer_obj.employer)
1346
1387
  if eer_obj.employer not in activity.companies.all():
1347
1388
  activity.companies.add(eer_obj.employer)
1389
+
1390
+
1391
+ @receiver(add_employee_activity_to_daily_brief, sender="directory.Person")
1392
+ def daily_activity_summary(sender, instance: Person, val_date: date, **kwargs) -> tuple[str, str] | None:
1393
+ tz_info = timezone.get_current_timezone()
1394
+ period = DateTimeTZRange(
1395
+ lower=datetime.combine(val_date, time(0, 0, 0), tzinfo=tz_info),
1396
+ upper=datetime.combine(val_date + timedelta(days=1), time(0, 0, 0), tzinfo=tz_info),
1397
+ )
1398
+
1399
+ # Get all the employee's activities from that day
1400
+ activity_qs: QuerySet[Activity] = (
1401
+ Activity.objects.exclude(status=Activity.Status.CANCELLED)
1402
+ .filter(period__overlap=period, participants=instance)
1403
+ .order_by("period__startswith")
1404
+ )
1405
+
1406
+ # Create the formatted activity dictionaries
1407
+ activity_list = []
1408
+ message = ""
1409
+ for activity in activity_qs:
1410
+ activity_list.append(
1411
+ {
1412
+ "type": activity.type.title,
1413
+ "title": activity.title,
1414
+ "start": activity.period.lower,
1415
+ "end": activity.period.upper,
1416
+ "endpoint": reverse("wbcrm:activity-detail", args=[activity.pk]),
1417
+ }
1418
+ )
1419
+ if activity_list:
1420
+ context = {"activities": activity_list}
1421
+ message = render_to_string("email/daily_summary.html", context)
1422
+ return "Daily Activity Summary", message
@@ -140,7 +140,7 @@ class ActivityCompanyThroughModelRepresentationSerializer(wb_serializers.Represe
140
140
 
141
141
  def to_representation(self, value):
142
142
  rep = super().to_representation(value)
143
- rep["id"] = value.company.id
143
+ rep["id"] = value.company_id
144
144
  return rep
145
145
 
146
146
  class Meta:
@@ -0,0 +1,4 @@
1
+ from django.db.models.signals import ModelSignal
2
+
3
+ # this signal gathers all activity report needed to be inserted into the daily brief
4
+ add_employee_activity_to_daily_brief = ModelSignal(use_caching=True)
@@ -4,7 +4,6 @@ import operator
4
4
  from functools import reduce
5
5
  from typing import Any
6
6
 
7
- from django.conf import settings
8
7
  from django.db import transaction
9
8
  from django.db.models import Q, QuerySet
10
9
  from django.http import HttpRequest, HttpResponse
@@ -47,8 +46,6 @@ class ActivityController:
47
46
  ]
48
47
 
49
48
  def __init__(self, backend: SyncBackend):
50
- if settings.DEBUG:
51
- raise ValueError("DEBUG Mode -> activity sync disabled")
52
49
  self.backend = backend()
53
50
 
54
51
  def _is_valid(self, activity_dto: ActivityDTO) -> bool:
@@ -0,0 +1,16 @@
1
+ from contextlib import suppress
2
+
3
+ from django.conf import settings
4
+ from dynamic_preferences.exceptions import NotFoundInRegistry
5
+ from dynamic_preferences.registries import global_preferences_registry
6
+ from wbcore.utils.importlib import import_from_dotted_path
7
+
8
+
9
+ def get_backend():
10
+ from wbcrm.synchronization.activity.controller import ActivityController
11
+
12
+ if not settings.DEBUG:
13
+ with suppress(NotFoundInRegistry):
14
+ if backend := global_preferences_registry.manager()["wbactivity_sync__sync_backend_calendar"]:
15
+ backend = import_from_dotted_path(backend)
16
+ return ActivityController(backend=backend)
@@ -5,6 +5,7 @@ from datetime import date, datetime, time, timedelta
5
5
 
6
6
  from celery import shared_task
7
7
  from django.contrib.auth import get_user_model
8
+ from django.db.backends.postgresql.psycopg_any import DateTimeTZRange
8
9
  from django.db.models import (
9
10
  F,
10
11
  FloatField,
@@ -23,12 +24,12 @@ from django.utils import timezone
23
24
  from django.utils.timezone import make_aware
24
25
  from django.utils.translation import gettext as _
25
26
  from dynamic_preferences.registries import global_preferences_registry
26
- from psycopg.types.range import TimestamptzRange
27
27
  from rest_framework.reverse import reverse
28
28
  from wbcore.contrib.directory.models import Company, Person
29
29
  from wbcore.contrib.notifications.dispatch import send_notification
30
30
 
31
31
  from wbcrm.models import Activity, ActivityType
32
+ from wbcrm.signals import add_employee_activity_to_daily_brief
32
33
 
33
34
  logger = logging.getLogger()
34
35
  User = get_user_model()
@@ -54,10 +55,10 @@ def notify(time_offset: int = 60, now: datetime | None = None):
54
55
  for reminder in reminder_choices:
55
56
  # get the reminder correspondance in minutes
56
57
  reminder_minutes = Activity.ReminderChoice.get_minutes_correspondance(reminder)
57
- reminder_range = TimestamptzRange(
58
+ reminder_range = DateTimeTZRange(
58
59
  now + timedelta(minutes=reminder_minutes),
59
60
  now + timedelta(minutes=reminder_minutes) + timedelta(seconds=time_offset),
60
- ) # type: ignore #ErrMsg: Expected no arguments to "TimestamptzRange" constructor
61
+ ) # type: ignore #ErrMsg: Expected no arguments to "DateTimeTZRange" constructor
61
62
  # get all incoming activity with same reminder that happen during the notify interval
62
63
  upcoming_occurence = base_queryset.filter(
63
64
  reminder_choice=reminder, period__startswith__contained_by=reminder_range
@@ -108,7 +109,7 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
108
109
  if not yesterday:
109
110
  yesterday = date.today() - timedelta(days=1)
110
111
  yesterday = datetime.combine(yesterday, time(0, 0, 0)) # we convert the date to datetime
111
- time_range = TimestamptzRange(make_aware(yesterday), make_aware(yesterday + timedelta(days=1))) # type: ignore #ErrMsg: Expected no arguments to "TimestamptzRange" constructor
112
+ time_range = DateTimeTZRange(make_aware(yesterday), make_aware(yesterday + timedelta(days=1))) # type: ignore #ErrMsg: Expected no arguments to "DateTimeTZRange" constructor
112
113
 
113
114
  # Create the list of all employees' activities for yesterday
114
115
  employees_list: list[Person] = list(Person.objects.filter_only_internal())
@@ -132,8 +133,8 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
132
133
  {
133
134
  "type": activity.type.title,
134
135
  "title": activity.title,
135
- "start": activity.period.lower, # type: ignore #ErrMsg: Cannot access member "lower" for type "TimestamptzRange"
136
- "end": activity.period.upper, # type: ignore #ErrMsg: Cannot access member "upper" for type "TimestamptzRange"
136
+ "start": activity.period.lower, # type: ignore #ErrMsg: Cannot access member "lower" for type "DateTimeTZRange"
137
+ "end": activity.period.upper, # type: ignore #ErrMsg: Cannot access member "upper" for type "DateTimeTZRange"
137
138
  "endpoint": reverse("wbcrm:activity-detail", args=[activity.pk]),
138
139
  }
139
140
  for activity in employees_activities
@@ -150,7 +151,7 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
150
151
  }
151
152
  message = render_to_string("email/global_daily_summary.html", context)
152
153
  send_notification(
153
- code="wbcrm.activity.daily_summary",
154
+ code="wbcrm.activity.global_daily_summary",
154
155
  title=_("Activity Summary {}").format(yesterday.strftime("%d.%m.%Y")),
155
156
  body=message,
156
157
  user=user,
@@ -158,50 +159,28 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
158
159
 
159
160
 
160
161
  @shared_task
161
- def todays_activity_summary(today: date | None = None):
162
- """Creates a summary of the daily upcoming activities for all employees
163
-
162
+ def daily_brief(today: date | None = None, **kwargs):
163
+ """Creates a summary of the daily brief for all internal employees
164
164
  Args:
165
165
  today (date | None, optional): Date of today. Defaults to None.
166
166
  """
167
167
 
168
168
  if not today:
169
169
  today = date.today()
170
- tz_info = timezone.get_current_timezone()
171
- today_range = TimestamptzRange(
172
- lower=datetime.combine(today, time(0, 0, 0)).replace(tzinfo=tz_info),
173
- upper=datetime.combine(today + timedelta(days=1), time(0, 0, 0).replace(tzinfo=tz_info)),
174
- )
175
-
176
170
  for employee in Person.objects.filter_only_internal():
177
- # Get all the employee's activities from that day
178
- activity_qs: QuerySet[Activity] = (
179
- Activity.objects.exclude(status=Activity.Status.CANCELLED)
180
- .filter(period__overlap=today_range, participants=employee)
181
- .order_by("period__startswith")
182
- )
183
-
184
- # Create the formatted activity dictionaries
185
- activity_list = []
186
- for activity in activity_qs:
187
- activity_list.append(
188
- {
189
- "type": activity.type.title,
190
- "title": activity.title,
191
- "start": activity.period.lower,
192
- "end": activity.period.upper,
193
- "endpoint": reverse("wbcrm:activity-detail", args=[activity.pk]),
194
- }
195
- )
196
-
197
- # Create the proper notification for each employee
198
- if activity_list and employee.primary_email_contact():
199
- context = {"activities": activity_list}
200
- message = render_to_string("email/daily_summary.html", context)
171
+ daily_brief = ""
172
+ for receiver, res in add_employee_activity_to_daily_brief.send( # noqa: B007
173
+ sender=Person, instance=employee, val_date=today, **kwargs
174
+ ):
175
+ if res:
176
+ title, html = res
177
+ daily_brief += f"<h2 text-align: center;>{title}</h2>\n<div style='margin-bottom: 1.5em; text-align: left;'>{html}</div>\n"
178
+
179
+ if daily_brief:
201
180
  send_notification(
202
- code="wbcrm.activity.daily_summary",
203
- title=_("Your Schedule for Today"),
204
- body=message,
181
+ code="wbcrm.activity.daily_brief",
182
+ title=_("Your Daily Brief"),
183
+ body=daily_brief,
205
184
  user=employee.user_account,
206
185
  )
207
186
 
@@ -95,7 +95,7 @@ class ActivityViewSet(RecurrenceModelViewSetMixin, CalendarItemViewSet):
95
95
  LIST_DOCUMENTATION = "wbcrm/markdown/documentation/activity.md"
96
96
 
97
97
  ordering = ["-edited", "id"]
98
- search_fields = ("title", "description", "result")
98
+ search_fields = ("search_vector",)
99
99
  serializer_class = crm_serializers.ActivityModelSerializer
100
100
  display_config_class = ActivityDisplay
101
101
  title_config_class = ActivityTitleConfig
@@ -180,11 +180,7 @@ class ActivityViewSet(RecurrenceModelViewSetMixin, CalendarItemViewSet):
180
180
  return (
181
181
  Activity.get_activities_for_user(user, base_qs=super().get_queryset())
182
182
  .select_related("latest_reviewer", "type")
183
- .prefetch_related(
184
- "groups",
185
- "participants",
186
- "companies",
187
- )
183
+ .prefetch_related("groups", "participants", "companies", "activity_companies")
188
184
  )
189
185
 
190
186
  def add_messages(self, request, instance: Activity | None = None, **kwargs):
@@ -1,14 +0,0 @@
1
- from contextlib import suppress
2
-
3
- from dynamic_preferences.exceptions import NotFoundInRegistry
4
- from dynamic_preferences.registries import global_preferences_registry
5
- from wbcore.utils.importlib import import_from_dotted_path
6
-
7
-
8
- def get_backend():
9
- from wbcrm.synchronization.activity.controller import ActivityController
10
-
11
- with suppress(NotFoundInRegistry):
12
- if backend := global_preferences_registry.manager()["wbactivity_sync__sync_backend_calendar"]:
13
- backend = import_from_dotted_path(backend)
14
- return ActivityController(backend=backend)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes