Begin implementing per-requisition configuration
This commit is contained in:
parent
4211d3c10a
commit
4d4406643f
|
@ -110,6 +110,11 @@ urlpatterns = [
|
|||
banks.BanksCurrencies.as_view(),
|
||||
name="currencies",
|
||||
),
|
||||
path(
|
||||
"banks/<str:type>/req/<str:aggregator_id>/<str:req_id>/",
|
||||
banks.BanksRequisitionUpdate.as_view(),
|
||||
name="requisition_update",
|
||||
),
|
||||
# Bank balances
|
||||
path(
|
||||
"banks/<str:type>/balances/",
|
||||
|
|
|
@ -19,6 +19,7 @@ class AggregatorClient(ABC):
|
|||
# #if account["account_id"] in self.banks
|
||||
# }
|
||||
# For each bank
|
||||
print("ACCOUNT INFOS", account_infos)
|
||||
for bank, accounts in account_infos.items():
|
||||
# Iterate the accounts
|
||||
for index, account in enumerate(list(accounts)):
|
||||
|
@ -50,6 +51,8 @@ class AggregatorClient(ABC):
|
|||
self.instance.currencies = currencies
|
||||
self.instance.save()
|
||||
|
||||
print("INSTANCE ACCOUNT INFO", self.instance.account_info)
|
||||
|
||||
async def process_transactions(self, account_id, transactions):
|
||||
if not transactions:
|
||||
return False
|
||||
|
|
|
@ -1104,6 +1104,12 @@ class LocalPlatformClient(ABC):
|
|||
currency_account_info_map[currency]["recipient"] = account[
|
||||
"ownerName"
|
||||
]
|
||||
currency_account_info_map[currency]["aggregator_id"] = account[
|
||||
"aggregator_id"
|
||||
]
|
||||
currency_account_info_map[currency]["requisition_id"] = account[
|
||||
"requisition_id"
|
||||
]
|
||||
return (list(currency_account_info_map.keys()), currency_account_info_map)
|
||||
|
||||
def get_matching_account_details(self, currency, ad):
|
||||
|
@ -1200,12 +1206,21 @@ class LocalPlatformClient(ABC):
|
|||
if not payment_details:
|
||||
return False
|
||||
if real:
|
||||
aggregator_id = payment_details["aggregator_id"]
|
||||
requisition_id = payment_details["requisition_id"]
|
||||
req = self.instance.get_requisition(aggregator_id, requisition_id)
|
||||
if req:
|
||||
payment = req.payment_details
|
||||
else:
|
||||
payment = ad.payment_details_real
|
||||
else:
|
||||
payment = ad.payment_details
|
||||
|
||||
payment_text = ""
|
||||
for field, value in payment_details.items():
|
||||
if field in ["aggregator_id", "requisition_id"]:
|
||||
# Don't send these to the user
|
||||
continue
|
||||
formatted_name = field.replace("_", " ")
|
||||
formatted_name = formatted_name.capitalize()
|
||||
payment_text += f"* {formatted_name}: **{value}**"
|
||||
|
|
|
@ -11,6 +11,7 @@ from .models import (
|
|||
NotificationSettings,
|
||||
Platform,
|
||||
Provider,
|
||||
Requisition,
|
||||
User,
|
||||
)
|
||||
|
||||
|
@ -205,3 +206,17 @@ class AdForm(RestrictedFormMixin, ModelForm):
|
|||
help_text=Meta.help_texts["aggregators"],
|
||||
required=True,
|
||||
)
|
||||
|
||||
|
||||
class RequisitionForm(RestrictedFormMixin, ModelForm):
|
||||
class Meta:
|
||||
model = Requisition
|
||||
fields = (
|
||||
"payment_details",
|
||||
"transaction_source",
|
||||
)
|
||||
|
||||
help_texts = {
|
||||
"payment_details": "Shown once a user opens a trade.",
|
||||
"transaction_source": "Whether to check pending or booked transactions.",
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ class TransactionsCurrencyExchange(MyModel):
|
|||
|
||||
|
||||
class TXAccount(MyModel):
|
||||
iban: str
|
||||
iban: str | None
|
||||
bban: str | None
|
||||
|
||||
|
||||
|
@ -191,6 +191,7 @@ class TransactionsNested(MyModel):
|
|||
proprietaryBankTransactionCode: str | None
|
||||
internalTransactionId: str | None
|
||||
currencyExchange: TransactionsCurrencyExchange | None
|
||||
merchantCategoryCode: str | None
|
||||
|
||||
|
||||
class TransactionsBookedPending(MyModel):
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 4.1.7 on 2023-03-15 10:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0024_ad_send_reference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Requisition',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('requisition_id', models.CharField(max_length=255)),
|
||||
('payment_details', models.TextField()),
|
||||
('transaction_source', models.CharField(choices=[('booked', 'Booked'), ('pending', 'Pending')], max_length=255)),
|
||||
('aggregator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.aggregator')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.1.7 on 2023-03-15 10:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0025_requisition'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='requisition',
|
||||
name='transaction_source',
|
||||
field=models.CharField(choices=[('booked', 'Booked'), ('pending', 'Pending')], default='booked', max_length=255),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.1.7 on 2023-03-15 10:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0026_alter_requisition_transaction_source'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='requisition',
|
||||
name='payment_details',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -363,6 +363,16 @@ class Platform(models.Model):
|
|||
|
||||
return aggregators
|
||||
|
||||
def get_requisition(self, aggregator_id, requisition_id):
|
||||
"""
|
||||
Get a Requisition object with the provided values.
|
||||
"""
|
||||
requisition = Requisition.objects.filter(
|
||||
aggregator_id=aggregator_id,
|
||||
requisition_id=requisition_id,
|
||||
).first()
|
||||
return requisition
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
code = models.CharField(max_length=64)
|
||||
|
@ -493,12 +503,13 @@ class Requisition(models.Model):
|
|||
A requisition for an Aggregator
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
aggregator = models.ForeignKey(Aggregator, on_delete=models.CASCADE)
|
||||
requisition_id = models.CharField(max_length=255)
|
||||
payment_details = models.TextField()
|
||||
payment_details = models.TextField(null=True, blank=True)
|
||||
|
||||
transaction_source = models.CharField(
|
||||
max_length=255, choices=TRANSACTION_SOURCE_CHOICES
|
||||
max_length=255, choices=TRANSACTION_SOURCE_CHOICES, default="booked"
|
||||
)
|
||||
|
||||
# throughput = models.FloatField(default=0)
|
||||
|
|
|
@ -7,7 +7,18 @@
|
|||
{# cache 600 objects_banks_currencies request.user.id object_list type last #}
|
||||
|
||||
{% for bank, accounts in object_list.items %}
|
||||
<h1 class="title is-4">{{ bank.0 }} <code>{{ bank.1 }}</code></h1>
|
||||
<h1 class="title is-4">{{ bank.0 }} <code>{{ bank.1 }}</code>
|
||||
<a
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'requisition_update' type=type aggregator_id=bank.2 req_id=bank.1 %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#{{ type }}s-here"
|
||||
hx-swap="innerHTML">
|
||||
<span class="icon has-text-black" data-tooltip="Configure">
|
||||
<i class="fa-solid fa-wrench"></i>
|
||||
</span>
|
||||
</a>
|
||||
</h1>
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
hx-target="#{{ bank }}-table"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import logging
|
||||
|
||||
from core.clients.aggregator import AggregatorClient
|
||||
from core.models import Aggregator, Platform, User
|
||||
|
||||
|
||||
class AggregatorPlatformMixin:
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
self.user = User.objects.create_user(
|
||||
username="testuser", email="test@example.com", password="test"
|
||||
)
|
||||
self.aggregator = Aggregator.objects.create(
|
||||
user=self.user,
|
||||
name="Test",
|
||||
service="nordigen",
|
||||
secret_id="a",
|
||||
secret_key="a",
|
||||
)
|
||||
|
||||
self.agg_client = AggregatorClient()
|
||||
self.agg_client.instance = self.aggregator
|
||||
|
||||
self.platform = Platform.objects.create(
|
||||
user=self.user,
|
||||
name="Test",
|
||||
service="agora",
|
||||
token="a",
|
||||
password="a",
|
||||
otp_token="a",
|
||||
username="myuser",
|
||||
)
|
|
@ -0,0 +1,106 @@
|
|||
from django.test import TransactionTestCase
|
||||
|
||||
from core.clients.platforms.agora import AgoraClient
|
||||
from core.models import Ad, Asset, Provider
|
||||
from core.tests.helpers import AggregatorPlatformMixin
|
||||
|
||||
|
||||
class TestPlatform(AggregatorPlatformMixin, TransactionTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.aggregator.account_info = {
|
||||
"MONZO_MONZGB2L": [
|
||||
{
|
||||
"resourceId": "ssss",
|
||||
"currency": "GBP",
|
||||
"ownerName": "JSNOW LIMITED",
|
||||
"cashAccountType": "CACC",
|
||||
"status": "enabled",
|
||||
"maskedPan": None,
|
||||
"details": "Private Limited Company",
|
||||
"bban": "ssss",
|
||||
"name": None,
|
||||
"product": None,
|
||||
"bic": None,
|
||||
"recipient": "TODO",
|
||||
"account_id": "s-s-s-s-s",
|
||||
"aggregator_id": str(self.aggregator.id),
|
||||
"requisition_id": "3ba3e65d-f44c-4c4e-9e28-08cc080830f6",
|
||||
"account_number": {"sort_code": "04-00-04", "number": "00000002"},
|
||||
},
|
||||
{
|
||||
"resourceId": "ssss",
|
||||
"currency": "GBP",
|
||||
"ownerName": "John Snow Smith",
|
||||
"cashAccountType": "CACC",
|
||||
"status": "enabled",
|
||||
"maskedPan": None,
|
||||
"details": "Personal Account",
|
||||
"bban": "ssss",
|
||||
"name": None,
|
||||
"product": None,
|
||||
"bic": None,
|
||||
"recipient": "TODO",
|
||||
"account_id": "s-s-s-s-s",
|
||||
"aggregator_id": str(self.aggregator.id),
|
||||
"requisition_id": "3ba3e65d-f44c-4c4e-9e28-08cc080830f6",
|
||||
"account_number": {"sort_code": "04-00-04", "number": "00000001"},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
self.aggregator.save()
|
||||
|
||||
self.plat_client = AgoraClient(self.platform)
|
||||
|
||||
asset = Asset.objects.create(
|
||||
code="XMR",
|
||||
name="Monero",
|
||||
)
|
||||
|
||||
provider = Provider.objects.create(
|
||||
code="REVOLUT",
|
||||
name="Revolut",
|
||||
)
|
||||
|
||||
self.ad = Ad.objects.create(
|
||||
user=self.user,
|
||||
name="Test",
|
||||
text="Ad text",
|
||||
payment_details="Payment details",
|
||||
payment_details_real="Payment details real",
|
||||
payment_method_details="Payment method details",
|
||||
dist_list="",
|
||||
asset_list=[asset],
|
||||
provider_list=[provider],
|
||||
platforms=[self.platform],
|
||||
aggregators=[self.aggregator],
|
||||
send_reference=True,
|
||||
visible=True,
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
def test_get_valid_account_details(self):
|
||||
result = self.plat_client.get_valid_account_details(self.ad)
|
||||
|
||||
def test_get_matching_account_details(self):
|
||||
result = self.plat_client.get_matching_account_details("GBP", self.ad)
|
||||
|
||||
def test_format_payment_details(self):
|
||||
account_info = self.plat_client.get_matching_account_details("GBP", self.ad)
|
||||
result = self.plat_client.format_payment_details(
|
||||
"GBP",
|
||||
account_info,
|
||||
self.ad,
|
||||
real=False,
|
||||
)
|
||||
|
||||
def test_format_payment_details_real(self):
|
||||
account_info = self.plat_client.get_matching_account_details("GBP", self.ad)
|
||||
result = self.plat_client.format_payment_details(
|
||||
"GBP",
|
||||
account_info,
|
||||
self.ad,
|
||||
real=True,
|
||||
)
|
|
@ -1,38 +1,14 @@
|
|||
import logging
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
from core.clients.aggregator import AggregatorClient
|
||||
from core.models import Aggregator, Platform, Trade, Transaction, User
|
||||
from core.models import Trade, Transaction
|
||||
from core.tests.helpers import AggregatorPlatformMixin
|
||||
|
||||
|
||||
class TestTransactions(TransactionTestCase):
|
||||
class TestTransactions(AggregatorPlatformMixin, TransactionTestCase):
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
self.user = User.objects.create_user(
|
||||
username="testuser", email="test@example.com", password="test"
|
||||
)
|
||||
self.aggregator = Aggregator.objects.create(
|
||||
user=self.user,
|
||||
name="Test",
|
||||
service="nordigen",
|
||||
secret_id="a",
|
||||
secret_key="a",
|
||||
)
|
||||
|
||||
self.agg_client = AggregatorClient()
|
||||
self.agg_client.instance = self.aggregator
|
||||
|
||||
self.platform = Platform.objects.create(
|
||||
user=self.user,
|
||||
name="Test",
|
||||
service="agora",
|
||||
token="a",
|
||||
password="a",
|
||||
otp_token="a",
|
||||
username="myuser",
|
||||
)
|
||||
super().setUp()
|
||||
self.transaction = Transaction.objects.create(
|
||||
aggregator=self.aggregator,
|
||||
account_id="my account id",
|
||||
|
|
|
@ -1,16 +1,50 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse
|
||||
from mixins.views import ObjectList
|
||||
from mixins.views import ObjectList, ObjectUpdate
|
||||
from rest_framework import status
|
||||
from two_factor.views.mixins import OTPRequiredMixin
|
||||
|
||||
from core.clients.aggregators.nordigen import NordigenClient
|
||||
from core.models import Aggregator
|
||||
from core.forms import RequisitionForm
|
||||
from core.models import Aggregator, Requisition
|
||||
from core.util import logs
|
||||
from core.views.helpers import synchronize_async_helper
|
||||
|
||||
log = logs.get_logger(__name__)
|
||||
|
||||
|
||||
class BanksRequisitionUpdate(LoginRequiredMixin, OTPRequiredMixin, ObjectUpdate):
|
||||
model = Requisition
|
||||
form_class = RequisitionForm
|
||||
|
||||
page_title = "Update settings for requisition"
|
||||
|
||||
submit_url_name = "requisition_update"
|
||||
submit_url_args = ["type", "aggregator_id", "req_id"]
|
||||
|
||||
pk_required = False
|
||||
|
||||
hide_cancel = True
|
||||
|
||||
def get_object(self, **kwargs):
|
||||
aggregator_id = self.kwargs["aggregator_id"]
|
||||
req_id = self.kwargs["req_id"]
|
||||
|
||||
try:
|
||||
aggregator = Aggregator.objects.get(
|
||||
user=self.request.user, pk=aggregator_id
|
||||
)
|
||||
except Aggregator.DoesNotExist:
|
||||
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
|
||||
req, _ = Requisition.objects.get_or_create(
|
||||
user=self.request.user,
|
||||
aggregator=aggregator,
|
||||
requisition_id=req_id,
|
||||
)
|
||||
return req
|
||||
|
||||
|
||||
class BanksCurrencies(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||
"""
|
||||
Get a list of bank accounts with their details.
|
||||
|
@ -45,7 +79,7 @@ class BanksCurrencies(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
|||
for agg in aggregators:
|
||||
for bank, accounts in agg.account_info.items():
|
||||
for account in accounts:
|
||||
ident = (bank, account["requisition_id"])
|
||||
ident = (bank, account["requisition_id"], account["aggregator_id"])
|
||||
if ident not in account_info:
|
||||
account_info[ident] = []
|
||||
account_info[ident].append(account)
|
||||
|
|
Loading…
Reference in New Issue