wbcommission 1.43.2__tar.gz → 1.59.0__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.
Files changed (82) hide show
  1. {wbcommission-1.43.2 → wbcommission-1.59.0}/.gitignore +0 -1
  2. {wbcommission-1.43.2 → wbcommission-1.59.0}/PKG-INFO +4 -1
  3. {wbcommission-1.43.2 → wbcommission-1.59.0}/pyproject.toml +6 -0
  4. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/accounts.py +2 -1
  5. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/commission.py +1 -0
  6. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/analytics/marginality.py +29 -14
  7. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/factories/commission.py +2 -1
  8. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/factories/rebate.py +1 -0
  9. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/filters/rebate.py +7 -8
  10. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/generators/rebate_generator.py +2 -1
  11. wbcommission-1.59.0/wbcommission/locale/de/LC_MESSAGES/django.mo +0 -0
  12. wbcommission-1.59.0/wbcommission/locale/de/LC_MESSAGES/django.po +29 -0
  13. wbcommission-1.59.0/wbcommission/locale/en/LC_MESSAGES/django.mo +0 -0
  14. wbcommission-1.59.0/wbcommission/locale/en/LC_MESSAGES/django.po +28 -0
  15. wbcommission-1.59.0/wbcommission/locale/fr/LC_MESSAGES/django.mo +0 -0
  16. wbcommission-1.59.0/wbcommission/locale/fr/LC_MESSAGES/django.po +29 -0
  17. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/account_service.py +7 -5
  18. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/commission.py +29 -14
  19. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/rebate.py +2 -1
  20. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/signals.py +4 -4
  21. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/permissions.py +2 -2
  22. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/audit_report.py +2 -1
  23. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/customer_report.py +3 -2
  24. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/commissions.py +2 -1
  25. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/rebate.py +2 -1
  26. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/signals.py +3 -3
  27. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/analytics/test_marginality.py +8 -6
  28. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/conftest.py +1 -2
  29. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/mixins.py +2 -1
  30. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/test_account_service.py +16 -20
  31. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/test_commission.py +10 -13
  32. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/test_rebate.py +11 -10
  33. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/test_permissions.py +2 -1
  34. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/viewsets/test_rebate.py +5 -4
  35. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/urls.py +2 -1
  36. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/commissions.py +2 -1
  37. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/endpoints/rebate.py +0 -6
  38. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/mixins.py +1 -0
  39. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/rebate.py +28 -19
  40. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/__init__.py +0 -0
  41. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/__init__.py +0 -0
  42. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/rebate.py +0 -0
  43. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/analytics/__init__.py +0 -0
  44. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/apps.py +0 -0
  45. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/dynamic_preferences_registry.py +0 -0
  46. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/factories/__init__.py +0 -0
  47. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/filters/__init__.py +0 -0
  48. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/filters/signals.py +0 -0
  49. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/generators/__init__.py +0 -0
  50. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0001_initial.py +0 -0
  51. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0002_commissionrule_remove_accountcustomer_account_and_more.py +0 -0
  52. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0003_alter_commission_account.py +0 -0
  53. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0004_rebate_audit_log.py +0 -0
  54. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0005_alter_rebate_audit_log.py +0 -0
  55. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0006_commissionrule_consider_zero_percent_for_exclusion.py +0 -0
  56. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0007_remove_commission_unique_crm_recipient_account_and_more.py +0 -0
  57. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0008_alter_commission_options_alter_commission_order.py +0 -0
  58. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/__init__.py +0 -0
  59. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/__init__.py +0 -0
  60. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/__init__.py +0 -0
  61. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/utils.py +0 -0
  62. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/__init__.py +0 -0
  63. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/__init__.py +0 -0
  64. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/analytics/__init__.py +0 -0
  65. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/__init__.py +0 -0
  66. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/signals.py +0 -0
  67. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/viewsets/__init__.py +0 -0
  68. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/__init__.py +0 -0
  69. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/buttons/__init__.py +0 -0
  70. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/buttons/rebate.py +0 -0
  71. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/buttons/signals.py +0 -0
  72. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/display/__init__.py +0 -0
  73. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/display/commissions.py +0 -0
  74. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/display/rebate.py +0 -0
  75. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/endpoints/__init__.py +0 -0
  76. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/endpoints/commissions.py +0 -0
  77. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/menu/__init__.py +0 -0
  78. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/menu/commissions.py +0 -0
  79. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/menu/rebate.py +0 -0
  80. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/titles/__init__.py +0 -0
  81. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/titles/commissions.py +0 -0
  82. {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/titles/rebate.py +0 -0
@@ -71,7 +71,6 @@ report.xml
71
71
  */report.xml
72
72
 
73
73
  # Translations
74
- *.mo
75
74
  *.pot
76
75
 
77
76
  # Django stuff:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbcommission
3
- Version: 1.43.2
3
+ Version: 1.59.0
4
4
  Summary: A workbench module for managing human resources.
5
5
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
6
6
  Requires-Dist: reportlab==3.*
@@ -8,4 +8,7 @@ Requires-Dist: wbcompliance
8
8
  Requires-Dist: wbcore
9
9
  Requires-Dist: wbcrm
10
10
  Requires-Dist: wbfdm
11
+ Requires-Dist: wbmailing
11
12
  Requires-Dist: wbnews
13
+ Requires-Dist: wbportfolio
14
+ Requires-Dist: wbreport
@@ -10,6 +10,9 @@ dependencies = [
10
10
  "wbnews",
11
11
  "wbfdm",
12
12
  "wbcompliance",
13
+ "wbportfolio",
14
+ "wbreport",
15
+ "wbmailing",
13
16
  "reportlab == 3.*",
14
17
  ]
15
18
 
@@ -19,6 +22,9 @@ wbcrm = { workspace = true }
19
22
  wbnews = { workspace = true }
20
23
  wbfdm = { workspace = true }
21
24
  wbcompliance = { workspace = true }
25
+ wbportfolio = { workspace = true }
26
+ wbmailing = { workspace = true }
27
+ wbreport = { workspace = true }
22
28
 
23
29
  [tool.uv]
24
30
  package = true
@@ -1,8 +1,9 @@
1
1
  from django.contrib import admin
2
- from wbcommission.models.rebate import manage_rebate_as_task
3
2
  from wbcrm.admin.accounts import AccountModelAdmin as BaseAccountModelAdmin
4
3
  from wbcrm.models.accounts import Account
5
4
 
5
+ from wbcommission.models.rebate import manage_rebate_as_task
6
+
6
7
  from .commission import CommissionTabularInline
7
8
 
8
9
  admin.site.unregister(Account)
@@ -1,4 +1,5 @@
1
1
  from django.contrib import admin
2
+
2
3
  from wbcommission.models import (
3
4
  Commission,
4
5
  CommissionExclusionRule,
@@ -1,15 +1,17 @@
1
- from datetime import date
1
+ from datetime import date, timedelta
2
2
  from decimal import Decimal
3
3
 
4
4
  import numpy as np
5
5
  import pandas as pd
6
6
  from django.db.models import Case, OuterRef, Subquery, Value, When
7
7
  from django.db.models.functions import Coalesce
8
- from wbcommission.models import CommissionType, Rebate
8
+ from pandas._libs.tslibs.offsets import BDay
9
9
  from wbcore.contrib.currency.models import CurrencyFXRates
10
10
  from wbfdm.models import InstrumentPrice
11
11
  from wbportfolio.models import Fees
12
12
 
13
+ from wbcommission.models import CommissionType, Rebate
14
+
13
15
 
14
16
  class MarginalityCalculator:
15
17
  FEE_MAP = {
@@ -19,6 +21,9 @@ class MarginalityCalculator:
19
21
  }
20
22
 
21
23
  def __init__(self, products, from_date: date, to_date: date):
24
+ bday_from_date = (from_date + timedelta(days=1) - BDay(1)).date()
25
+ bday_to_date = (to_date - timedelta(days=1) + BDay(1)).date()
26
+
22
27
  products = products.annotate(
23
28
  fx_rate=Coalesce(
24
29
  Subquery(
@@ -39,7 +44,7 @@ class MarginalityCalculator:
39
44
  # compute net marginality
40
45
  self.df_aum = pd.DataFrame(
41
46
  InstrumentPrice.objects.annotate_base_data()
42
- .filter(instrument__in=products, date__gte=from_date, date__lte=to_date)
47
+ .filter(instrument__in=products, date__gte=bday_from_date, date__lte=bday_to_date)
43
48
  .values_list("calculated", "net_value_usd", "date", "outstanding_shares", "instrument"),
44
49
  columns=["calculated", "net_value_usd", "date", "outstanding_shares", "instrument"],
45
50
  ).rename(columns={"instrument": "id"})
@@ -65,12 +70,11 @@ class MarginalityCalculator:
65
70
  ).dropna()
66
71
 
67
72
  # Build the fees dataframe where product id is the index and colum are the every fees type available and value are the amount.
68
-
69
73
  fees = Fees.valid_objects.filter(
70
- transaction_date__lte=to_date,
71
- transaction_date__gte=from_date,
74
+ fee_date__lte=bday_to_date,
75
+ fee_date__gte=bday_from_date,
72
76
  transaction_subtype__in=self.FEE_MAP.keys(),
73
- linked_product__in=products,
77
+ product__in=products,
74
78
  ).annotate(
75
79
  fee_type=Case(
76
80
  *[When(transaction_subtype=k, then=Value(v)) for k, v in self.FEE_MAP.items()],
@@ -78,9 +82,9 @@ class MarginalityCalculator:
78
82
  )
79
83
  )
80
84
  self.df_fees = pd.DataFrame(
81
- fees.values_list("linked_product", "fee_type", "total_value", "transaction_date", "calculated"),
82
- columns=["linked_product", "fee_type", "total_value", "transaction_date", "calculated"],
83
- ).rename(columns={"linked_product": "id", "transaction_date": "date"})
85
+ fees.values_list("product", "fee_type", "total_value", "fee_date", "calculated"),
86
+ columns=["product", "fee_type", "total_value", "fee_date", "calculated"],
87
+ ).rename(columns={"product": "id", "fee_date": "date"})
84
88
  self.df_fees["date"] = pd.to_datetime(self.df_fees["date"])
85
89
 
86
90
  self.df_fees = (
@@ -92,10 +96,14 @@ class MarginalityCalculator:
92
96
  self.df_fees["total"] = self.df_fees.sum(axis=1)
93
97
  self.df_fees = self.df_fees.reindex(self.df_aum.index, fill_value=0)
94
98
  self.df_fees = self._rolling_average_monday(self.df_fees)
99
+ self.df_fees = self.df_fees[
100
+ (self.df_fees.index.get_level_values(1) >= pd.Timestamp(from_date))
101
+ & (self.df_fees.index.get_level_values(1) <= pd.Timestamp(to_date))
102
+ ]
95
103
 
96
104
  # Build the fees dataframe where product id is the index and colum are the every fees type available and value are the amount.
97
105
  self.df_rebates = pd.DataFrame(
98
- Rebate.objects.filter(date__gte=from_date, date__lte=to_date, product__in=products).values_list(
106
+ Rebate.objects.filter(date__gte=bday_from_date, date__lte=bday_to_date, product__in=products).values_list(
99
107
  "product", "value", "date", "commission_type__key"
100
108
  ),
101
109
  columns=["product", "value", "date", "commission_type__key"],
@@ -117,9 +125,16 @@ class MarginalityCalculator:
117
125
  self.df_rebates = self.df_rebates.reindex(self.df_aum.index, fill_value=0)
118
126
  self.df_rebates["total"] = self.df_rebates.sum(axis=1)
119
127
  self.df_rebates = self._rolling_average_monday(self.df_rebates)
120
- # Iniliaze basic column
121
-
122
- self.empty_column = pd.Series(0.0, dtype="float64", index=self.df_fees.index)
128
+ self.df_rebates = self.df_rebates[
129
+ (self.df_rebates.index.get_level_values(1) >= pd.Timestamp(from_date))
130
+ & (self.df_rebates.index.get_level_values(1) <= pd.Timestamp(to_date))
131
+ ]
132
+ # Initialize basic column
133
+ self.df_aum = self.df_aum[
134
+ (self.df_aum.index.get_level_values(1) >= pd.Timestamp(from_date))
135
+ & (self.df_aum.index.get_level_values(1) <= pd.Timestamp(to_date))
136
+ ]
137
+ self.empty_column = pd.Series(0.0, dtype="float64", index=self.df_aum.index)
123
138
  self._set_basics_statistics()
124
139
 
125
140
  def _set_basics_statistics(self):
@@ -2,13 +2,14 @@ from datetime import date
2
2
 
3
3
  import factory
4
4
  from psycopg.types.range import DateRange
5
+ from wbportfolio.models.roles import PortfolioRole
6
+
5
7
  from wbcommission.models.commission import (
6
8
  Commission,
7
9
  CommissionExclusionRule,
8
10
  CommissionRole,
9
11
  CommissionType,
10
12
  )
11
- from wbportfolio.models.roles import PortfolioRole
12
13
 
13
14
 
14
15
  class CommissionTypeFactory(factory.django.DjangoModelFactory):
@@ -1,4 +1,5 @@
1
1
  import factory
2
+
2
3
  from wbcommission.models import Rebate
3
4
 
4
5
 
@@ -1,5 +1,3 @@
1
- from wbcommission.models import Rebate
2
- from wbcommission.models.rebate import RebateGroupbyChoice
3
1
  from wbcore import filters as wb_filters
4
2
  from wbcore.contrib.directory.models import Company
5
3
  from wbcore.filters.defaults import current_quarter_date_range
@@ -9,14 +7,16 @@ from wbfdm.preferences import get_default_classification_group
9
7
  from wbportfolio.filters.transactions.claim import CommissionBaseFilterSet
10
8
  from wbportfolio.models import Product
11
9
 
10
+ from wbcommission.models import Rebate
11
+ from wbcommission.models.rebate import RebateGroupbyChoice
12
+
12
13
 
13
14
  class RebateDateFilter(CommissionBaseFilterSet):
14
15
  date = wb_filters.DateRangeFilter(
15
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
16
16
  label="Date Range",
17
17
  required=True,
18
18
  clearable=False,
19
- default=current_quarter_date_range,
19
+ initial=current_quarter_date_range,
20
20
  )
21
21
 
22
22
  class Meta:
@@ -34,13 +34,13 @@ class RebateGroupByFilter(PandasFilterSetMixin, RebateDateFilter):
34
34
  group_by = wb_filters.ChoiceFilter(
35
35
  label="Group By",
36
36
  choices=RebateGroupbyChoice.choices(),
37
- default=RebateGroupbyChoice.ACCOUNT.name, # typing: ignore
37
+ initial=RebateGroupbyChoice.ACCOUNT.name, # typing: ignore
38
38
  method=lambda queryset, label, value: queryset,
39
39
  clearable=False,
40
40
  required=True,
41
41
  )
42
42
  groupby_classification_group = wb_filters.ModelChoiceFilter(
43
- default=lambda k, v, f: get_default_classification_group().id,
43
+ initial=lambda k, v, f: get_default_classification_group().id,
44
44
  method=lambda queryset, label, value: queryset,
45
45
  label="Group by Classification Group",
46
46
  queryset=ClassificationGroup.objects.all(),
@@ -97,10 +97,9 @@ class CustomerRebateGroupByFilter(RebateGroupByFilter):
97
97
  class RebateMarginalityFilter(PandasFilterSetMixin, wb_filters.FilterSet):
98
98
  date = wb_filters.DateRangeFilter(
99
99
  label="Date Range",
100
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
101
100
  required=True,
102
101
  clearable=False,
103
- default=current_quarter_date_range,
102
+ initial=current_quarter_date_range,
104
103
  )
105
104
 
106
105
  bank = wb_filters.ModelMultipleChoiceFilter(
@@ -7,10 +7,11 @@ from typing import Iterable
7
7
  from django.db.models import QuerySet, Sum
8
8
  from wbaccounting.generators.base import AbstractBookingEntryGenerator
9
9
  from wbaccounting.models import BookingEntry
10
- from wbcommission.models import Rebate
11
10
  from wbcore.contrib.directory.models import Entry
12
11
  from wbportfolio.models import Product
13
12
 
13
+ from wbcommission.models import Rebate
14
+
14
15
 
15
16
  class RebateGenerator(AbstractBookingEntryGenerator):
16
17
  TITLE = "Rebate Generation"
@@ -0,0 +1,29 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2025-05-30 11:37+0200\n"
12
+ "PO-Revision-Date: 2025-05-30 09:40+0000\n"
13
+ "Language-Team: German (https://app.transifex.com/stainly/teams/171242/de/)\n"
14
+ "MIME-Version: 1.0\n"
15
+ "Content-Type: text/plain; charset=UTF-8\n"
16
+ "Content-Transfer-Encoding: 8bit\n"
17
+ "Language: de\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+
20
+ #: viewsets/buttons/rebate.py:19
21
+ msgid "Start"
22
+ msgstr ""
23
+
24
+ #: viewsets/rebate.py:435
25
+ msgid ""
26
+ "The selected date range includes a Saturday or Sunday. Please note that fees"
27
+ " and rebates are normalized over the weekend, as fees continue to accumulate"
28
+ " during this period."
29
+ msgstr ""
@@ -0,0 +1,28 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: PACKAGE VERSION\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2025-05-30 11:37+0200\n"
11
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+ "Language: \n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+
19
+ #: viewsets/buttons/rebate.py:19
20
+ msgid "Start"
21
+ msgstr ""
22
+
23
+ #: viewsets/rebate.py:435
24
+ msgid ""
25
+ "The selected date range includes a Saturday or Sunday. Please note that fees "
26
+ "and rebates are normalized over the weekend, as fees continue to accumulate "
27
+ "during this period."
28
+ msgstr ""
@@ -0,0 +1,29 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2025-05-30 11:37+0200\n"
12
+ "PO-Revision-Date: 2025-05-30 09:40+0000\n"
13
+ "Language-Team: French (https://app.transifex.com/stainly/teams/171242/fr/)\n"
14
+ "MIME-Version: 1.0\n"
15
+ "Content-Type: text/plain; charset=UTF-8\n"
16
+ "Content-Transfer-Encoding: 8bit\n"
17
+ "Language: fr\n"
18
+ "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
19
+
20
+ #: viewsets/buttons/rebate.py:19
21
+ msgid "Start"
22
+ msgstr ""
23
+
24
+ #: viewsets/rebate.py:435
25
+ msgid ""
26
+ "The selected date range includes a Saturday or Sunday. Please note that fees"
27
+ " and rebates are normalized over the weekend, as fees continue to accumulate"
28
+ " during this period."
29
+ msgstr ""
@@ -31,13 +31,13 @@ class AccountRebateManager:
31
31
  # get the fees as a multi-index matrix
32
32
  self.df_fees = pd.DataFrame(
33
33
  Fees.valid_objects.filter(
34
- linked_product__in=claim_products,
34
+ product__in=claim_products,
35
35
  transaction_subtype__in=self.FEE_MAP[self.commission_type_key],
36
- ).values("linked_product", "transaction_date", "total_value")
36
+ ).values("product", "fee_date", "total_value")
37
37
  )
38
38
  if not self.df_fees.empty:
39
39
  self.df_fees = (
40
- self.df_fees.rename(columns={"linked_product": "product", "transaction_date": "date"})
40
+ self.df_fees.rename(columns={"product": "product", "fee_date": "date"})
41
41
  .groupby(["product", "date"])
42
42
  .sum()
43
43
  .total_value.astype(float)
@@ -91,7 +91,7 @@ class AccountRebateManager:
91
91
  only_content_object_ids: list[int] | None = None,
92
92
  start_date: date | None = None,
93
93
  terminal_account_filter_dict: dict[str, Any] | None = None,
94
- **kwargs
94
+ **kwargs,
95
95
  ) -> Generator[tuple[Account, Product, date], None, None]:
96
96
  """
97
97
  Given the parameters and the instance root account and commission type, yield all valid terminal account, product and date
@@ -182,7 +182,9 @@ class AccountRebateManager:
182
182
  and date, a KeyError will be caught, and the function will return Decimal(0).
183
183
  """
184
184
  with suppress(InstrumentPrice.DoesNotExist, KeyError):
185
- product_shares = max(product.prices.get(date=compute_date, calculated=True).outstanding_shares, Decimal(0))
185
+ product_shares = max(
186
+ product.prices.get(date=compute_date, calculated=True).outstanding_shares or Decimal(0), Decimal(0)
187
+ )
186
188
  account_shares = max(
187
189
  Decimal(self.df_shares.loc[(terminal_account.id, product.id, compute_date)]), Decimal(0)
188
190
  )
@@ -201,13 +201,21 @@ class CommissionType(WBModel):
201
201
  ) # If fees are negative, then we need to return the negative of the recipient fees
202
202
 
203
203
  # Yield rebate information
204
- yield terminal_account, compute_date, commission, content_object, recipient, rebate_gain, {
205
- "terminal_account_holding_ratio": terminal_account_holding_ratio,
206
- "root_account_total_holding": root_account_total_holding,
207
- "commission_percent": actual_percent,
208
- "commission_pool": commission_pool,
209
- "recipient_percent": recipient_percent,
210
- }
204
+ yield (
205
+ terminal_account,
206
+ compute_date,
207
+ commission,
208
+ content_object,
209
+ recipient,
210
+ rebate_gain,
211
+ {
212
+ "terminal_account_holding_ratio": terminal_account_holding_ratio,
213
+ "root_account_total_holding": root_account_total_holding,
214
+ "commission_percent": actual_percent,
215
+ "commission_pool": commission_pool,
216
+ "recipient_percent": recipient_percent,
217
+ },
218
+ )
211
219
 
212
220
  def __str__(self) -> str:
213
221
  return self.name
@@ -433,11 +441,14 @@ class Commission(OrderedModel, WBModel):
433
441
  )
434
442
  total_weighting = Decimal(account_roles.aggregate(s=Sum("weighting"))["s"] or 0)
435
443
  for account_role in account_roles:
436
- yield account_role.entry, Decimal(
437
- account_role.weighting
438
- ) / total_weighting if total_weighting else Decimal(0)
444
+ yield (
445
+ account_role.entry,
446
+ Decimal(account_role.weighting) / total_weighting if total_weighting else Decimal(0),
447
+ )
439
448
  else:
440
- role_recipients = PortfolioRole.objects.exclude(weighting=0).filter(
449
+ role_recipients = PortfolioRole.objects.exclude(
450
+ weighting=0
451
+ ).filter(
441
452
  (Q(role_type=self.portfolio_role_recipient) & (Q(instrument=product) | Q(instrument__isnull=True)))
442
453
  & (Q(start__lte=val_date) | Q(start__isnull=True))
443
454
  & (
@@ -446,9 +457,10 @@ class Commission(OrderedModel, WBModel):
446
457
  )
447
458
  total_weighting = Decimal(role_recipients.aggregate(s=Sum("weighting"))["s"] or 0)
448
459
  for portfolio_role in role_recipients:
449
- yield portfolio_role.person.entry_ptr, Decimal(
450
- Decimal(portfolio_role.weighting) / total_weighting
451
- ) if total_weighting else Decimal(0)
460
+ yield (
461
+ portfolio_role.person.entry_ptr,
462
+ Decimal(Decimal(portfolio_role.weighting) / total_weighting) if total_weighting else Decimal(0),
463
+ )
452
464
 
453
465
  @classmethod
454
466
  def get_endpoint_basename(cls) -> str:
@@ -539,6 +551,9 @@ class CommissionExclusionRule(models.Model):
539
551
  ),
540
552
  ]
541
553
 
554
+ def __str__(self) -> str:
555
+ return super().__str__()
556
+
542
557
 
543
558
  class CommissionRule(ComplexToStringMixin, WBModel):
544
559
  id: Optional[int]
@@ -10,6 +10,7 @@ from django.db.models import Exists, OuterRef, QuerySet
10
10
  from django.dispatch import receiver
11
11
  from wbcore.contrib.authentication.models import User
12
12
  from wbcore.contrib.notifications.dispatch import send_notification
13
+ from wbcore.contrib.notifications.utils import create_notification_type
13
14
  from wbcore.signals import pre_merge
14
15
  from wbcore.utils.enum import ChoiceEnum
15
16
  from wbcrm.models.accounts import Account
@@ -160,7 +161,7 @@ class Rebate(BookingEntryCalculatedValueMixin, models.Model):
160
161
  # ("date", "recipient", "product", "commission"),
161
162
  ]
162
163
  notification_types = [
163
- (
164
+ create_notification_type(
164
165
  "wbcommission.rebate.computation_done",
165
166
  "Rebate Computation Done",
166
167
  "Sends a notification to notify rebate computation requester that the calculation is done",
@@ -31,15 +31,15 @@ def post_fees_save_for_rebate_computation(sender, instance, created, **kwargs):
31
31
  # if a new commission line is created, we create a general rule
32
32
  if (
33
33
  created
34
- and (date.today() - instance.transaction_date).days
34
+ and (date.today() - instance.fee_date).days
35
35
  <= global_preferences_registry.manager()["wbcommission__days_to_recompute_rebate_from_fees_threshold"]
36
36
  ): # we make sure that the fee won't trigger rebate computation if they are created too much in the past
37
37
  for root_account in Account.objects.filter(level=0):
38
38
  if Claim.objects.filter(
39
- account__in=root_account.get_descendants(include_self=True), product=instance.linked_product
39
+ account__in=root_account.get_descendants(include_self=True), product=instance.product
40
40
  ).exists():
41
41
  manage_rebate_as_task.delay(
42
42
  root_account.id,
43
- start_date=instance.transaction_date,
44
- only_content_object_ids=[instance.linked_product.id],
43
+ start_date=instance.fee_date,
44
+ only_content_object_ids=[instance.product.id],
45
45
  )
@@ -1,6 +1,6 @@
1
- from rest_framework.permissions import BasePermission
1
+ from rest_framework.permissions import IsAuthenticated
2
2
 
3
3
 
4
- class IsCommissionAdmin(BasePermission):
4
+ class IsCommissionAdmin(IsAuthenticated):
5
5
  def has_permission(self, request, view):
6
6
  return request.user.has_perm("wbcommission.administrate_commission")
@@ -3,12 +3,13 @@ from io import BytesIO
3
3
 
4
4
  import pandas as pd
5
5
  from celery import shared_task
6
- from wbcommission.models import Commission, CommissionType, Rebate
7
6
  from wbcore.contrib.authentication.models import User
8
7
  from wbcore.contrib.directory.models import Entry
9
8
  from wbcrm.models.accounts import Account
10
9
  from wbportfolio.models.products import Product
11
10
 
11
+ from wbcommission.models import Commission, CommissionType, Rebate
12
+
12
13
  from .utils import create_report_and_send
13
14
 
14
15
 
@@ -5,7 +5,6 @@ from io import BytesIO
5
5
  import xlsxwriter
6
6
  from celery import shared_task
7
7
  from django.db.models import Case, F, Sum, When
8
- from wbcommission.models import CommissionType, Rebate
9
8
  from wbcore.contrib.authentication.models import User
10
9
  from wbcore.contrib.currency.models import CurrencyFXRates
11
10
  from wbcore.contrib.directory.models import Entry
@@ -19,6 +18,8 @@ from wbfdm.models import InstrumentPrice
19
18
  from wbportfolio.models.transactions.claim import Claim
20
19
  from xlsxwriter.utility import xl_rowcol_to_cell
21
20
 
21
+ from wbcommission.models import CommissionType, Rebate
22
+
22
23
  from .utils import create_report_and_send
23
24
 
24
25
  AccountCustomer = None
@@ -217,7 +218,7 @@ def create_report(user, customer, start_date, end_date):
217
218
  worksheet_trade_performance.write_string(bank_cell, claim.bank, base_format)
218
219
  worksheet_trade_performance.write_string(root_account_cell, claim.account.get_root().title, base_format)
219
220
  worksheet_trade_performance.write_string(account_cell, str(claim.account), base_format)
220
- worksheet_trade_performance.write_string(product_cell, claim.product.title, base_format)
221
+ worksheet_trade_performance.write_string(product_cell, claim.product.name, base_format)
221
222
 
222
223
  worksheet_trade_performance.write_number(
223
224
  price_trade_cell, claim.price_date or claim.product.share_price, decimal_format
@@ -1,6 +1,7 @@
1
- from wbcommission.models import CommissionType
2
1
  from wbcore import serializers as wb_serializers
3
2
 
3
+ from wbcommission.models import CommissionType
4
+
4
5
 
5
6
  class CommissionTypeRepresentationSerializer(wb_serializers.RepresentationSerializer):
6
7
  _detail = wb_serializers.HyperlinkField(reverse_name="wbcommission:commissiontype-detail")
@@ -1,6 +1,7 @@
1
- from wbcommission.models import Rebate
2
1
  from wbcore import serializers as wb_serializers
3
2
 
3
+ from wbcommission.models import Rebate
4
+
4
5
 
5
6
  class RebateModelSerializer(wb_serializers.ModelSerializer):
6
7
  id = wb_serializers.PrimaryKeyField()
@@ -21,7 +21,7 @@ def commission_adding_additional_resource(sender, serializer, instance, request,
21
21
  "generate_customer_commission_report": f'{reverse("wbcommission:rebate-customerreport", request=request)}?recipient_id={instance.id}',
22
22
  }
23
23
  if user.has_perm("wbcommission.administrate_commission"):
24
- res[
25
- "generate_audit_commission_report"
26
- ] = f'{reverse("wbcommission:rebate-auditreport", request=request)}?recipient_id={instance.id}'
24
+ res["generate_audit_commission_report"] = (
25
+ f'{reverse("wbcommission:rebate-auditreport", request=request)}?recipient_id={instance.id}'
26
+ )
27
27
  return res
@@ -3,11 +3,13 @@ from faker import Faker
3
3
  from pandas.tseries.offsets import BDay
4
4
  from rest_framework.reverse import reverse
5
5
  from rest_framework.test import APIClient, APIRequestFactory
6
+ from wbfdm.factories import InstrumentPriceFactory
7
+ from wbportfolio.factories import FeesFactory, ProductFactory
8
+ from wbportfolio.models import Product
9
+
6
10
  from wbcommission.analytics.marginality import MarginalityCalculator
7
11
  from wbcommission.factories import CommissionTypeFactory, RebateFactory
8
12
  from wbcommission.viewsets.rebate import RebateProductMarginalityViewSet
9
- from wbportfolio.factories import FeesFactory, InstrumentPriceFactory, ProductFactory
10
- from wbportfolio.models import Product
11
13
 
12
14
  fake = Faker()
13
15
 
@@ -19,14 +21,14 @@ def _create_fixture(product, val_date, net_value=100, outstanding_shares=100):
19
21
  instrument=product, net_value=net_value, outstanding_shares=outstanding_shares, calculated=False, date=val_date
20
22
  ).net_value # create a price of AUM 100*100
21
23
  management_fees_1 = FeesFactory.create(
22
- linked_product=product, transaction_date=val_date, transaction_subtype="MANAGEMENT", calculated=False
24
+ product=product, fee_date=val_date, transaction_subtype="MANAGEMENT", calculated=False
23
25
  ).total_value
24
26
  performance_fees_1 = FeesFactory.create(
25
- linked_product=product, transaction_date=val_date, transaction_subtype="PERFORMANCE", calculated=False
27
+ product=product, fee_date=val_date, transaction_subtype="PERFORMANCE", calculated=False
26
28
  ).total_value
27
29
  performance_crys_fees_1 = FeesFactory.create(
28
- linked_product=product,
29
- transaction_date=val_date,
30
+ product=product,
31
+ fee_date=val_date,
30
32
  transaction_subtype="PERFORMANCE_CRYSTALIZED",
31
33
  calculated=False,
32
34
  ).total_value
@@ -23,12 +23,11 @@ from wbcore.contrib.geography.factories import (
23
23
  )
24
24
  from wbcore.contrib.geography.tests.signals import app_pre_migration
25
25
  from wbcrm.factories import AccountFactory, AccountRoleFactory, AccountRoleTypeFactory
26
- from wbfdm.factories import ExchangeFactory, InstrumentFactory, InstrumentTypeFactory
26
+ from wbfdm.factories import ExchangeFactory, InstrumentFactory, InstrumentTypeFactory, InstrumentPriceFactory
27
27
  from wbportfolio.factories import (
28
28
  ClaimFactory,
29
29
  CustomerTradeFactory,
30
30
  FeesFactory,
31
- InstrumentPriceFactory,
32
31
  PortfolioFactory,
33
32
  ProductFactory,
34
33
  ProductPortfolioRoleFactory,
@@ -1,7 +1,8 @@
1
1
  import pytest
2
+ from wbcrm.factories import AccountFactory
3
+
2
4
  from wbcommission.factories import CommissionTypeFactory
3
5
  from wbcommission.models.account_service import AccountRebateManager
4
- from wbcrm.factories import AccountFactory
5
6
 
6
7
 
7
8
  class AccountManagerFixture: