wbcrm 1.56.1__tar.gz → 1.56.3__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 (183) hide show
  1. {wbcrm-1.56.1 → wbcrm-1.56.3}/PKG-INFO +1 -1
  2. wbcrm-1.56.3/wbcrm/migrations/0018_activity_search_vector.py +24 -0
  3. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/activities.py +76 -4
  4. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/activities.py +1 -1
  5. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/controller.py +0 -3
  6. wbcrm-1.56.3/wbcrm/synchronization/activity/shortcuts.py +16 -0
  7. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tasks.py +7 -56
  8. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/activities.py +2 -6
  9. wbcrm-1.56.1/wbcrm/synchronization/activity/shortcuts.py +0 -14
  10. {wbcrm-1.56.1 → wbcrm-1.56.3}/.gitignore +0 -0
  11. {wbcrm-1.56.1 → wbcrm-1.56.3}/pyproject.toml +0 -0
  12. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/__init__.py +0 -0
  13. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/admin/__init__.py +0 -0
  14. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/admin/accounts.py +0 -0
  15. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/admin/activities.py +0 -0
  16. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/admin/events.py +0 -0
  17. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/admin/groups.py +0 -0
  18. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/admin/products.py +0 -0
  19. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/apps.py +0 -0
  20. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/configurations/__init__.py +0 -0
  21. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/configurations/base.py +0 -0
  22. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/dynamic_preferences_registry.py +0 -0
  23. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/factories/__init__.py +0 -0
  24. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/factories/accounts.py +0 -0
  25. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/factories/activities.py +0 -0
  26. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/factories/groups.py +0 -0
  27. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/factories/products.py +0 -0
  28. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/filters/__init__.py +0 -0
  29. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/filters/accounts.py +0 -0
  30. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/filters/activities.py +0 -0
  31. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/filters/groups.py +0 -0
  32. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/filters/products.py +0 -0
  33. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/filters/signals.py +0 -0
  34. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/fixtures/wbcrm.json +0 -0
  35. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/kpi_handlers/activities.py +0 -0
  36. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/de/LC_MESSAGES/django.mo +0 -0
  37. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/de/LC_MESSAGES/django.po +0 -0
  38. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/de/LC_MESSAGES/django.po.translated +0 -0
  39. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/en/LC_MESSAGES/django.mo +0 -0
  40. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/en/LC_MESSAGES/django.po +0 -0
  41. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/fr/LC_MESSAGES/django.mo +0 -0
  42. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/locale/fr/LC_MESSAGES/django.po +0 -0
  43. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +0 -0
  44. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0002_alter_activity_repeat_choice.py +0 -0
  45. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0003_remove_activity_external_id_and_more.py +0 -0
  46. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0004_alter_activity_status.py +0 -0
  47. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +0 -0
  48. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0006_alter_activity_location.py +0 -0
  49. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0007_alter_account_status.py +0 -0
  50. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0008_alter_activity_options.py +0 -0
  51. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0009_alter_account_is_public.py +0 -0
  52. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0010_alter_account_reference_id.py +0 -0
  53. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0011_activity_summary.py +0 -0
  54. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0012_alter_activity_summary.py +0 -0
  55. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +0 -0
  56. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0014_alter_account_relationship_status.py +0 -0
  57. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0015_alter_activity_type.py +0 -0
  58. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0016_auto_20241205_1015.py +0 -0
  59. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/0017_event.py +0 -0
  60. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/migrations/__init__.py +0 -0
  61. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/__init__.py +0 -0
  62. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/accounts.py +0 -0
  63. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/events.py +0 -0
  64. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/groups.py +0 -0
  65. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/llm/activity_summaries.py +0 -0
  66. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/llm/analyze_relationship.py +0 -0
  67. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/products.py +0 -0
  68. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/models/recurrence.py +0 -0
  69. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/preferences.py +0 -0
  70. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/report/activity_report.py +0 -0
  71. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/__init__.py +0 -0
  72. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/accounts.py +0 -0
  73. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/groups.py +0 -0
  74. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/products.py +0 -0
  75. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/recurrence.py +0 -0
  76. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/serializers/signals.py +0 -0
  77. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/static/wbcrm/markdown/documentation/activity.md +0 -0
  78. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/static/wbcrm/markdown/documentation/activitytype.md +0 -0
  79. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/static/wbcrm/markdown/documentation/group.md +0 -0
  80. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/static/wbcrm/markdown/documentation/product.md +0 -0
  81. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/__init__.py +0 -0
  82. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/__init__.py +0 -0
  83. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/admin.py +0 -0
  84. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backend.py +0 -0
  85. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/__init__.py +0 -0
  86. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/__init__.py +0 -0
  87. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +0 -0
  88. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +0 -0
  89. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/create.py +0 -0
  90. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/delete.py +0 -0
  91. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/update.py +0 -0
  92. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/request_utils/internal_to_external/update.py +0 -0
  93. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/tasks.py +0 -0
  94. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
  95. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/tests/conftest.py +0 -0
  96. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/tests/test_data.py +0 -0
  97. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +0 -0
  98. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/tests/test_utils.py +0 -0
  99. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/typing_informations.py +0 -0
  100. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/google/utils.py +0 -0
  101. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
  102. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/backend.py +0 -0
  103. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/msgraph.py +0 -0
  104. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/parser.py +0 -0
  105. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
  106. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +0 -0
  107. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +0 -0
  108. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +0 -0
  109. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +0 -0
  110. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +0 -0
  111. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +0 -0
  112. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/dynamic_preferences_registry.py +0 -0
  113. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/preferences.py +0 -0
  114. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/tasks.py +0 -0
  115. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/urls.py +0 -0
  116. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/utils.py +0 -0
  117. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/activity/views.py +0 -0
  118. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/admin.py +0 -0
  119. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/apps.py +0 -0
  120. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/dynamic_preferences_registry.py +0 -0
  121. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/management.py +0 -0
  122. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/tasks.py +0 -0
  123. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/synchronization/urls.py +0 -0
  124. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/templates/email/activity.html +0 -0
  125. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/templates/email/activity_report.html +0 -0
  126. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/templates/email/daily_summary.html +0 -0
  127. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/templates/email/global_daily_summary.html +0 -0
  128. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/__init__.py +0 -0
  129. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/accounts/__init__.py +0 -0
  130. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/accounts/test_models.py +0 -0
  131. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/accounts/test_viewsets.py +0 -0
  132. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/conftest.py +0 -0
  133. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/disable_signals.py +0 -0
  134. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/e2e/__init__.py +0 -0
  135. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/e2e/e2e_wbcrm_utility.py +0 -0
  136. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/e2e/test_e2e.py +0 -0
  137. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_assignee_methods.py +0 -0
  138. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_chartviewsets.py +0 -0
  139. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_dto.py +0 -0
  140. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_filters.py +0 -0
  141. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_models.py +0 -0
  142. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_recurrence.py +0 -0
  143. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_report.py +0 -0
  144. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_serializers.py +0 -0
  145. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_tasks.py +0 -0
  146. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/test_viewsets.py +0 -0
  147. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/tests/tests.py +0 -0
  148. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/typings.py +0 -0
  149. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/urls.py +0 -0
  150. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/__init__.py +0 -0
  151. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/accounts.py +0 -0
  152. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/buttons/__init__.py +0 -0
  153. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/buttons/accounts.py +0 -0
  154. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/buttons/activities.py +0 -0
  155. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/buttons/signals.py +0 -0
  156. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/display/__init__.py +0 -0
  157. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/display/accounts.py +0 -0
  158. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/display/activities.py +0 -0
  159. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/display/groups.py +0 -0
  160. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/display/products.py +0 -0
  161. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/endpoints/__init__.py +0 -0
  162. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/endpoints/accounts.py +0 -0
  163. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/endpoints/activities.py +0 -0
  164. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/endpoints/groups.py +0 -0
  165. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/endpoints/products.py +0 -0
  166. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/groups.py +0 -0
  167. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/menu/__init__.py +0 -0
  168. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/menu/accounts.py +0 -0
  169. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/menu/activities.py +0 -0
  170. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/menu/groups.py +0 -0
  171. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/menu/products.py +0 -0
  172. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/mixins.py +0 -0
  173. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/previews/__init__.py +0 -0
  174. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/previews/activities.py +0 -0
  175. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/products.py +0 -0
  176. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/recurrence.py +0 -0
  177. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/titles/__init__.py +0 -0
  178. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/titles/accounts.py +0 -0
  179. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/titles/activities.py +0 -0
  180. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/titles/products.py +0 -0
  181. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/viewsets/titles/utils.py +0 -0
  182. {wbcrm-1.56.1 → wbcrm-1.56.3}/wbcrm/workflows/__init__.py +0 -0
  183. {wbcrm-1.56.1 → wbcrm-1.56.3}/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.1
