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.
- wbcrm/__init__.py +1 -0
- wbcrm/admin/__init__.py +5 -0
- wbcrm/admin/accounts.py +60 -0
- wbcrm/admin/activities.py +104 -0
- wbcrm/admin/events.py +43 -0
- wbcrm/admin/groups.py +8 -0
- wbcrm/admin/products.py +9 -0
- wbcrm/apps.py +5 -0
- wbcrm/configurations/__init__.py +1 -0
- wbcrm/configurations/base.py +16 -0
- wbcrm/dynamic_preferences_registry.py +38 -0
- wbcrm/factories/__init__.py +14 -0
- wbcrm/factories/accounts.py +57 -0
- wbcrm/factories/activities.py +124 -0
- wbcrm/factories/groups.py +24 -0
- wbcrm/factories/products.py +11 -0
- wbcrm/filters/__init__.py +10 -0
- wbcrm/filters/accounts.py +80 -0
- wbcrm/filters/activities.py +204 -0
- wbcrm/filters/groups.py +21 -0
- wbcrm/filters/products.py +38 -0
- wbcrm/filters/signals.py +95 -0
- wbcrm/fixtures/wbcrm.json +1215 -0
- wbcrm/kpi_handlers/activities.py +171 -0
- wbcrm/locale/de/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/de/LC_MESSAGES/django.po +1557 -0
- wbcrm/locale/de/LC_MESSAGES/django.po.translated +1630 -0
- wbcrm/locale/en/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/en/LC_MESSAGES/django.po +1466 -0
- wbcrm/locale/fr/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/fr/LC_MESSAGES/django.po +1467 -0
- wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
- wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
- wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
- wbcrm/migrations/0004_alter_activity_status.py +28 -0
- wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
- wbcrm/migrations/0006_alter_activity_location.py +17 -0
- wbcrm/migrations/0007_alter_account_status.py +23 -0
- wbcrm/migrations/0008_alter_activity_options.py +16 -0
- wbcrm/migrations/0009_alter_account_is_public.py +19 -0
- wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
- wbcrm/migrations/0011_activity_summary.py +22 -0
- wbcrm/migrations/0012_alter_activity_summary.py +17 -0
- wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
- wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
- wbcrm/migrations/0015_alter_activity_type.py +23 -0
- wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
- wbcrm/migrations/0017_event.py +40 -0
- wbcrm/migrations/0018_activity_search_vector.py +24 -0
- wbcrm/migrations/__init__.py +0 -0
- wbcrm/models/__init__.py +5 -0
- wbcrm/models/accounts.py +648 -0
- wbcrm/models/activities.py +1419 -0
- wbcrm/models/events.py +15 -0
- wbcrm/models/groups.py +119 -0
- wbcrm/models/llm/activity_summaries.py +41 -0
- wbcrm/models/llm/analyze_relationship.py +50 -0
- wbcrm/models/products.py +86 -0
- wbcrm/models/recurrence.py +280 -0
- wbcrm/preferences.py +13 -0
- wbcrm/report/activity_report.py +110 -0
- wbcrm/serializers/__init__.py +23 -0
- wbcrm/serializers/accounts.py +141 -0
- wbcrm/serializers/activities.py +525 -0
- wbcrm/serializers/groups.py +30 -0
- wbcrm/serializers/products.py +58 -0
- wbcrm/serializers/recurrence.py +91 -0
- wbcrm/serializers/signals.py +71 -0
- wbcrm/static/wbcrm/markdown/documentation/activity.md +86 -0
- wbcrm/static/wbcrm/markdown/documentation/activitytype.md +20 -0
- wbcrm/static/wbcrm/markdown/documentation/group.md +2 -0
- wbcrm/static/wbcrm/markdown/documentation/product.md +11 -0
- wbcrm/synchronization/__init__.py +0 -0
- wbcrm/synchronization/activity/__init__.py +0 -0
- wbcrm/synchronization/activity/admin.py +73 -0
- wbcrm/synchronization/activity/backend.py +214 -0
- wbcrm/synchronization/activity/backends/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
- wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +406 -0
- wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/create.py +75 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/delete.py +78 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/update.py +155 -0
- wbcrm/synchronization/activity/backends/google/request_utils/internal_to_external/update.py +181 -0
- wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
- wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
- wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
- wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
- wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
- wbcrm/synchronization/activity/backends/google/utils.py +217 -0
- wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/backend.py +593 -0
- wbcrm/synchronization/activity/backends/outlook/msgraph.py +436 -0
- wbcrm/synchronization/activity/backends/outlook/parser.py +432 -0
- wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +118 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +274 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +249 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +174 -0
- wbcrm/synchronization/activity/controller.py +627 -0
- wbcrm/synchronization/activity/dynamic_preferences_registry.py +119 -0
- wbcrm/synchronization/activity/preferences.py +27 -0
- wbcrm/synchronization/activity/shortcuts.py +16 -0
- wbcrm/synchronization/activity/tasks.py +21 -0
- wbcrm/synchronization/activity/urls.py +7 -0
- wbcrm/synchronization/activity/utils.py +46 -0
- wbcrm/synchronization/activity/views.py +41 -0
- wbcrm/synchronization/admin.py +1 -0
- wbcrm/synchronization/apps.py +14 -0
- wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
- wbcrm/synchronization/management.py +36 -0
- wbcrm/synchronization/tasks.py +1 -0
- wbcrm/synchronization/urls.py +5 -0
- wbcrm/tasks.py +264 -0
- wbcrm/templates/email/activity.html +98 -0
- wbcrm/templates/email/activity_report.html +6 -0
- wbcrm/templates/email/daily_summary.html +72 -0
- wbcrm/templates/email/global_daily_summary.html +85 -0
- wbcrm/tests/__init__.py +0 -0
- wbcrm/tests/accounts/__init__.py +0 -0
- wbcrm/tests/accounts/test_models.py +393 -0
- wbcrm/tests/accounts/test_viewsets.py +88 -0
- wbcrm/tests/conftest.py +76 -0
- wbcrm/tests/disable_signals.py +62 -0
- wbcrm/tests/e2e/__init__.py +1 -0
- wbcrm/tests/e2e/e2e_wbcrm_utility.py +83 -0
- wbcrm/tests/e2e/test_e2e.py +370 -0
- wbcrm/tests/test_assignee_methods.py +40 -0
- wbcrm/tests/test_chartviewsets.py +112 -0
- wbcrm/tests/test_dto.py +64 -0
- wbcrm/tests/test_filters.py +52 -0
- wbcrm/tests/test_models.py +217 -0
- wbcrm/tests/test_recurrence.py +292 -0
- wbcrm/tests/test_report.py +21 -0
- wbcrm/tests/test_serializers.py +171 -0
- wbcrm/tests/test_tasks.py +95 -0
- wbcrm/tests/test_viewsets.py +967 -0
- wbcrm/tests/tests.py +121 -0
- wbcrm/typings.py +109 -0
- wbcrm/urls.py +67 -0
- wbcrm/viewsets/__init__.py +22 -0
- wbcrm/viewsets/accounts.py +122 -0
- wbcrm/viewsets/activities.py +341 -0
- wbcrm/viewsets/buttons/__init__.py +7 -0
- wbcrm/viewsets/buttons/accounts.py +27 -0
- wbcrm/viewsets/buttons/activities.py +89 -0
- wbcrm/viewsets/buttons/signals.py +17 -0
- wbcrm/viewsets/display/__init__.py +12 -0
- wbcrm/viewsets/display/accounts.py +110 -0
- wbcrm/viewsets/display/activities.py +444 -0
- wbcrm/viewsets/display/groups.py +22 -0
- wbcrm/viewsets/display/products.py +105 -0
- wbcrm/viewsets/endpoints/__init__.py +8 -0
- wbcrm/viewsets/endpoints/accounts.py +25 -0
- wbcrm/viewsets/endpoints/activities.py +30 -0
- wbcrm/viewsets/endpoints/groups.py +7 -0
- wbcrm/viewsets/endpoints/products.py +9 -0
- wbcrm/viewsets/groups.py +38 -0
- wbcrm/viewsets/menu/__init__.py +8 -0
- wbcrm/viewsets/menu/accounts.py +18 -0
- wbcrm/viewsets/menu/activities.py +49 -0
- wbcrm/viewsets/menu/groups.py +16 -0
- wbcrm/viewsets/menu/products.py +20 -0
- wbcrm/viewsets/mixins.py +35 -0
- wbcrm/viewsets/previews/__init__.py +1 -0
- wbcrm/viewsets/previews/activities.py +10 -0
- wbcrm/viewsets/products.py +57 -0
- wbcrm/viewsets/recurrence.py +27 -0
- wbcrm/viewsets/titles/__init__.py +13 -0
- wbcrm/viewsets/titles/accounts.py +23 -0
- wbcrm/viewsets/titles/activities.py +61 -0
- wbcrm/viewsets/titles/products.py +13 -0
- wbcrm/viewsets/titles/utils.py +46 -0
- wbcrm/workflows/__init__.py +1 -0
- wbcrm/workflows/assignee_methods.py +25 -0
- wbcrm-1.56.8.dist-info/METADATA +11 -0
- wbcrm-1.56.8.dist-info/RECORD +182 -0
- wbcrm-1.56.8.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import django_filters
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from django.db.models import Exists, OuterRef, Q
|
|
6
|
+
from django.db.models.query import QuerySet
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
from django.utils.translation import gettext_lazy
|
|
9
|
+
from django.utils.translation import gettext_lazy as _
|
|
10
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
11
|
+
from psycopg.types.range import DateRange, TimestamptzRange
|
|
12
|
+
from wbcore import filters as wb_filters
|
|
13
|
+
from wbcore.contrib.agenda.filters import CalendarItemPeriodBaseFilterSet
|
|
14
|
+
from wbcore.contrib.agenda.models import CalendarItem
|
|
15
|
+
from wbcore.contrib.directory.models import CustomerStatus
|
|
16
|
+
from wbcore.contrib.directory.models.entries import Company, Person
|
|
17
|
+
from wbcore.filters.lookups import ALL_TEXT_LOOKUPS
|
|
18
|
+
|
|
19
|
+
from wbcrm.models import Activity, ActivityParticipant, ActivityType, Group
|
|
20
|
+
from wbcrm.models.activities import ActivityCompanyThroughModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_employee_filter_params(request, view):
|
|
24
|
+
if employer_id := global_preferences_registry.manager()["directory__main_company"]:
|
|
25
|
+
return {"employers": employer_id}
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ActivityBaseFilterSet(CalendarItemPeriodBaseFilterSet):
|
|
30
|
+
type = wb_filters.ModelMultipleChoiceFilter(
|
|
31
|
+
label=gettext_lazy("Types"),
|
|
32
|
+
queryset=ActivityType.objects.all(),
|
|
33
|
+
endpoint=ActivityType.get_representation_endpoint(),
|
|
34
|
+
value_key=ActivityType.get_representation_value_key(),
|
|
35
|
+
label_key=ActivityType.get_representation_label_key(),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
visibility = wb_filters.MultipleChoiceFilter(
|
|
39
|
+
choices=CalendarItem.Visibility.choices,
|
|
40
|
+
label=gettext_lazy("Visibility"),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
participants = wb_filters.ModelMultipleChoiceFilter(
|
|
44
|
+
label=gettext_lazy("Participants"),
|
|
45
|
+
queryset=Person.objects.all(),
|
|
46
|
+
endpoint=Person.get_representation_endpoint(),
|
|
47
|
+
value_key=Person.get_representation_value_key(),
|
|
48
|
+
label_key=Person.get_representation_label_key(),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
companies = wb_filters.ModelMultipleChoiceFilter(
|
|
52
|
+
label=gettext_lazy("Companies"),
|
|
53
|
+
queryset=Company.objects.all(),
|
|
54
|
+
endpoint=Company.get_representation_endpoint(),
|
|
55
|
+
value_key=Company.get_representation_value_key(),
|
|
56
|
+
label_key=Company.get_representation_label_key(),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
groups = wb_filters.ModelMultipleChoiceFilter(
|
|
60
|
+
label=gettext_lazy("Groups"),
|
|
61
|
+
queryset=Group.objects.all(),
|
|
62
|
+
endpoint=Group.get_representation_endpoint(),
|
|
63
|
+
value_key=Group.get_representation_value_key(),
|
|
64
|
+
label_key=Group.get_representation_label_key(),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ActivityFilter(ActivityBaseFilterSet):
|
|
69
|
+
clients_of = wb_filters.ModelChoiceFilter(
|
|
70
|
+
label=gettext_lazy("Clients of"),
|
|
71
|
+
queryset=Person.objects.all(),
|
|
72
|
+
endpoint=Person.get_representation_endpoint(),
|
|
73
|
+
value_key=Person.get_representation_value_key(),
|
|
74
|
+
label_key=Person.get_representation_label_key(),
|
|
75
|
+
method="filter_clients_of",
|
|
76
|
+
filter_params=get_employee_filter_params,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
latest_reviewer = wb_filters.ModelMultipleChoiceFilter(
|
|
80
|
+
label=gettext_lazy("Latest Reviewers"),
|
|
81
|
+
queryset=Person.objects.all(),
|
|
82
|
+
endpoint=Person.get_representation_endpoint(),
|
|
83
|
+
value_key=Person.get_representation_value_key(),
|
|
84
|
+
label_key=Person.get_representation_label_key(),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
importance = wb_filters.MultipleChoiceFilter(
|
|
88
|
+
label=gettext_lazy("Importance"), choices=Activity.Importance.choices, widget=django_filters.widgets.CSVWidget
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
created = wb_filters.DateTimeRangeFilter(label=gettext_lazy("Created"))
|
|
92
|
+
edited = wb_filters.DateTimeRangeFilter(label=gettext_lazy("Edited"))
|
|
93
|
+
|
|
94
|
+
only_recent = wb_filters.BooleanFilter(label=gettext_lazy("Only Recent"), method="boolean_only_recent")
|
|
95
|
+
|
|
96
|
+
customer_status = wb_filters.ModelChoiceFilter(
|
|
97
|
+
label=gettext_lazy("Customer Status"),
|
|
98
|
+
queryset=CustomerStatus.objects.all(),
|
|
99
|
+
endpoint=CustomerStatus.get_representation_endpoint(),
|
|
100
|
+
value_key=CustomerStatus.get_representation_value_key(),
|
|
101
|
+
label_key=CustomerStatus.get_representation_label_key(),
|
|
102
|
+
method="filter_customer_status",
|
|
103
|
+
help_text="Filter activities based on the company customer status at the activity creation time",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def boolean_only_recent(self, queryset: QuerySet[Activity], name, value: bool | None) -> QuerySet[Activity]:
|
|
107
|
+
if value:
|
|
108
|
+
today = date.today()
|
|
109
|
+
next_week = today + timedelta(days=7)
|
|
110
|
+
last_month = today - timedelta(days=30)
|
|
111
|
+
return queryset.filter(period__overlap=TimestamptzRange(last_month, next_week))
|
|
112
|
+
return queryset
|
|
113
|
+
|
|
114
|
+
def filter_clients_of(self, queryset, name, value):
|
|
115
|
+
if value:
|
|
116
|
+
user = self.request.user
|
|
117
|
+
return Activity.objects.filter(
|
|
118
|
+
(Q(participants__relationship_managers=value) | Q(companies__relationship_managers=value))
|
|
119
|
+
& (
|
|
120
|
+
Q(visibility=CalendarItem.Visibility.PUBLIC)
|
|
121
|
+
| (
|
|
122
|
+
Q(visibility=CalendarItem.Visibility.CONFIDENTIAL) # TODO move that to a queryset method
|
|
123
|
+
& Exists(
|
|
124
|
+
get_user_model().objects.filter(
|
|
125
|
+
Q(id=user.id)
|
|
126
|
+
& (
|
|
127
|
+
Q(groups__permissions__codename="administrate_confidential_items")
|
|
128
|
+
| Q(user_permissions__codename="administrate_confidential_items")
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
| Q(
|
|
134
|
+
Exists(
|
|
135
|
+
ActivityParticipant.objects.filter(activity_id=OuterRef("pk"), participant=user.profile)
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
| Q(assigned_to=user.profile)
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return queryset
|
|
142
|
+
|
|
143
|
+
def filter_customer_status(self, queryset, name, value):
|
|
144
|
+
if value:
|
|
145
|
+
rel = ActivityCompanyThroughModel.objects.filter(customer_status=value)
|
|
146
|
+
return queryset.filter(id__in=rel.values("activity"))
|
|
147
|
+
return queryset
|
|
148
|
+
|
|
149
|
+
class Meta:
|
|
150
|
+
model = Activity
|
|
151
|
+
fields = {
|
|
152
|
+
"status": ["exact"],
|
|
153
|
+
"repeat_choice": ["exact"],
|
|
154
|
+
"title": ALL_TEXT_LOOKUPS,
|
|
155
|
+
"result": ALL_TEXT_LOOKUPS,
|
|
156
|
+
"description": ALL_TEXT_LOOKUPS,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def default_activitychart_get_params(*args, **kwargs):
|
|
161
|
+
current_last_week_date_start = timezone.now().date() - timedelta(days=7)
|
|
162
|
+
current_next_week_date_end = timezone.now().date() + timedelta(days=7)
|
|
163
|
+
return DateRange(current_last_week_date_start, current_next_week_date_end)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ActivityChartFilter(ActivityBaseFilterSet):
|
|
167
|
+
status = wb_filters.MultipleChoiceFilter(
|
|
168
|
+
label=gettext_lazy("Status"), choices=Activity.Status.choices, widget=django_filters.widgets.CSVWidget
|
|
169
|
+
)
|
|
170
|
+
period = wb_filters.DateTimeRangeFilter(label="Period", required=True, initial=default_activitychart_get_params)
|
|
171
|
+
|
|
172
|
+
class Meta:
|
|
173
|
+
model = Activity
|
|
174
|
+
fields = {}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class ActivityTypeFilter(wb_filters.FilterSet):
|
|
178
|
+
score = wb_filters.MultipleChoiceFilter(
|
|
179
|
+
label=_("Multipliers"), choices=ActivityType.Score.choices[:3], widget=django_filters.widgets.CSVWidget
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
class Meta:
|
|
183
|
+
model = ActivityType
|
|
184
|
+
fields = {
|
|
185
|
+
"title": ["exact", "icontains"],
|
|
186
|
+
"color": ["exact", "icontains"],
|
|
187
|
+
"default": ["exact"],
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ActivityParticipantFilter(wb_filters.FilterSet):
|
|
192
|
+
is_occupied_filter = wb_filters.BooleanFilter(
|
|
193
|
+
label=gettext_lazy("Is occupied by different activity"), method="boolean_is_occupied"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def boolean_is_occupied(self, queryset, name, value):
|
|
197
|
+
if value is None:
|
|
198
|
+
return queryset
|
|
199
|
+
else:
|
|
200
|
+
return queryset.filter(is_occupied=value)
|
|
201
|
+
|
|
202
|
+
class Meta:
|
|
203
|
+
model = ActivityParticipant
|
|
204
|
+
fields = {}
|
wbcrm/filters/groups.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from wbcore import filters as wb_filters
|
|
3
|
+
from wbcore.contrib.directory.models import Entry
|
|
4
|
+
|
|
5
|
+
from wbcrm.models import Group
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GroupFilter(wb_filters.FilterSet):
|
|
9
|
+
members = wb_filters.ModelMultipleChoiceFilter(
|
|
10
|
+
label=_("Members"),
|
|
11
|
+
queryset=Entry.objects.all(),
|
|
12
|
+
endpoint=Entry.get_representation_endpoint(),
|
|
13
|
+
value_key=Entry.get_representation_value_key(),
|
|
14
|
+
label_key=Entry.get_representation_label_key(),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
class Meta:
|
|
18
|
+
model = Group
|
|
19
|
+
fields = {
|
|
20
|
+
"title": ["exact", "icontains"],
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
from wbcore import filters as wb_filters
|
|
3
|
+
from wbcore.contrib.directory.models import Company
|
|
4
|
+
|
|
5
|
+
from wbcrm.models import Product, ProductCompanyRelationship
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProductFilterSet(wb_filters.FilterSet):
|
|
9
|
+
prospects = wb_filters.ModelMultipleChoiceFilter(
|
|
10
|
+
label=_("Prospects"),
|
|
11
|
+
queryset=Company.objects.all(),
|
|
12
|
+
endpoint=Company.get_representation_endpoint(),
|
|
13
|
+
value_key=Company.get_representation_value_key(),
|
|
14
|
+
label_key=Company.get_representation_label_key(),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
class Meta:
|
|
18
|
+
model = Product
|
|
19
|
+
fields = {
|
|
20
|
+
"title": ["exact", "icontains"],
|
|
21
|
+
"is_competitor": ["exact"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProductCompanyFilterSet(wb_filters.FilterSet):
|
|
26
|
+
competitor_product = wb_filters.BooleanFilter(label=_("Is Competitor"), method="filter_competitor_product")
|
|
27
|
+
|
|
28
|
+
def filter_competitor_product(self, queryset, label, value):
|
|
29
|
+
if value is None:
|
|
30
|
+
return queryset
|
|
31
|
+
else:
|
|
32
|
+
return queryset.filter(product__is_competitor=value)
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
model = ProductCompanyRelationship
|
|
36
|
+
fields = {
|
|
37
|
+
"product": ["exact"],
|
|
38
|
+
}
|
wbcrm/filters/signals.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
from django.db.models.query import QuerySet
|
|
4
|
+
from django.dispatch import receiver
|
|
5
|
+
from django.utils import timezone
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
from psycopg.types.range import TimestamptzRange
|
|
8
|
+
from wbcore import filters as wb_filters
|
|
9
|
+
from wbcore.contrib.directory.filters.entries import (
|
|
10
|
+
CompanyFilter,
|
|
11
|
+
EntryFilter,
|
|
12
|
+
PersonFilter,
|
|
13
|
+
UserIsManagerFilter,
|
|
14
|
+
)
|
|
15
|
+
from wbcore.contrib.directory.models import Entry
|
|
16
|
+
from wbcore.signals.filters import add_filters
|
|
17
|
+
|
|
18
|
+
from wbcrm.models import Account, AccountRole, Product
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def choice_noactivity(queryset: QuerySet[Entry], name, value: str | None) -> QuerySet[Entry]:
|
|
22
|
+
"""Returns only those entries who didn't participate in any activities during the selected timeframe"""
|
|
23
|
+
|
|
24
|
+
if value is None:
|
|
25
|
+
return queryset
|
|
26
|
+
else:
|
|
27
|
+
value = int(value)
|
|
28
|
+
if value:
|
|
29
|
+
end = timezone.now()
|
|
30
|
+
start = end - timedelta(days=value)
|
|
31
|
+
no_activity_timeframe = TimestamptzRange(start, end)
|
|
32
|
+
return queryset.exclude(
|
|
33
|
+
calendar_entities__period__overlap=no_activity_timeframe,
|
|
34
|
+
calendar_entities__item_type="wbcrm.Activity",
|
|
35
|
+
)
|
|
36
|
+
# A value of 0 corresponds to "All Time" in the filter so we need exclude any activities the entries participated in
|
|
37
|
+
return queryset.exclude(calendar_entities__item_type="wbcrm.Activity")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@receiver(add_filters, sender=EntryFilter)
|
|
41
|
+
@receiver(add_filters, sender=PersonFilter)
|
|
42
|
+
@receiver(add_filters, sender=CompanyFilter)
|
|
43
|
+
@receiver(add_filters, sender=UserIsManagerFilter)
|
|
44
|
+
def add_account_filter(sender, request=None, *args, **kwargs):
|
|
45
|
+
def _filter_with_account(queryset: QuerySet[Entry], name, value: str | None) -> QuerySet[Entry]:
|
|
46
|
+
accounts = Account.objects.filter_for_user(request.user)
|
|
47
|
+
if value:
|
|
48
|
+
return queryset.filter(accounts__in=accounts).distinct()
|
|
49
|
+
return queryset
|
|
50
|
+
|
|
51
|
+
def _filter_with_account_role(queryset: QuerySet[Entry], name, value: str | None) -> QuerySet[Entry]:
|
|
52
|
+
roles = AccountRole.objects.filter_for_user(request.user)
|
|
53
|
+
if value:
|
|
54
|
+
return queryset.filter(account_roles__in=roles).distinct()
|
|
55
|
+
return queryset
|
|
56
|
+
|
|
57
|
+
def filter_interested_products(queryset, name, value):
|
|
58
|
+
if value:
|
|
59
|
+
return queryset.filter(interested_products__in=value)
|
|
60
|
+
return queryset
|
|
61
|
+
|
|
62
|
+
interested_products = wb_filters.ModelMultipleChoiceFilter(
|
|
63
|
+
label="Interested Products",
|
|
64
|
+
help_text="Filter by products (ours and comptetitors) that the customer is interested in",
|
|
65
|
+
queryset=Product.objects.all(),
|
|
66
|
+
endpoint=Product.get_representation_endpoint(),
|
|
67
|
+
value_key=Product.get_representation_value_key(),
|
|
68
|
+
label_key=Product.get_representation_label_key(),
|
|
69
|
+
method=filter_interested_products,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"without_activity": wb_filters.ChoiceFilter(
|
|
74
|
+
label=_("No Activity"),
|
|
75
|
+
help_text="Filter for entries without any activities",
|
|
76
|
+
field_name="without_activity",
|
|
77
|
+
choices=[
|
|
78
|
+
(31, _("Last Month")),
|
|
79
|
+
(92, _("Last 3 Months")),
|
|
80
|
+
(182, _("Last 6 Months")),
|
|
81
|
+
(365, _("Last 12 Months")),
|
|
82
|
+
(0, _("All Time")),
|
|
83
|
+
],
|
|
84
|
+
method=choice_noactivity,
|
|
85
|
+
),
|
|
86
|
+
"with_account": wb_filters.BooleanFilter(
|
|
87
|
+
label="With Account", help_text="Filter for entries without an account", method=_filter_with_account
|
|
88
|
+
),
|
|
89
|
+
"with_account_role": wb_filters.BooleanFilter(
|
|
90
|
+
label="With Account Role",
|
|
91
|
+
help_text="Filter for entries without an account role",
|
|
92
|
+
method=_filter_with_account_role,
|
|
93
|
+
),
|
|
94
|
+
"interested_products": interested_products,
|
|
95
|
+
}
|