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.
- {wbcommission-1.43.2 → wbcommission-1.59.0}/.gitignore +0 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/PKG-INFO +4 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/pyproject.toml +6 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/accounts.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/commission.py +1 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/analytics/marginality.py +29 -14
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/factories/commission.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/factories/rebate.py +1 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/filters/rebate.py +7 -8
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/generators/rebate_generator.py +2 -1
- wbcommission-1.59.0/wbcommission/locale/de/LC_MESSAGES/django.mo +0 -0
- wbcommission-1.59.0/wbcommission/locale/de/LC_MESSAGES/django.po +29 -0
- wbcommission-1.59.0/wbcommission/locale/en/LC_MESSAGES/django.mo +0 -0
- wbcommission-1.59.0/wbcommission/locale/en/LC_MESSAGES/django.po +28 -0
- wbcommission-1.59.0/wbcommission/locale/fr/LC_MESSAGES/django.mo +0 -0
- wbcommission-1.59.0/wbcommission/locale/fr/LC_MESSAGES/django.po +29 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/account_service.py +7 -5
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/commission.py +29 -14
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/rebate.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/signals.py +4 -4
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/permissions.py +2 -2
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/audit_report.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/customer_report.py +3 -2
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/commissions.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/rebate.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/signals.py +3 -3
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/analytics/test_marginality.py +8 -6
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/conftest.py +1 -2
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/mixins.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/test_account_service.py +16 -20
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/test_commission.py +10 -13
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/test_rebate.py +11 -10
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/test_permissions.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/viewsets/test_rebate.py +5 -4
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/urls.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/commissions.py +2 -1
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/endpoints/rebate.py +0 -6
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/mixins.py +1 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/rebate.py +28 -19
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/admin/rebate.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/analytics/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/apps.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/dynamic_preferences_registry.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/factories/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/filters/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/filters/signals.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/generators/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0001_initial.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0002_commissionrule_remove_accountcustomer_account_and_more.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0003_alter_commission_account.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0004_rebate_audit_log.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0005_alter_rebate_audit_log.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0006_commissionrule_consider_zero_percent_for_exclusion.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0007_remove_commission_unique_crm_recipient_account_and_more.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/0008_alter_commission_options_alter_commission_order.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/migrations/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/models/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/reports/utils.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/serializers/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/analytics/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/models/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/signals.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/viewsets/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/buttons/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/buttons/rebate.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/buttons/signals.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/display/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/display/commissions.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/display/rebate.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/endpoints/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/endpoints/commissions.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/menu/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/menu/commissions.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/menu/rebate.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/titles/__init__.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/titles/commissions.py +0 -0
- {wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/viewsets/titles/rebate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wbcommission
|
|
3
|
-
Version: 1.
|
|
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,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
|
|
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=
|
|
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
|
-
|
|
71
|
-
|
|
74
|
+
fee_date__lte=bday_to_date,
|
|
75
|
+
fee_date__gte=bday_from_date,
|
|
72
76
|
transaction_subtype__in=self.FEE_MAP.keys(),
|
|
73
|
-
|
|
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("
|
|
82
|
-
columns=["
|
|
83
|
-
).rename(columns={"
|
|
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=
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
Binary file
|
|
@@ -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 ""
|
|
Binary file
|
|
@@ -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 ""
|
|
Binary file
|
|
@@ -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
|
-
|
|
34
|
+
product__in=claim_products,
|
|
35
35
|
transaction_subtype__in=self.FEE_MAP[self.commission_type_key],
|
|
36
|
-
).values("
|
|
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={"
|
|
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(
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
437
|
-
account_role.
|
|
438
|
-
|
|
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(
|
|
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
|
|
450
|
-
|
|
451
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
44
|
-
only_content_object_ids=[instance.
|
|
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
|
|
1
|
+
from rest_framework.permissions import IsAuthenticated
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class IsCommissionAdmin(
|
|
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.
|
|
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")
|
|
@@ -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
|
-
"
|
|
26
|
-
|
|
24
|
+
res["generate_audit_commission_report"] = (
|
|
25
|
+
f'{reverse("wbcommission:rebate-auditreport", request=request)}?recipient_id={instance.id}'
|
|
26
|
+
)
|
|
27
27
|
return res
|
{wbcommission-1.43.2 → wbcommission-1.59.0}/wbcommission/tests/analytics/test_marginality.py
RENAMED
|
@@ -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
|
-
|
|
24
|
+
product=product, fee_date=val_date, transaction_subtype="MANAGEMENT", calculated=False
|
|
23
25
|
).total_value
|
|
24
26
|
performance_fees_1 = FeesFactory.create(
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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:
|