3
+ Version: 1.56.3
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
@@ -48,6 +53,7 @@ from wbcore.utils.models import (
48
53
  ComplexToStringMixin,
49
54
  DefaultMixin,
50
55
  )
56
+ from wbhuman_resources.signals import add_employee_activity_to_daily_brief
51
57
 
52
58
  from wbcrm.models.llm.activity_summaries import analyze_activity
53
59
  from wbcrm.models.recurrence import Recurrence
@@ -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,34 @@ 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
+ for activity in activity_qs:
1409
+ activity_list.append(
1410
+ {
1411
+ "type": activity.type.title,
1412
+ "title": activity.title,
1413
+ "start": activity.period.lower,
1414
+ "end": activity.period.upper,
1415
+ "endpoint": reverse("wbcrm:activity-detail", args=[activity.pk]),
1416
+ }
1417
+ )
1418
+ if activity_list:
1419
+ return "Daily Activity Summary", render_to_string("email/daily_summary.html", {"activities": activity_list})
@@ -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:
@@ -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,7 +24,6 @@ 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
@@ -54,10 +54,10 @@ def notify(time_offset: int = 60, now: datetime | None = None):
54
54
  for reminder in reminder_choices:
55
55
  # get the reminder correspondance in minutes
56
56
  reminder_minutes = Activity.ReminderChoice.get_minutes_correspondance(reminder)
