Implement link group detail screen with profit simulation
This commit is contained in:
parent
bbd25c7450
commit
8c490d6ee3
|
@ -252,4 +252,9 @@ urlpatterns = [
|
|||
linkgroups.LinkGroupDelete.as_view(),
|
||||
name="linkgroup_delete",
|
||||
),
|
||||
path(
|
||||
"links/<str:type>/info/<str:pk>/",
|
||||
linkgroups.LinkGroupInfo.as_view(),
|
||||
name="linkgroup_info",
|
||||
),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
|
@ -268,8 +268,6 @@ class LinkGroupForm(RestrictedFormMixin, ModelForm):
|
|||
model = LinkGroup
|
||||
fields = (
|
||||
"name",
|
||||
"aggregators",
|
||||
"platforms",
|
||||
"platform_owner_cut_percentage",
|
||||
"requisition_owner_cut_percentage",
|
||||
"operator_cut_percentage",
|
||||
|
@ -278,27 +276,12 @@ class LinkGroupForm(RestrictedFormMixin, ModelForm):
|
|||
|
||||
help_texts = {
|
||||
"name": "The name of the link group.",
|
||||
"aggregators": "The aggregators to use.",
|
||||
"platforms": "The platforms to use.",
|
||||
"platform_owner_cut_percentage": "The percentage of the total profit of this group to give to the platform owners.",
|
||||
"requisition_owner_cut_percentage": "The percentage of the total profit of this group to give to the requisition owners.",
|
||||
"operator_cut_percentage": "The percentage of the total profit of this group to give to the operator.",
|
||||
"enabled": "Whether or not this link group is enabled.",
|
||||
}
|
||||
|
||||
platforms = forms.ModelMultipleChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=Meta.help_texts["platforms"],
|
||||
required=True,
|
||||
)
|
||||
aggregators = forms.ModelMultipleChoiceField(
|
||||
queryset=Aggregator.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=Meta.help_texts["aggregators"],
|
||||
required=True,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(LinkGroupForm, self).clean()
|
||||
platform_owner_cut_percentage = cleaned_data.get(
|
||||
|
|
|
@ -69,6 +69,24 @@ class LinkGroup(models.Model):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def payees(self):
|
||||
payees = {}
|
||||
for platform in self.platform_set.all():
|
||||
for payee in platform.payees.all():
|
||||
if "platform" not in payees:
|
||||
payees["platform"] = []
|
||||
payees["platform"].append(payee)
|
||||
|
||||
for aggregator in self.aggregator_set.all():
|
||||
agg_reqs = aggregator.requisition_set.all()
|
||||
for req in agg_reqs:
|
||||
for payee in req.payees.all():
|
||||
if "requisition" not in payees:
|
||||
payees["requisition"] = []
|
||||
payees["requisition"].append(payee)
|
||||
|
||||
return payees
|
||||
|
||||
|
||||
class Aggregator(models.Model):
|
||||
"""
|
||||
|
|
|
@ -173,6 +173,7 @@
|
|||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.grid-stack-item:hover .ui-resizable-handle {
|
||||
display: block !important;
|
||||
}
|
||||
|
@ -360,7 +361,7 @@
|
|||
{% block outer_content %}
|
||||
{% endblock %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="container is-widescreen">
|
||||
{% block content_wrapper %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<th>created</th>
|
||||
<th>institution</th>
|
||||
<th>accounts</th>
|
||||
<th>payees</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
{% for item in object_list %}
|
||||
|
@ -26,6 +27,11 @@
|
|||
<td>{{ item.created }}</td>
|
||||
<td>{{ item.institution_id }}</td>
|
||||
<td>{{ item.accounts|length }}</td>
|
||||
<td>
|
||||
{% for payee in item.requisition.payees.all %}
|
||||
{{ payee.name }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<th>user</th>
|
||||
<th>name</th>
|
||||
<th>service</th>
|
||||
<th>link group</th>
|
||||
<th>enabled</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
|
@ -32,6 +33,7 @@
|
|||
<td>{{ item.user }}</td>
|
||||
<td><a href="{% url 'reqs' type='page' pk=item.id %}">{{ item.name }}</a></td>
|
||||
<td>{{ item.get_service_display }}</td>
|
||||
<td>{{ item.link_group|default_if_none:"—" }}</td>
|
||||
<td>
|
||||
{% if item.enabled %}
|
||||
<span class="icon">
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
<h1 class="title">Information for link group {{ linkgroup.name }}</h1>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h1 class="title is-4">Platforms</h1>
|
||||
{% include 'partials/platform-list.html' with object_list=platforms type=type %}
|
||||
<h1 class="title is-4">Aggregators</h1>
|
||||
{% include 'partials/aggregator-list.html' with object_list=aggregators type=type %}
|
||||
|
||||
<h1 class="title is-4">Requisitions</h1>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<th>id</th>
|
||||
<th>aggregator</th>
|
||||
<th>payees</th>
|
||||
</thead>
|
||||
{% for item in requisitions %}
|
||||
<tr>
|
||||
<td>{{ item.id|truncatechars:20 }}</td>
|
||||
<td>{{ item.aggregator.name }}</td>
|
||||
<td>
|
||||
{% for payee in item.payees.all %}
|
||||
{{ payee.name }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h1 class="title is-4">Platform payees</h1>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<th>name</th>
|
||||
<th>address</th>
|
||||
</thead>
|
||||
{% for item in linkgroup.payees.platform %}
|
||||
<tr>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.address }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<h1 class="title is-4">Requisition payees</h1>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<th>name</th>
|
||||
<th>address</th>
|
||||
</thead>
|
||||
{% for item in linkgroup.payees.requisition %}
|
||||
<tr>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.address }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<h1 class="title is-4">Split</h1>
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<th>attribute</th>
|
||||
<th>%</th>
|
||||
<th>graphic</th>
|
||||
|
||||
</thead>
|
||||
<tr>
|
||||
<td>platform</td>
|
||||
<td>{{ linkgroup.platform_owner_cut_percentage }}</td>
|
||||
<td>
|
||||
<progress class="progress" value="{{ linkgroup.platform_owner_cut_percentage }}" max="100"></progress>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>requisition</td>
|
||||
<td>{{ linkgroup.requisition_owner_cut_percentage }}</td>
|
||||
<td>
|
||||
<progress class="progress" value="{{ linkgroup.requisition_owner_cut_percentage }}" max="100"></progress>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>operator</td>
|
||||
<td>{{ linkgroup.operator_cut_percentage }}</td>
|
||||
<td>
|
||||
<progress class="progress" value="{{ linkgroup.operator_cut_percentage }}" max="100"></progress>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="title">Simulation for $1000</h1>
|
||||
<p>Assuming equal throughput for platforms and requisitions.</p>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="content">
|
||||
<ul>
|
||||
{% for key, list in simulation.items %}
|
||||
<li>
|
||||
{{ key.0 }}: ${{ key.1 }}
|
||||
<ul>
|
||||
{% for item in list %}
|
||||
<li>${{ item.amount }} to {{ item.name }} at <code>{{ item.address }}</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="title is-4">Total for wallets</h1>
|
||||
<div class="box">
|
||||
{% for wallet, pay_list in pay_list.items %}
|
||||
{{ wallet }}: ${{ pay_list.amount }}
|
||||
<progress class="progress" value="{{ pay_list.amount }}" max="1000"></progress>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="box">
|
||||
{% for key, list in simulation.items %}
|
||||
<strong>{{ key.0 }}: ${{ key.1 }}</strong>
|
||||
<progress class="progress" value="{{ key.1 }}" max="1000"></progress>
|
||||
{% for item in list %}
|
||||
<em>{{ item.name }}: ${{ item.amount }}</em><progress class="progress" value="{{ item.amount }}" max="{{ item.max }}"></progress>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -76,6 +76,15 @@
|
|||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<a href="{% url 'linkgroup_info' type='page' pk=item.id %}"><button
|
||||
class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% load cache %}
|
||||
{% load joinsep %}
|
||||
{% load cachalot cache %}
|
||||
{% get_last_invalidation 'core.Platform' as last %}
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
|
@ -15,6 +16,8 @@
|
|||
<th>user</th>
|
||||
<th>name</th>
|
||||
<th>service</th>
|
||||
<th>payees</th>
|
||||
<th>link group</th>
|
||||
<th>enabled</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
|
@ -32,6 +35,12 @@
|
|||
<td>{{ item.user }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.get_service_display }}</td>
|
||||
<td>
|
||||
{% for payee in item.payees.all %}
|
||||
{{ payee.name }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ item.link_group|default_if_none:"—" }}</td>
|
||||
<td>
|
||||
{% if item.enabled %}
|
||||
<span class="icon">
|
||||
|
|
|
@ -14,7 +14,7 @@ from two_factor.views.mixins import OTPRequiredMixin
|
|||
|
||||
from core.clients.aggregators.nordigen import NordigenClient
|
||||
from core.forms import AggregatorForm
|
||||
from core.models import Aggregator
|
||||
from core.models import Aggregator, Requisition
|
||||
from core.util import logs
|
||||
from core.views.helpers import synchronize_async_helper
|
||||
|
||||
|
@ -95,6 +95,16 @@ class ReqsList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
|||
|
||||
run = synchronize_async_helper(NordigenClient(aggregator))
|
||||
reqs = synchronize_async_helper(run.get_requisitions())
|
||||
for req in reqs:
|
||||
# Add in Requisition object
|
||||
requisition_id = req["id"]
|
||||
requisition = Requisition.objects.filter(
|
||||
user=self.request.user,
|
||||
aggregator=aggregator,
|
||||
requisition_id=requisition_id,
|
||||
).first()
|
||||
if requisition:
|
||||
req["requisition"] = requisition
|
||||
return reqs
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,107 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from mixins.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
|
||||
from django.http import HttpResponse
|
||||
from mixins.views import (
|
||||
ObjectCreate,
|
||||
ObjectDelete,
|
||||
ObjectList,
|
||||
ObjectRead,
|
||||
ObjectUpdate,
|
||||
)
|
||||
from rest_framework import status
|
||||
from two_factor.views.mixins import OTPRequiredMixin
|
||||
|
||||
from core.forms import LinkGroupForm
|
||||
from core.models import LinkGroup
|
||||
from core.models import Aggregator, LinkGroup, Platform, Requisition
|
||||
|
||||
|
||||
class LinkGroupInfo(LoginRequiredMixin, OTPRequiredMixin, ObjectRead):
|
||||
context_object_name_singular = "linkgroup"
|
||||
context_object_name = "linkgroups"
|
||||
detail_template = "partials/linkgroup-info.html"
|
||||
|
||||
def get_object(self, **kwargs):
|
||||
pk = self.kwargs.get("pk")
|
||||
linkgroup = LinkGroup.objects.filter(
|
||||
user=self.request.user,
|
||||
id=pk,
|
||||
).first()
|
||||
if not linkgroup:
|
||||
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
||||
return linkgroup
|
||||
|
||||
def get_context_data(self):
|
||||
context = super().get_context_data()
|
||||
|
||||
aggregators = Aggregator.objects.filter(
|
||||
user=self.request.user,
|
||||
link_group=self.object,
|
||||
)
|
||||
platforms = Platform.objects.filter(
|
||||
user=self.request.user,
|
||||
link_group=self.object,
|
||||
)
|
||||
requisitions = Requisition.objects.filter(
|
||||
user=self.request.user,
|
||||
aggregator__in=aggregators,
|
||||
)
|
||||
context["aggregators"] = aggregators
|
||||
context["platforms"] = platforms
|
||||
context["requisitions"] = requisitions
|
||||
context["linkgroup"] = self.object
|
||||
|
||||
payees = self.object.payees()
|
||||
|
||||
simulation = {}
|
||||
profit = 1000
|
||||
profit_platform = profit * (self.object.platform_owner_cut_percentage / 100)
|
||||
profit_requisition = profit * (
|
||||
self.object.requisition_owner_cut_percentage / 100
|
||||
)
|
||||
profit_operator = profit * (self.object.operator_cut_percentage / 100)
|
||||
|
||||
pay_list = {}
|
||||
|
||||
platform_pay_list = []
|
||||
for payee in payees["platform"]:
|
||||
cast = {
|
||||
"name": payee.name,
|
||||
"address": payee.address,
|
||||
"amount": profit_platform / len(payees["platform"]),
|
||||
"max": profit_platform,
|
||||
}
|
||||
if payee not in pay_list:
|
||||
pay_list[payee] = {}
|
||||
if "amount" in pay_list[payee]:
|
||||
pay_list[payee]["amount"] += cast["amount"]
|
||||
else:
|
||||
pay_list[payee] = dict(cast)
|
||||
platform_pay_list.append(cast)
|
||||
|
||||
requisition_pay_list = []
|
||||
for payee in payees["requisition"]:
|
||||
cast = {
|
||||
"name": payee.name,
|
||||
"address": payee.address,
|
||||
"amount": profit_requisition / len(payees["requisition"]),
|
||||
"max": profit_requisition,
|
||||
}
|
||||
if payee not in pay_list:
|
||||
pay_list[payee] = {}
|
||||
if "amount" in pay_list[payee]:
|
||||
pay_list[payee]["amount"] += cast["amount"]
|
||||
else:
|
||||
pay_list[payee] = dict(cast)
|
||||
requisition_pay_list.append(cast)
|
||||
|
||||
simulation[("Platform", profit_platform)] = platform_pay_list
|
||||
simulation[("Requisition", profit_requisition)] = requisition_pay_list
|
||||
simulation[("Operator", profit_operator)] = []
|
||||
|
||||
context["pay_list"] = pay_list
|
||||
|
||||
context["simulation"] = simulation
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class LinkGroupList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||
|
|
Loading…
Reference in New Issue