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
wbcommission/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from wbcommission.models.rebate import manage_rebate_as_task
|
|
3
|
+
from wbcrm.admin.accounts import AccountModelAdmin as BaseAccountModelAdmin
|
|
4
|
+
from wbcrm.models.accounts import Account
|
|
5
|
+
|
|
6
|
+
from .commission import CommissionTabularInline
|
|
7
|
+
|
|
8
|
+
admin.site.unregister(Account)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@admin.register(Account)
|
|
12
|
+
class AccountModelAdmin(BaseAccountModelAdmin):
|
|
13
|
+
def make_rebates(self, request, queryset):
|
|
14
|
+
for account in queryset:
|
|
15
|
+
manage_rebate_as_task.delay(account.id)
|
|
16
|
+
|
|
17
|
+
def get_assets_under_management(self, request, queryset):
|
|
18
|
+
for account in queryset:
|
|
19
|
+
account.get_assets_under_management()
|
|
20
|
+
|
|
21
|
+
actions = list(BaseAccountModelAdmin.actions) + [make_rebates, get_assets_under_management]
|
|
22
|
+
inlines = BaseAccountModelAdmin.inlines + [CommissionTabularInline]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from wbcommission.models import (
|
|
3
|
+
Commission,
|
|
4
|
+
CommissionExclusionRule,
|
|
5
|
+
CommissionRole,
|
|
6
|
+
CommissionRule,
|
|
7
|
+
CommissionType,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@admin.register(CommissionType)
|
|
12
|
+
class CommissionTypeModelAdmin(admin.ModelAdmin):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CommissionRuleTabularInline(admin.TabularInline):
|
|
17
|
+
ordering = ("timespan__startswith", "assets_under_management_range__startswith")
|
|
18
|
+
fields = (
|
|
19
|
+
"timespan",
|
|
20
|
+
"assets_under_management_range",
|
|
21
|
+
"percent",
|
|
22
|
+
)
|
|
23
|
+
model = CommissionRule
|
|
24
|
+
extra = 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CommissionRoleTabularInline(admin.TabularInline):
|
|
28
|
+
model = CommissionRole
|
|
29
|
+
autocomplete_fields = ["person"]
|
|
30
|
+
fields = (
|
|
31
|
+
"person",
|
|
32
|
+
"commission",
|
|
33
|
+
)
|
|
34
|
+
extra = 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CommissionTabularInline(admin.TabularInline):
|
|
38
|
+
extra = 0
|
|
39
|
+
model = Commission
|
|
40
|
+
fields = [
|
|
41
|
+
"order",
|
|
42
|
+
"crm_recipient",
|
|
43
|
+
"account_role_type_recipient",
|
|
44
|
+
"portfolio_role_recipient",
|
|
45
|
+
"commission_type",
|
|
46
|
+
"net_commission",
|
|
47
|
+
"is_hidden",
|
|
48
|
+
"exclusion_rule_account_role_type",
|
|
49
|
+
]
|
|
50
|
+
readonly_fields = ["order"]
|
|
51
|
+
ordering = ["order"]
|
|
52
|
+
autocomplete_fields = ["account", "crm_recipient"]
|
|
53
|
+
show_change_link = True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@admin.register(Commission)
|
|
57
|
+
class CommissionModelAdmin(admin.ModelAdmin):
|
|
58
|
+
list_display = [
|
|
59
|
+
"account",
|
|
60
|
+
"crm_recipient",
|
|
61
|
+
"portfolio_role_recipient",
|
|
62
|
+
"account_role_type_recipient",
|
|
63
|
+
"order",
|
|
64
|
+
"net_commission",
|
|
65
|
+
"commission_type",
|
|
66
|
+
"is_hidden",
|
|
67
|
+
"exclusion_rule_account_role_type",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
autocomplete_fields = ["account", "crm_recipient"]
|
|
71
|
+
|
|
72
|
+
inlines = [CommissionRoleTabularInline, CommissionRuleTabularInline]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@admin.register(CommissionExclusionRule)
|
|
76
|
+
class CommissionExclusionRuleAdmin(admin.ModelAdmin):
|
|
77
|
+
list_display = [
|
|
78
|
+
"product",
|
|
79
|
+
"commission_type",
|
|
80
|
+
"account_role_type",
|
|
81
|
+
"timespan",
|
|
82
|
+
"overriding_percent",
|
|
83
|
+
"overriding_net_or_gross_commission",
|
|
84
|
+
]
|
|
85
|
+
autocomplete_fields = ["product", "account_role_type"]
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from django.db.models import Case, OuterRef, Subquery, Value, When
|
|
7
|
+
from django.db.models.functions import Coalesce
|
|
8
|
+
from wbcommission.models import CommissionType, Rebate
|
|
9
|
+
from wbcore.contrib.currency.models import CurrencyFXRates
|
|
10
|
+
from wbfdm.models import InstrumentPrice
|
|
11
|
+
from wbportfolio.models import Fees
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MarginalityCalculator:
|
|
15
|
+
FEE_MAP = {
|
|
16
|
+
"MANAGEMENT": "management",
|
|
17
|
+
"PERFORMANCE": "performance",
|
|
18
|
+
"PERFORMANCE_CRYSTALIZED": "performance",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def __init__(self, products, from_date: date, to_date: date):
|
|
22
|
+
products = products.annotate(
|
|
23
|
+
fx_rate=Coalesce(
|
|
24
|
+
Subquery(
|
|
25
|
+
CurrencyFXRates.objects.filter(
|
|
26
|
+
currency=OuterRef("currency"), date=OuterRef("last_valuation_date")
|
|
27
|
+
).values("value")[:1]
|
|
28
|
+
),
|
|
29
|
+
Decimal(1.0),
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.fx_rates = (
|
|
34
|
+
pd.DataFrame(products.values_list("id", "fx_rate"), columns=["id", "fx_rate"])
|
|
35
|
+
.set_index("id")["fx_rate"]
|
|
36
|
+
.astype(float)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# compute net marginality
|
|
40
|
+
self.df_aum = pd.DataFrame(
|
|
41
|
+
InstrumentPrice.objects.annotate_base_data()
|
|
42
|
+
.filter(instrument__in=products, date__gte=from_date, date__lte=to_date)
|
|
43
|
+
.values_list("calculated", "net_value_usd", "date", "outstanding_shares", "instrument"),
|
|
44
|
+
columns=["calculated", "net_value_usd", "date", "outstanding_shares", "instrument"],
|
|
45
|
+
).rename(columns={"instrument": "id"})
|
|
46
|
+
self.df_aum["date"] = pd.to_datetime(self.df_aum["date"])
|
|
47
|
+
self.df_aum = (
|
|
48
|
+
self.df_aum.sort_values(by="calculated")
|
|
49
|
+
.groupby(["id", "date"])
|
|
50
|
+
.agg({"net_value_usd": "last", "outstanding_shares": "first"})
|
|
51
|
+
)
|
|
52
|
+
self.df_aum = (self.df_aum.net_value_usd * self.df_aum.outstanding_shares).astype(float)
|
|
53
|
+
self.df_aum = self.df_aum.reindex(
|
|
54
|
+
pd.MultiIndex.from_product(
|
|
55
|
+
[
|
|
56
|
+
self.df_aum.index.levels[0],
|
|
57
|
+
pd.date_range(
|
|
58
|
+
self.df_aum.index.get_level_values("date").min(),
|
|
59
|
+
self.df_aum.index.get_level_values("date").max(),
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
names=["id", "date"],
|
|
63
|
+
),
|
|
64
|
+
method="ffill",
|
|
65
|
+
).dropna()
|
|
66
|
+
|
|
67
|
+
# 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
|
+
fees = Fees.valid_objects.filter(
|
|
70
|
+
transaction_date__lte=to_date,
|
|
71
|
+
transaction_date__gte=from_date,
|
|
72
|
+
transaction_subtype__in=self.FEE_MAP.keys(),
|
|
73
|
+
linked_product__in=products,
|
|
74
|
+
).annotate(
|
|
75
|
+
fee_type=Case(
|
|
76
|
+
*[When(transaction_subtype=k, then=Value(v)) for k, v in self.FEE_MAP.items()],
|
|
77
|
+
default=Value("management"),
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
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"})
|
|
84
|
+
self.df_fees["date"] = pd.to_datetime(self.df_fees["date"])
|
|
85
|
+
|
|
86
|
+
self.df_fees = (
|
|
87
|
+
self.df_fees[["fee_type", "total_value", "id", "date"]]
|
|
88
|
+
.pivot_table(index=["id", "date"], columns="fee_type", values="total_value", aggfunc="sum")
|
|
89
|
+
.astype("float")
|
|
90
|
+
.round(4)
|
|
91
|
+
)
|
|
92
|
+
self.df_fees["total"] = self.df_fees.sum(axis=1)
|
|
93
|
+
self.df_fees = self.df_fees.reindex(self.df_aum.index, fill_value=0)
|
|
94
|
+
self.df_fees = self._rolling_average_monday(self.df_fees)
|
|
95
|
+
|
|
96
|
+
# Build the fees dataframe where product id is the index and colum are the every fees type available and value are the amount.
|
|
97
|
+
self.df_rebates = pd.DataFrame(
|
|
98
|
+
Rebate.objects.filter(date__gte=from_date, date__lte=to_date, product__in=products).values_list(
|
|
99
|
+
"product", "value", "date", "commission_type__key"
|
|
100
|
+
),
|
|
101
|
+
columns=["product", "value", "date", "commission_type__key"],
|
|
102
|
+
).rename(columns={"product": "id"})
|
|
103
|
+
self.df_rebates["date"] = pd.to_datetime(self.df_rebates["date"])
|
|
104
|
+
|
|
105
|
+
self.df_rebates = (
|
|
106
|
+
pd.pivot_table(
|
|
107
|
+
self.df_rebates,
|
|
108
|
+
index=["id", "date"],
|
|
109
|
+
columns="commission_type__key",
|
|
110
|
+
values="value",
|
|
111
|
+
aggfunc="sum",
|
|
112
|
+
fill_value=0,
|
|
113
|
+
)
|
|
114
|
+
.astype("float")
|
|
115
|
+
.round(4)
|
|
116
|
+
)
|
|
117
|
+
self.df_rebates = self.df_rebates.reindex(self.df_aum.index, fill_value=0)
|
|
118
|
+
self.df_rebates["total"] = self.df_rebates.sum(axis=1)
|
|
119
|
+
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)
|
|
123
|
+
self._set_basics_statistics()
|
|
124
|
+
|
|
125
|
+
def _set_basics_statistics(self):
|
|
126
|
+
groupby_fees = self.df_fees.groupby(level=0).sum(numeric_only=True)
|
|
127
|
+
groupby_rebates = self.df_rebates.groupby(level=0).sum(numeric_only=True)
|
|
128
|
+
for key in [*CommissionType.objects.values_list("key", flat=True), "total"]:
|
|
129
|
+
fees = groupby_fees.get(key, self.empty_column)
|
|
130
|
+
rebates = groupby_rebates.get(key, self.empty_column)
|
|
131
|
+
fees_usd = fees * self.fx_rates
|
|
132
|
+
rebates_usd = rebates * self.fx_rates
|
|
133
|
+
marginality = fees - rebates
|
|
134
|
+
marginality_usd = fees_usd - rebates_usd
|
|
135
|
+
marginality_percent = (fees - rebates) / fees.replace(0, np.nan)
|
|
136
|
+
marginality_percent_usd = (fees_usd - rebates_usd) / fees_usd.replace(0, np.nan)
|
|
137
|
+
|
|
138
|
+
setattr(self, f"{key}_fees", fees.rename(f"{key}_fees"))
|
|
139
|
+
setattr(self, f"{key}_rebates", rebates.rename(f"{key}_rebates"))
|
|
140
|
+
setattr(self, f"{key}_fees_usd", fees_usd.rename(f"{key}_fees_usd"))
|
|
141
|
+
setattr(self, f"{key}_rebates_usd", rebates_usd.rename(f"{key}_rebates_usd"))
|
|
142
|
+
setattr(self, f"{key}_marginality", marginality.rename(f"{key}_marginality"))
|
|
143
|
+
setattr(self, f"{key}_marginality_usd", marginality_usd.rename(f"{key}_marginality_usd"))
|
|
144
|
+
setattr(self, f"{key}_marginality_percent", marginality_percent.rename(f"{key}_marginality_percent"))
|
|
145
|
+
setattr(
|
|
146
|
+
self,
|
|
147
|
+
f"{key}_marginality_percent_usd",
|
|
148
|
+
marginality_percent_usd.rename(f"{key}_marginality_percent_usd"),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _rolling_average_monday(self, df):
|
|
152
|
+
"""
|
|
153
|
+
This utility method take a dataframe and assum the values on Mondays are accumulated over the weekend. So we need to average every Saturday, Sunday and Monday together.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
monday = df[df.index.get_level_values("date").weekday == 0] / 3
|
|
157
|
+
monday = monday.reindex(df.index, method="bfill")
|
|
158
|
+
df[df.index.get_level_values("date").weekday.isin([5, 6, 0])] = monday[
|
|
159
|
+
df.index.get_level_values("date").weekday.isin([5, 6, 0])
|
|
160
|
+
]
|
|
161
|
+
return df
|
|
162
|
+
|
|
163
|
+
def get_net_marginality(self, type: str) -> pd.Series:
|
|
164
|
+
return (
|
|
165
|
+
((self.df_fees.get(type, self.empty_column) - self.df_rebates.get(type, self.empty_column)) / self.df_aum)
|
|
166
|
+
.groupby("id")
|
|
167
|
+
.mean()
|
|
168
|
+
) * 360
|
|
169
|
+
|
|
170
|
+
def get_aggregated_net_marginality(self, type: str) -> pd.Series:
|
|
171
|
+
# we compute the total net marginality for management for the aggregate function
|
|
172
|
+
total_aum = self.df_aum.groupby(level=1).sum()
|
|
173
|
+
return (
|
|
174
|
+
(
|
|
175
|
+
(
|
|
176
|
+
self.df_fees.get(type, self.empty_column).groupby(level=1).sum()
|
|
177
|
+
- self.df_rebates.get(type, self.empty_column).groupby(level=1).sum()
|
|
178
|
+
)
|
|
179
|
+
/ total_aum
|
|
180
|
+
).mean(axis=0)
|
|
181
|
+
) * 360
|
wbcommission/apps.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
import factory
|
|
4
|
+
from psycopg.types.range import DateRange
|
|
5
|
+
from wbcommission.models.commission import (
|
|
6
|
+
Commission,
|
|
7
|
+
CommissionExclusionRule,
|
|
8
|
+
CommissionRole,
|
|
9
|
+
CommissionType,
|
|
10
|
+
)
|
|
11
|
+
from wbportfolio.models.roles import PortfolioRole
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommissionTypeFactory(factory.django.DjangoModelFactory):
|
|
15
|
+
name = "MANAGEMENT"
|
|
16
|
+
key = factory.LazyAttribute(lambda x: x.name.lower())
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
model = CommissionType
|
|
20
|
+
django_get_or_create = ["key"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CommissionFactory(factory.django.DjangoModelFactory):
|
|
24
|
+
account = factory.SubFactory("wbcrm.factories.AccountFactory")
|
|
25
|
+
crm_recipient = factory.SubFactory("wbcore.contrib.directory.factories.entries.EntryFactory")
|
|
26
|
+
|
|
27
|
+
portfolio_role_recipient = None
|
|
28
|
+
account_role_type_recipient = None
|
|
29
|
+
order = 0
|
|
30
|
+
commission_type = factory.SubFactory(CommissionTypeFactory)
|
|
31
|
+
net_commission = True
|
|
32
|
+
is_hidden = False
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
model = Commission
|
|
36
|
+
|
|
37
|
+
@factory.post_generation
|
|
38
|
+
def rule_timespan(self, create, extracted, **kwargs):
|
|
39
|
+
if not create:
|
|
40
|
+
return
|
|
41
|
+
if extracted:
|
|
42
|
+
v = self.rules.first()
|
|
43
|
+
v.timespan = extracted
|
|
44
|
+
v.save()
|
|
45
|
+
|
|
46
|
+
@factory.post_generation
|
|
47
|
+
def rule_aum(self, create, extracted, **kwargs):
|
|
48
|
+
if not create:
|
|
49
|
+
return
|
|
50
|
+
if extracted:
|
|
51
|
+
v = self.rules.first()
|
|
52
|
+
v.assets_under_management_range = extracted
|
|
53
|
+
v.save()
|
|
54
|
+
|
|
55
|
+
@factory.post_generation
|
|
56
|
+
def rule_percent(self, create, extracted, **kwargs):
|
|
57
|
+
if not create:
|
|
58
|
+
return
|
|
59
|
+
if extracted:
|
|
60
|
+
v = self.rules.first()
|
|
61
|
+
v.percent = extracted
|
|
62
|
+
v.save()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PortfolioRoleCommissionFactory(CommissionFactory):
|
|
66
|
+
crm_recipient = None
|
|
67
|
+
account_role_type_recipient = None
|
|
68
|
+
portfolio_role_recipient = factory.Iterator([role_choice[0] for role_choice in PortfolioRole.RoleType.choices])
|
|
69
|
+
|
|
70
|
+
class Meta:
|
|
71
|
+
model = Commission
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AccountTypeRoleCommissionFactory(CommissionFactory):
|
|
75
|
+
crm_recipient = None
|
|
76
|
+
portfolio_role_recipient = None
|
|
77
|
+
account_role_type_recipient = factory.SubFactory("wbcrm.factories.AccountRoleTypeFactory")
|
|
78
|
+
|
|
79
|
+
class Meta:
|
|
80
|
+
model = Commission
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CommissionRoleFactory(factory.django.DjangoModelFactory):
|
|
84
|
+
commission = factory.SubFactory(CommissionFactory)
|
|
85
|
+
person = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
|
|
86
|
+
|
|
87
|
+
class Meta:
|
|
88
|
+
model = CommissionRole
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CommissionExclusionRuleFactory(factory.django.DjangoModelFactory):
|
|
92
|
+
product = factory.SubFactory("wbportfolio.factories.products.ProductFactory")
|
|
93
|
+
commission_type = factory.SubFactory(CommissionTypeFactory)
|
|
94
|
+
overriding_percent = factory.Faker("pydecimal", min_value=0, max_value=1, right_digits=2)
|
|
95
|
+
overriding_net_or_gross_commission = "DEFAULT"
|
|
96
|
+
account_role_type = None
|
|
97
|
+
timespan = DateRange(date.min, date.max)
|
|
98
|
+
|
|
99
|
+
class Meta:
|
|
100
|
+
model = CommissionExclusionRule
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
from wbcommission.models import Rebate
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RebateFactory(factory.django.DjangoModelFactory):
|
|
6
|
+
date = factory.Faker("date_object")
|
|
7
|
+
|
|
8
|
+
commission = factory.SubFactory("wbcommission.factories.CommissionFactory")
|
|
9
|
+
account = factory.LazyAttribute(lambda o: o.commission.account)
|
|
10
|
+
commission_type = factory.LazyAttribute(lambda o: o.commission.commission_type)
|
|
11
|
+
product = factory.SubFactory("wbportfolio.factories.ProductFactory")
|
|
12
|
+
recipient = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
|
|
13
|
+
value = factory.Faker("pydecimal", positive=True, max_value=1000000, right_digits=4)
|
|
14
|
+
|
|
15
|
+
class Meta:
|
|
16
|
+
model = Rebate
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from wbcommission.models import Rebate
|
|
2
|
+
from wbcommission.models.rebate import RebateGroupbyChoice
|
|
3
|
+
from wbcore import filters as wb_filters
|
|
4
|
+
from wbcore.contrib.directory.models import Company
|
|
5
|
+
from wbcore.filters.defaults import current_quarter_date_range
|
|
6
|
+
from wbcore.pandas.filterset import PandasFilterSetMixin
|
|
7
|
+
from wbfdm.models import ClassificationGroup
|
|
8
|
+
from wbfdm.preferences import get_default_classification_group
|
|
9
|
+
from wbportfolio.filters.transactions.claim import CommissionBaseFilterSet
|
|
10
|
+
from wbportfolio.models import Product
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RebateDateFilter(CommissionBaseFilterSet):
|
|
14
|
+
date = wb_filters.DateRangeFilter(
|
|
15
|
+
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
16
|
+
label="Date Range",
|
|
17
|
+
required=True,
|
|
18
|
+
clearable=False,
|
|
19
|
+
default=current_quarter_date_range,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
class Meta:
|
|
23
|
+
model = Rebate
|
|
24
|
+
fields = {
|
|
25
|
+
"date": ["exact"],
|
|
26
|
+
"account": ["exact"],
|
|
27
|
+
"product": ["exact"],
|
|
28
|
+
"recipient": ["exact"],
|
|
29
|
+
"commission_type": ["exact"],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RebateGroupByFilter(PandasFilterSetMixin, RebateDateFilter):
|
|
34
|
+
group_by = wb_filters.ChoiceFilter(
|
|
35
|
+
label="Group By",
|
|
36
|
+
choices=RebateGroupbyChoice.choices(),
|
|
37
|
+
default=RebateGroupbyChoice.ACCOUNT.name, # typing: ignore
|
|
38
|
+
method=lambda queryset, label, value: queryset,
|
|
39
|
+
clearable=False,
|
|
40
|
+
required=True,
|
|
41
|
+
)
|
|
42
|
+
groupby_classification_group = wb_filters.ModelChoiceFilter(
|
|
43
|
+
default=lambda k, v, f: get_default_classification_group().id,
|
|
44
|
+
method=lambda queryset, label, value: queryset,
|
|
45
|
+
label="Group by Classification Group",
|
|
46
|
+
queryset=ClassificationGroup.objects.all(),
|
|
47
|
+
endpoint=ClassificationGroup.get_representation_endpoint(),
|
|
48
|
+
value_key=ClassificationGroup.get_representation_value_key(),
|
|
49
|
+
label_key=ClassificationGroup.get_representation_label_key(),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
class Meta:
|
|
53
|
+
model = Rebate
|
|
54
|
+
fields = {"recipient": ["exact"]}
|
|
55
|
+
df_fields = {
|
|
56
|
+
"management__gte": wb_filters.NumberFilter(
|
|
57
|
+
label="Management Fees (USD)", lookup_expr="gte", field_name="management"
|
|
58
|
+
),
|
|
59
|
+
"management__lte": wb_filters.NumberFilter(
|
|
60
|
+
label="Management Fees (USD)", lookup_expr="lte", field_name="management"
|
|
61
|
+
),
|
|
62
|
+
"performance__gte": wb_filters.NumberFilter(
|
|
63
|
+
label="Performance Fees (USD)", lookup_expr="gte", field_name="performance"
|
|
64
|
+
),
|
|
65
|
+
"performance__lte": wb_filters.NumberFilter(
|
|
66
|
+
label="Performance Fees (USD)", lookup_expr="lte", field_name="performance"
|
|
67
|
+
),
|
|
68
|
+
"total_rebate__gte": wb_filters.NumberFilter(
|
|
69
|
+
label="Total Fees (USD)", lookup_expr="gte", field_name="total_rebate"
|
|
70
|
+
),
|
|
71
|
+
"total_rebate__lte": wb_filters.NumberFilter(
|
|
72
|
+
label="Total Fees (USD)", lookup_expr="lte", field_name="total_rebate"
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CustomerRebateGroupByFilter(RebateGroupByFilter):
|
|
78
|
+
product = wb_filters.ModelChoiceFilter(
|
|
79
|
+
label="Product",
|
|
80
|
+
queryset=Product.objects.all(),
|
|
81
|
+
endpoint=Product.get_representation_endpoint(),
|
|
82
|
+
value_key=Product.get_representation_value_key(),
|
|
83
|
+
label_key="{{title}} {{currency_repr}} - {{isin}}",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
class Meta:
|
|
87
|
+
model = Rebate
|
|
88
|
+
fields = {
|
|
89
|
+
# "price_start" : ["gte", "exact", "lte"],
|
|
90
|
+
# "price_end" : ["gte", "exact", "lte"],
|
|
91
|
+
"account": ["exact"],
|
|
92
|
+
"product": ["exact"],
|
|
93
|
+
"recipient": ["exact"],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class RebateMarginalityFilter(PandasFilterSetMixin, wb_filters.FilterSet):
|
|
98
|
+
date = wb_filters.DateRangeFilter(
|
|
99
|
+
label="Date Range",
|
|
100
|
+
method=wb_filters.DateRangeFilter.base_date_range_filter_method,
|
|
101
|
+
required=True,
|
|
102
|
+
clearable=False,
|
|
103
|
+
default=current_quarter_date_range,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
bank = wb_filters.ModelMultipleChoiceFilter(
|
|
107
|
+
label="Bank",
|
|
108
|
+
queryset=Company.bank_objects.all(),
|
|
109
|
+
endpoint=Company.get_representation_endpoint(),
|
|
110
|
+
filter_params={"bank_product": True},
|
|
111
|
+
value_key=Company.get_representation_value_key(),
|
|
112
|
+
label_key=Company.get_representation_label_key(),
|
|
113
|
+
method="filter_bank",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def filter_bank(self, queryset, name, value):
|
|
117
|
+
if value:
|
|
118
|
+
return queryset.filter(bank__in=value)
|
|
119
|
+
return queryset
|
|
120
|
+
|
|
121
|
+
class Meta:
|
|
122
|
+
model = Product
|
|
123
|
+
fields = {}
|
|
124
|
+
df_fields = {
|
|
125
|
+
"management_fees__gte": wb_filters.NumberFilter(
|
|
126
|
+
field_name="management_fees", lookup_expr="gte", label="Sum Management Fees"
|
|
127
|
+
),
|
|
128
|
+
"management_rebates__gte": wb_filters.NumberFilter(
|
|
129
|
+
field_name="management_rebates", lookup_expr="gte", label="Sum Management Rebates"
|
|
130
|
+
),
|
|
131
|
+
"management_marginality__gte": wb_filters.NumberFilter(
|
|
132
|
+
field_name="management_marginality", lookup_expr="gte", label="Marginality Management"
|
|
133
|
+
),
|
|
134
|
+
"performance_fees__gte": wb_filters.NumberFilter(
|
|
135
|
+
field_name="performance_fees", lookup_expr="gte", label="Sum Performance Fees"
|
|
136
|
+
),
|
|
137
|
+
"performance_rebates__gte": wb_filters.NumberFilter(
|
|
138
|
+
field_name="performance_rebates", lookup_expr="gte", label="Sum Performance Rebates"
|
|
139
|
+
),
|
|
140
|
+
"performance_marginality__gte": wb_filters.NumberFilter(
|
|
141
|
+
field_name="performance_marginality", lookup_expr="gte", label="Marginality Performance"
|
|
142
|
+
),
|
|
143
|
+
"total_fees__gte": wb_filters.NumberFilter(field_name="total_fees", lookup_expr="gte", label="Total Fees"),
|
|
144
|
+
"total_rebates__gte": wb_filters.NumberFilter(
|
|
145
|
+
field_name="total_rebates", lookup_expr="gte", label="Total Rebates"
|
|
146
|
+
),
|
|
147
|
+
"total_marginality_percent__gte": wb_filters.NumberFilter(
|
|
148
|
+
field_name="total_marginality_percent", lookup_expr="gte", label="Total Marginality"
|
|
149
|
+
),
|
|
150
|
+
"total_fees_usd__gte": wb_filters.NumberFilter(
|
|
151
|
+
field_name="total_fees_usd", lookup_expr="gte", label="Total Fees"
|
|
152
|
+
),
|
|
153
|
+
"total_rebates_usd__gte": wb_filters.NumberFilter(
|
|
154
|
+
field_name="total_rebates_usd", lookup_expr="gte", label="Total Rebates"
|
|
155
|
+
),
|
|
156
|
+
"management_fees__lte": wb_filters.NumberFilter(
|
|
157
|
+
field_name="management_fees", lookup_expr="lte", label="Sum Management Fees"
|
|
158
|
+
),
|
|
159
|
+
"management_rebates__lte": wb_filters.NumberFilter(
|
|
160
|
+
field_name="management_rebates", lookup_expr="lte", label="Sum Management Rebates"
|
|
161
|
+
),
|
|
162
|
+
"management_marginality__lte": wb_filters.NumberFilter(
|
|
163
|
+
field_name="management_marginality", lookup_expr="lte", label="Marginality Management"
|
|
164
|
+
),
|
|
165
|
+
"performance_fees__lte": wb_filters.NumberFilter(
|
|
166
|
+
field_name="performance_fees", lookup_expr="lte", label="Sum Performance Fees"
|
|
167
|
+
),
|
|
168
|
+
"performance_rebates__lte": wb_filters.NumberFilter(
|
|
169
|
+
field_name="performance_rebates", lookup_expr="lte", label="Sum Performance Rebates"
|
|
170
|
+
),
|
|
171
|
+
"performance_marginality__lte": wb_filters.NumberFilter(
|
|
172
|
+
field_name="performance_marginality", lookup_expr="lte", label="Marginality Performance"
|
|
173
|
+
),
|
|
174
|
+
"total_fees__lte": wb_filters.NumberFilter(field_name="total_fees", lookup_expr="lte", label="Total Fees"),
|
|
175
|
+
"total_rebates__lte": wb_filters.NumberFilter(
|
|
176
|
+
field_name="total_rebates", lookup_expr="lte", label="Total Rebates"
|
|
177
|
+
),
|
|
178
|
+
"total_marginality_percent__lte": wb_filters.NumberFilter(
|
|
179
|
+
field_name="total_marginality_percent", lookup_expr="lte", label="Total Marginality"
|
|
180
|
+
),
|
|
181
|
+
"total_fees_usd__lte": wb_filters.NumberFilter(
|
|
182
|
+
field_name="total_fees_usd", lookup_expr="lte", label="Total Fees"
|
|
183
|
+
),
|
|
184
|
+
"total_rebates_usd__lte": wb_filters.NumberFilter(
|
|
185
|
+
field_name="total_rebates_usd", lookup_expr="lte", label="Total Rebates"
|
|
186
|
+
),
|
|
187
|
+
}
|