57
- reminder_range = TimestamptzRange(
57
+ reminder_range = DateTimeTZRange(
58
58
  now + timedelta(minutes=reminder_minutes),
59
59
  now + timedelta(minutes=reminder_minutes) + timedelta(seconds=time_offset),
60
- ) # type: ignore #ErrMsg: Expected no arguments to "TimestamptzRange" constructor
60
+ ) # type: ignore #ErrMsg: Expected no arguments to "DateTimeTZRange" constructor
61
61
  # get all incoming activity with same reminder that happen during the notify interval
62
62
  upcoming_occurence = base_queryset.filter(
63
63
  reminder_choice=reminder, period__startswith__contained_by=reminder_range
@@ -108,7 +108,7 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
108
108
  if not yesterday:
109
109
  yesterday = date.today() - timedelta(days=1)
110
110
  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
111
+ time_range = DateTimeTZRange(make_aware(yesterday), make_aware(yesterday + timedelta(days=1))) # type: ignore #ErrMsg: Expected no arguments to "DateTimeTZRange" constructor
112
112
 
113
113
  # Create the list of all employees' activities for yesterday
114
114
  employees_list: list[Person] = list(Person.objects.filter_only_internal())
@@ -132,8 +132,8 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
132
132
  {
133
133
  "type": activity.type.title,
134
134
  "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"
135
+ "start": activity.period.lower, # type: ignore #ErrMsg: Cannot access member "lower" for type "DateTimeTZRange"
136
+ "end": activity.period.upper, # type: ignore #ErrMsg: Cannot access member "upper" for type "DateTimeTZRange"
137
137
  "endpoint": reverse("wbcrm:activity-detail", args=[activity.pk]),
138
138
  }
139
139
  for activity in employees_activities
@@ -150,62 +150,13 @@ def yesterdays_activity_summary(yesterday: date | None = None, report_receiver_u
150
150
  }
151
151
  message = render_to_string("email/global_daily_summary.html", context)
152
152
  send_notification(
153
- code="wbcrm.activity.daily_summary",
153
+ code="wbcrm.activity.global_daily_summary",
154
154
  title=_("Activity Summary {}").format(yesterday.strftime("%d.%m.%Y")),
155
155
  body=message,
156
156
  user=user,
157
157
  )
158
158
 
159
159
 
160
- @shared_task
161
- def todays_activity_summary(today: date | None = None):
162
- """Creates a summary of the daily upcoming activities for all employees
163
-
164
- Args:
165
- today (date | None, optional): Date of today. Defaults to None.
166
- """
167
-
168
- if not today:
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
- 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)
201
- send_notification(
202
- code="wbcrm.activity.daily_summary",
203
- title=_("Your Schedule for Today"),
204
- body=message,
205
- user=employee.user_account,
206
- )
207
-
208
-
209
160
  @shared_task
210
161
  def finish(now: datetime | None = None):
211
162
  """Cron task running every X Seconds. Checks all activities that have finished and sends a reminder to review the activity to the assigned person.
@@ -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