wbcommission 2.2.1__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.
Potentially problematic release.
This version of wbcommission might be problematic. Click here for more details.
- wbcommission/__init__.py +1 -0
- wbcommission/admin/__init__.py +4 -0
- wbcommission/admin/accounts.py +22 -0
- wbcommission/admin/commission.py +85 -0
- wbcommission/admin/rebate.py +7 -0
- wbcommission/analytics/__init__.py +0 -0
- wbcommission/analytics/marginality.py +181 -0
- wbcommission/apps.py +5 -0
- wbcommission/dynamic_preferences_registry.py +0 -0
- wbcommission/factories/__init__.py +9 -0
- wbcommission/factories/commission.py +100 -0
- wbcommission/factories/rebate.py +16 -0
- wbcommission/filters/__init__.py +7 -0
- wbcommission/filters/rebate.py +187 -0
- wbcommission/filters/signals.py +44 -0
- wbcommission/generators/__init__.py +2 -0
- wbcommission/generators/rebate_generator.py +93 -0
- wbcommission/migrations/0001_initial.py +299 -0
- wbcommission/migrations/0002_commissionrule_remove_accountcustomer_account_and_more.py +395 -0
- wbcommission/migrations/0003_alter_commission_account.py +24 -0
- wbcommission/migrations/0004_rebate_audit_log.py +19 -0
- wbcommission/migrations/0005_alter_rebate_audit_log.py +20 -0
- wbcommission/migrations/0006_commissionrule_consider_zero_percent_for_exclusion.py +21 -0
- wbcommission/migrations/0007_remove_commission_unique_crm_recipient_account_and_more.py +50 -0
- wbcommission/migrations/0008_alter_commission_options_alter_commission_order.py +26 -0
- wbcommission/migrations/__init__.py +0 -0
- wbcommission/models/__init__.py +9 -0
- wbcommission/models/account_service.py +217 -0
- wbcommission/models/commission.py +679 -0
- wbcommission/models/rebate.py +319 -0
- wbcommission/models/signals.py +45 -0
- wbcommission/permissions.py +6 -0
- wbcommission/reports/__init__.py +0 -0
- wbcommission/reports/audit_report.py +51 -0
- wbcommission/reports/customer_report.py +299 -0
- wbcommission/reports/utils.py +30 -0
- wbcommission/serializers/__init__.py +3 -0
- wbcommission/serializers/commissions.py +26 -0
- wbcommission/serializers/rebate.py +87 -0
- wbcommission/serializers/signals.py +27 -0
- wbcommission/tests/__init__.py +0 -0
- wbcommission/tests/analytics/__init__.py +0 -0
- wbcommission/tests/analytics/test_marginality.py +253 -0
- wbcommission/tests/conftest.py +89 -0
- wbcommission/tests/models/__init__.py +0 -0
- wbcommission/tests/models/mixins.py +22 -0
- wbcommission/tests/models/test_account_service.py +293 -0
- wbcommission/tests/models/test_commission.py +587 -0
- wbcommission/tests/models/test_rebate.py +136 -0
- wbcommission/tests/signals.py +0 -0
- wbcommission/tests/test_permissions.py +66 -0
- wbcommission/tests/viewsets/__init__.py +0 -0
- wbcommission/tests/viewsets/test_rebate.py +76 -0
- wbcommission/urls.py +42 -0
- wbcommission/viewsets/__init__.py +7 -0
- wbcommission/viewsets/buttons/__init__.py +2 -0
- wbcommission/viewsets/buttons/rebate.py +46 -0
- wbcommission/viewsets/buttons/signals.py +53 -0
- wbcommission/viewsets/commissions.py +21 -0
- wbcommission/viewsets/display/__init__.py +5 -0
- wbcommission/viewsets/display/commissions.py +21 -0
- wbcommission/viewsets/display/rebate.py +117 -0
- wbcommission/viewsets/endpoints/__init__.py +4 -0
- wbcommission/viewsets/endpoints/commissions.py +0 -0
- wbcommission/viewsets/endpoints/rebate.py +21 -0
- wbcommission/viewsets/menu/__init__.py +1 -0
- wbcommission/viewsets/menu/commissions.py +0 -0
- wbcommission/viewsets/menu/rebate.py +13 -0
- wbcommission/viewsets/mixins.py +39 -0
- wbcommission/viewsets/rebate.py +481 -0
- wbcommission/viewsets/titles/__init__.py +1 -0
- wbcommission/viewsets/titles/commissions.py +0 -0
- wbcommission/viewsets/titles/rebate.py +11 -0
- wbcommission-2.2.1.dist-info/METADATA +11 -0
- wbcommission-2.2.1.dist-info/RECORD +76 -0
- wbcommission-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
from datetime import date as date_lib
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from celery import shared_task
|
|
6
|
+
from django.apps import apps
|
|
7
|
+
from django.core.serializers.json import DjangoJSONEncoder
|
|
8
|
+
from django.db import models
|
|
9
|
+
from django.db.models import Exists, OuterRef, QuerySet
|
|
10
|
+
from django.dispatch import receiver
|
|
11
|
+
from wbcore.contrib.authentication.models import User
|
|
12
|
+
from wbcore.contrib.notifications.dispatch import send_notification
|
|
13
|
+
from wbcore.signals import pre_merge
|
|
14
|
+
from wbcore.utils.enum import ChoiceEnum
|
|
15
|
+
from wbcrm.models.accounts import Account
|
|
16
|
+
|
|
17
|
+
from .commission import Commission, CommissionType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BookingEntryCalculatedValueMixin:
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_accounting_sum(cls, related_data: dict[str, str], queryset: QuerySet) -> float:
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
def update_calculated_value_of_booking_entry(self):
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RebateGroupbyChoice(ChoiceEnum):
|
|
30
|
+
ROOT_ACCOUNT = "Root Account"
|
|
31
|
+
ACCOUNT = "Account"
|
|
32
|
+
ROOT_ACCOUNT_OWNER = "Root Account Owner"
|
|
33
|
+
ACCOUNT_OWNER = "Account Owner"
|
|
34
|
+
PRODUCT = "Product"
|
|
35
|
+
PRODUCT_GROUP = "ProductGroup"
|
|
36
|
+
CLASSIFICATION = "Classification"
|
|
37
|
+
RECIPIENT = "Recipient"
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
@property
|
|
41
|
+
def map(cls) -> dict[str, dict[str, Any]]:
|
|
42
|
+
"""
|
|
43
|
+
Field map used in the groupby filter in the rebate table view
|
|
44
|
+
"""
|
|
45
|
+
return {
|
|
46
|
+
"ROOT_ACCOUNT": {
|
|
47
|
+
"pk": "root_account",
|
|
48
|
+
"title_key": "root_account_repr",
|
|
49
|
+
"search_fields": ["root_account_repr"],
|
|
50
|
+
},
|
|
51
|
+
"ACCOUNT": {
|
|
52
|
+
"pk": "account",
|
|
53
|
+
"title_key": "account__computed_str",
|
|
54
|
+
"search_fields": ["account__computed_str"],
|
|
55
|
+
},
|
|
56
|
+
"ROOT_ACCOUNT_OWNER": {
|
|
57
|
+
"pk": "root_account_owner",
|
|
58
|
+
"title_key": "root_account_owner_repr",
|
|
59
|
+
"search_fields": ["root_account_owner_repr"],
|
|
60
|
+
},
|
|
61
|
+
"ACCOUNT_OWNER": {
|
|
62
|
+
"pk": "account__owner",
|
|
63
|
+
"title_key": "account__owner__computed_str",
|
|
64
|
+
"search_fields": ["account__owner__computed_str"],
|
|
65
|
+
},
|
|
66
|
+
"PRODUCT": {
|
|
67
|
+
"pk": "product",
|
|
68
|
+
"title_key": "product__computed_str",
|
|
69
|
+
"search_fields": ["product__computed_str"],
|
|
70
|
+
},
|
|
71
|
+
"PRODUCT_GROUP": {
|
|
72
|
+
"pk": "product__parent",
|
|
73
|
+
"title_key": "product__parent__name",
|
|
74
|
+
"search_fields": ["product__parent__name"],
|
|
75
|
+
},
|
|
76
|
+
"CLASSIFICATION": {
|
|
77
|
+
"pk": "classification_id",
|
|
78
|
+
"title_key": "classification_title",
|
|
79
|
+
"search_fields": ["classification_title"],
|
|
80
|
+
},
|
|
81
|
+
"RECIPIENT": {
|
|
82
|
+
"pk": "recipient__id",
|
|
83
|
+
"title_key": "recipient__computed_str",
|
|
84
|
+
"search_fields": ["recipient__computed_str"],
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RebateDefaultQueryset(QuerySet):
|
|
90
|
+
def filter_for_user(self, user: User, validity_date: date_lib | None = None) -> QuerySet:
|
|
91
|
+
"""
|
|
92
|
+
Protect the chained queryset and filter the rebates that this user cannot see based on the following rules:
|
|
93
|
+
|
|
94
|
+
* not-hidden commission: Ever user with a direct valid account role on the commission's account
|
|
95
|
+
* hidden: only user with a direct commission role on that commission line
|
|
96
|
+
* in any case, all user with direct commission role
|
|
97
|
+
"""
|
|
98
|
+
if not validity_date:
|
|
99
|
+
validity_date = date_lib.today()
|
|
100
|
+
if user.has_perm("wbcommission.administrate_commission"):
|
|
101
|
+
return self
|
|
102
|
+
allowed_commission_lines = Commission.objects.filter_for_user(user, validity_date=validity_date)
|
|
103
|
+
return self.annotate(
|
|
104
|
+
can_see_commission=Exists(allowed_commission_lines.filter(id=OuterRef("commission"))),
|
|
105
|
+
).filter(can_see_commission=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RebateManager(models.Manager):
|
|
109
|
+
def get_queryset(self) -> RebateDefaultQueryset:
|
|
110
|
+
return RebateDefaultQueryset(self.model)
|
|
111
|
+
|
|
112
|
+
def filter_for_user(self, user: User, validity_date: date_lib | None = None) -> QuerySet:
|
|
113
|
+
return self.get_queryset().filter_for_user(user, validity_date=validity_date)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Rebate(BookingEntryCalculatedValueMixin, models.Model):
|
|
117
|
+
"""The fees that get rebated to a recipient"""
|
|
118
|
+
|
|
119
|
+
int: Optional[int]
|
|
120
|
+
date = models.DateField(verbose_name="Date")
|
|
121
|
+
account = models.ForeignKey(
|
|
122
|
+
"wbcrm.Account",
|
|
123
|
+
related_name="rebates",
|
|
124
|
+
on_delete=models.CASCADE,
|
|
125
|
+
verbose_name="Account",
|
|
126
|
+
limit_choices_to=models.Q(("is_terminal_account", True)),
|
|
127
|
+
)
|
|
128
|
+
product = models.ForeignKey(
|
|
129
|
+
"wbportfolio.Product", related_name="rebates", on_delete=models.CASCADE, verbose_name="Product"
|
|
130
|
+
)
|
|
131
|
+
recipient = models.ForeignKey(
|
|
132
|
+
"directory.Entry", related_name="recipient_rebates", on_delete=models.PROTECT, verbose_name="Recipient"
|
|
133
|
+
)
|
|
134
|
+
commission_type = models.ForeignKey(
|
|
135
|
+
"wbcommission.CommissionType",
|
|
136
|
+
on_delete=models.PROTECT,
|
|
137
|
+
related_name="rebates",
|
|
138
|
+
verbose_name="Commission Type",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
commission = models.ForeignKey(
|
|
142
|
+
"wbcommission.Commission",
|
|
143
|
+
related_name="rebates",
|
|
144
|
+
on_delete=models.PROTECT,
|
|
145
|
+
verbose_name="Commission Line",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
value = models.DecimalField(max_digits=16, decimal_places=4, default=Decimal(0.0), verbose_name="Value")
|
|
149
|
+
|
|
150
|
+
audit_log = models.JSONField(default=dict, verbose_name="Audit Log", encoder=DjangoJSONEncoder)
|
|
151
|
+
objects = RebateManager()
|
|
152
|
+
|
|
153
|
+
class Meta:
|
|
154
|
+
verbose_name = "Rebate"
|
|
155
|
+
verbose_name_plural = "Rebates"
|
|
156
|
+
unique_together = ("date", "recipient", "account", "product", "commission_type")
|
|
157
|
+
index_together = [
|
|
158
|
+
# ("date", "recipient", "product"),
|
|
159
|
+
("commission_type", "date", "recipient", "product", "account"),
|
|
160
|
+
# ("date", "recipient", "product", "commission"),
|
|
161
|
+
]
|
|
162
|
+
notification_types = [
|
|
163
|
+
(
|
|
164
|
+
"wbcommission.rebate.computation_done",
|
|
165
|
+
"Rebate Computation Done",
|
|
166
|
+
"Sends a notification to notify rebate computation requester that the calculation is done",
|
|
167
|
+
True,
|
|
168
|
+
True,
|
|
169
|
+
False,
|
|
170
|
+
),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
def __str__(self) -> str:
|
|
174
|
+
return repr(self)
|
|
175
|
+
|
|
176
|
+
def __repr__(self) -> str:
|
|
177
|
+
return f"{self.date:%d.%m.%Y}: {self.recipient.computed_str} {self.account.title}"
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def manage_rebate(
|
|
181
|
+
cls,
|
|
182
|
+
main_account: Account,
|
|
183
|
+
prune_existing: bool = False,
|
|
184
|
+
start_date: date_lib | None = None,
|
|
185
|
+
only_content_object_ids: list[int] | None = None,
|
|
186
|
+
**filter_kwargs,
|
|
187
|
+
):
|
|
188
|
+
"""
|
|
189
|
+
Utility method to generate rebate for all commission types for the given root account.
|
|
190
|
+
|
|
191
|
+
This method get the rebate information from the commission type's rebate manager and decide wether to create a new rebate or update an existing one
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
main_account: root account to generate rebates from
|
|
195
|
+
prune_existing: If true, will delete all existing rebates for that account and its children. Default to False. Note: For optimization, the rebate manager will yield only valid rebate information and thus, existing obselete rebate might never be overidden with a zero value.
|
|
196
|
+
**filter_kwargs: these key-word arguments are passed down the rebate manager for as keyword argument for the iterator function
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
if prune_existing:
|
|
200
|
+
rebates_to_pruned = cls.objects.filter(account__in=main_account.get_descendants(include_self=True))
|
|
201
|
+
if only_content_object_ids:
|
|
202
|
+
rebates_to_pruned = rebates_to_pruned.filter(product__in=only_content_object_ids)
|
|
203
|
+
if start_date:
|
|
204
|
+
rebates_to_pruned = rebates_to_pruned.filter(date__gte=start_date)
|
|
205
|
+
rebates_to_pruned.delete()
|
|
206
|
+
updated_objs = []
|
|
207
|
+
created_objs = []
|
|
208
|
+
for commission_type in CommissionType.objects.all():
|
|
209
|
+
for (
|
|
210
|
+
terminal_account,
|
|
211
|
+
compute_date,
|
|
212
|
+
commission,
|
|
213
|
+
content_object,
|
|
214
|
+
recipient,
|
|
215
|
+
recipient_fees,
|
|
216
|
+
audit_log,
|
|
217
|
+
) in commission_type.compute_rebates(
|
|
218
|
+
main_account, start_date=start_date, only_content_object_ids=only_content_object_ids, **filter_kwargs
|
|
219
|
+
):
|
|
220
|
+
try:
|
|
221
|
+
rebate = Rebate.objects.get(
|
|
222
|
+
date=compute_date,
|
|
223
|
+
recipient=recipient,
|
|
224
|
+
account=terminal_account,
|
|
225
|
+
product=content_object,
|
|
226
|
+
commission_type=commission_type,
|
|
227
|
+
)
|
|
228
|
+
rebate.value = recipient_fees
|
|
229
|
+
rebate.commission = commission
|
|
230
|
+
rebate.audit_log = audit_log
|
|
231
|
+
updated_objs.append(rebate)
|
|
232
|
+
except cls.DoesNotExist:
|
|
233
|
+
rebate = Rebate(
|
|
234
|
+
date=compute_date,
|
|
235
|
+
recipient=recipient,
|
|
236
|
+
account=terminal_account,
|
|
237
|
+
product=content_object,
|
|
238
|
+
commission_type=commission_type,
|
|
239
|
+
commission=commission,
|
|
240
|
+
value=recipient_fees,
|
|
241
|
+
audit_log=audit_log,
|
|
242
|
+
)
|
|
243
|
+
created_objs.append(rebate)
|
|
244
|
+
|
|
245
|
+
cls.objects.bulk_update(updated_objs, ["value", "commission"], batch_size=10000)
|
|
246
|
+
cls.objects.bulk_create(created_objs, batch_size=10000, ignore_conflicts=True)
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def get_accounting_sum(cls, related_data: dict[str, str], queryset: QuerySet) -> float:
|
|
250
|
+
return (queryset.aggregate(s=models.Sum(related_data["field"]))["s"] or 0) * -1
|
|
251
|
+
|
|
252
|
+
def update_calculated_value_of_booking_entry(self):
|
|
253
|
+
try:
|
|
254
|
+
BookingEntry = apps.get_model("wbaccounting", "BookingEntry") # type: ignore
|
|
255
|
+
booking_entries = BookingEntry.objects.filter( # type: ignore
|
|
256
|
+
related_data__model="wbcommission.Rebate",
|
|
257
|
+
related_data__data__date_gte__lte=self.date.strftime("%Y-%m-%d"),
|
|
258
|
+
related_data__data__date_lte__gte=self.date.strftime("%Y-%m-%d"),
|
|
259
|
+
related_data__data__recipient_id=self.recipient.id,
|
|
260
|
+
related_data__data__product_id=self.product.id,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
for booking_entry in booking_entries:
|
|
264
|
+
booking_entry.calculated_value = booking_entry.get_related_data_accounting_sum() # type: ignore
|
|
265
|
+
booking_entry.save() # type: ignore
|
|
266
|
+
except LookupError:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ----------- TASKS -----------
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@shared_task(queue="commission")
|
|
274
|
+
def manage_rebate_as_task(
|
|
275
|
+
main_account_id: int,
|
|
276
|
+
start_date: date_lib | None = None,
|
|
277
|
+
only_content_object_ids: list[int] | None = None,
|
|
278
|
+
terminal_account_id: int | None = None,
|
|
279
|
+
user: User | None = None,
|
|
280
|
+
**kwargs,
|
|
281
|
+
):
|
|
282
|
+
account = Account.objects.get(id=main_account_id)
|
|
283
|
+
terminal_account_filter_dict = dict(id=terminal_account_id) if terminal_account_id else dict()
|
|
284
|
+
Rebate.manage_rebate(
|
|
285
|
+
main_account=account,
|
|
286
|
+
start_date=start_date,
|
|
287
|
+
only_content_object_ids=only_content_object_ids,
|
|
288
|
+
terminal_account_filter_dict=terminal_account_filter_dict,
|
|
289
|
+
**kwargs,
|
|
290
|
+
)
|
|
291
|
+
if user:
|
|
292
|
+
send_notification(
|
|
293
|
+
code="wbcommission.rebate.computation_done",
|
|
294
|
+
title="Rebate Computation Done",
|
|
295
|
+
body=f"The rebate computation for root account {account} is done",
|
|
296
|
+
user=user,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@receiver(pre_merge, sender="wbcrm.Account")
|
|
301
|
+
def handle_pre_merge_account_for_rebate(sender: models.Model, merged_object: Account, main_object: Account, **kwargs):
|
|
302
|
+
"""
|
|
303
|
+
Aggregate the rebate if it exists already for the main account. Otherwise, reassign the account to point to the main account
|
|
304
|
+
"""
|
|
305
|
+
for rebate in Rebate.objects.filter(account=merged_object).select_for_update():
|
|
306
|
+
try:
|
|
307
|
+
existing_rebate = Rebate.objects.get(
|
|
308
|
+
date=rebate.date,
|
|
309
|
+
recipient=rebate.recipient,
|
|
310
|
+
account=main_object,
|
|
311
|
+
product=rebate.product,
|
|
312
|
+
commission_type=rebate.commission_type,
|
|
313
|
+
)
|
|
314
|
+
existing_rebate.value += rebate.value
|
|
315
|
+
existing_rebate.save()
|
|
316
|
+
rebate.delete()
|
|
317
|
+
except Rebate.DoesNotExist:
|
|
318
|
+
rebate.account = main_object
|
|
319
|
+
rebate.save()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from django.db.models.signals import post_save
|
|
4
|
+
from django.dispatch import receiver
|
|
5
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
6
|
+
from wbcrm.models.accounts import Account
|
|
7
|
+
from wbportfolio.models import Claim
|
|
8
|
+
|
|
9
|
+
from .rebate import manage_rebate_as_task
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# This file contains the different receiver directly linked to wbportfolio. In a future iteration, this will be moved outside of wbcommission and we will use a generic commission signal
|
|
13
|
+
@receiver(post_save, sender="wbportfolio.Claim")
|
|
14
|
+
def post_claim_save_for_rebate_computation(sender, instance, created, **kwargs):
|
|
15
|
+
# if a new commission line is created, we create a general rule
|
|
16
|
+
if instance.status == Claim.Status.APPROVED and instance.account and (root_account := instance.account.get_root()):
|
|
17
|
+
if isinstance(
|
|
18
|
+
instance.date, str
|
|
19
|
+
): # we need to do this in case claim are created manually with date as string (allowed). This corner case led to date being still a string when it hits this signal
|
|
20
|
+
instance.refresh_from_db()
|
|
21
|
+
manage_rebate_as_task.delay(
|
|
22
|
+
root_account.id,
|
|
23
|
+
start_date=instance.date,
|
|
24
|
+
only_content_object_ids=[instance.product.id],
|
|
25
|
+
terminal_account_id=instance.account.id,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@receiver(post_save, sender="wbportfolio.Fees")
|
|
30
|
+
def post_fees_save_for_rebate_computation(sender, instance, created, **kwargs):
|
|
31
|
+
# if a new commission line is created, we create a general rule
|
|
32
|
+
if (
|
|
33
|
+
created
|
|
34
|
+
and (date.today() - instance.transaction_date).days
|
|
35
|
+
<= global_preferences_registry.manager()["wbcommission__days_to_recompute_rebate_from_fees_threshold"]
|
|
36
|
+
): # we make sure that the fee won't trigger rebate computation if they are created too much in the past
|
|
37
|
+
for root_account in Account.objects.filter(level=0):
|
|
38
|
+
if Claim.objects.filter(
|
|
39
|
+
account__in=root_account.get_descendants(include_self=True), product=instance.linked_product
|
|
40
|
+
).exists():
|
|
41
|
+
manage_rebate_as_task.delay(
|
|
42
|
+
root_account.id,
|
|
43
|
+
start_date=instance.transaction_date,
|
|
44
|
+
only_content_object_ids=[instance.linked_product.id],
|
|
45
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from celery import shared_task
|
|
6
|
+
from wbcommission.models import Commission, CommissionType, Rebate
|
|
7
|
+
from wbcore.contrib.authentication.models import User
|
|
8
|
+
from wbcore.contrib.directory.models import Entry
|
|
9
|
+
from wbcrm.models.accounts import Account
|
|
10
|
+
from wbportfolio.models.products import Product
|
|
11
|
+
|
|
12
|
+
from .utils import create_report_and_send
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@shared_task(queue="commission")
|
|
16
|
+
def create_audit_report_and_send_as_task(user_id: int, recipient_id: int, start_date: date, end_date: date):
|
|
17
|
+
user = User.objects.get(id=user_id)
|
|
18
|
+
recipient = Entry.objects.get(id=recipient_id)
|
|
19
|
+
create_report_and_send(user, recipient, start_date, end_date, create_report)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_report(user, customer, start_date, end_date):
|
|
23
|
+
rebates = Rebate.objects.filter(recipient=customer, date__gte=start_date, date__lte=end_date).filter_for_user(user)
|
|
24
|
+
df = pd.DataFrame(
|
|
25
|
+
rebates.values(
|
|
26
|
+
"date",
|
|
27
|
+
"account",
|
|
28
|
+
"product",
|
|
29
|
+
"recipient",
|
|
30
|
+
"commission_type",
|
|
31
|
+
"commission",
|
|
32
|
+
"value",
|
|
33
|
+
"audit_log",
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
buffer = BytesIO()
|
|
37
|
+
if df.empty:
|
|
38
|
+
raise ValueError("There is not rebate for this customer and given time period")
|
|
39
|
+
df = pd.concat([df[df.columns.difference(["audit_log"])], pd.json_normalize(df["audit_log"])], axis=1).sort_values(
|
|
40
|
+
by="date"
|
|
41
|
+
)
|
|
42
|
+
# we deserialize ids into human readable name
|
|
43
|
+
df.commission_type = df.commission_type.map(dict(CommissionType.objects.values_list("id", "name")))
|
|
44
|
+
df["product"] = df["product"].map(dict(Product.objects.values_list("id", "computed_str")))
|
|
45
|
+
df.commission = df.commission.map(
|
|
46
|
+
dict(map(lambda x: (x, str(Commission.objects.get(id=x))), df.commission.unique()))
|
|
47
|
+
)
|
|
48
|
+
df.recipient = df.recipient.map(dict(Entry.objects.values_list("id", "computed_str")))
|
|
49
|
+
df.account = df.account.map(dict(Account.objects.values_list("id", "computed_str")))
|
|
50
|
+
df.to_csv(buffer, index=False, mode="wb", encoding="UTF-8")
|
|
51
|
+
return buffer, "audit_report_{}_{}_{}.csv".format(customer.computed_str, start_date, end_date), "application/csv"
|