From 8ce7f9935e9db2a99dcb935b26507f702a1ecf8c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 30 May 2024 11:20:32 +0200 Subject: [PATCH] Refactor abacus XML code --- server/vbv_lernwelt/shop/invoice/abacus.py | 244 +++++++++++------- server/vbv_lernwelt/shop/tests/factories.py | 2 + .../vbv_lernwelt/shop/tests/test_invoice.py | 128 +++++++-- 3 files changed, 248 insertions(+), 126 deletions(-) diff --git a/server/vbv_lernwelt/shop/invoice/abacus.py b/server/vbv_lernwelt/shop/invoice/abacus.py index 697f5b15..2725e8ee 100644 --- a/server/vbv_lernwelt/shop/invoice/abacus.py +++ b/server/vbv_lernwelt/shop/invoice/abacus.py @@ -1,5 +1,4 @@ import datetime -from typing import List from uuid import uuid4 from xml.dom import minidom from xml.etree.ElementTree import Element, SubElement, tostring @@ -44,120 +43,165 @@ class AbacusInvoiceCreator(InvoiceCreator): self.repository.upload_invoice(invoice, filename) - def create_invoice_xml(self, checkout_information: CheckoutInformation): - pass - @staticmethod - def render_invoice_xml( - abacus_debitor_number: int, - abacus_order_id: int, - datatrans_transaction_id: str, - order_date: datetime.date, - item_description: str, - ) -> str: - container = Element("AbaConnectContainer") - task = SubElement(container, "Task") - parameter = SubElement(task, "Parameter") - SubElement(parameter, "Application").text = "ORDE" - SubElement(parameter, "Id").text = "Verkaufsauftrag" - SubElement(parameter, "MapId").text = "AbaDefault" - SubElement(parameter, "Version").text = "2022.00" +def create_invoice_xml(checkout_information: CheckoutInformation): + # set fill abacus numbers + checkout_information = checkout_information.set_increment_abacus_order_id() + customer = checkout_information.user.set_increment_abacus_debitor_number() - transaction = SubElement(task, "Transaction") - sales_order_header = SubElement(transaction, "SalesOrderHeader", mode="SAVE") - sales_order_header_fields = SubElement( - sales_order_header, "SalesOrderHeaderFields", mode="SAVE" - ) + invoice_xml_content = render_invoice_xml( + abacus_debitor_number=customer.abacus_debitor_number, + abacus_order_id=checkout_information.abacus_order_id, + datatrans_transaction_id=checkout_information.transaction_id, + order_date=checkout_information.created_at.date(), + # TODO was ist der korrekte text für die item_description? + item_description=f"{checkout_information.product_name} - {checkout_information.created_at.date().isoformat()} - {checkout_information.user.last_name} {checkout_information.user.first_name}", + ) - # Skender: Kundennummer, erste Ziffer abhängig von der Plattform (4 = LMS, 6 = myVBV, 7 = EduManager), Plattform führt ein eigenständiges hochzählendes Mapping. - SubElement(sales_order_header_fields, "CustomerNumber").text = str( - abacus_debitor_number - ) + # YYYYMMDDhhmmss + filename_datetime = checkout_information.created_at.strftime("%Y%m%d%H%M%S") + invoice_xml_filename = f"myVBV_orde_{customer.abacus_debitor_number}_{filename_datetime}_{checkout_information.abacus_order_id}.xml" - # Skender: ePayment: Ablaufnr. für ePayment Rechnung in Abacus - SubElement(sales_order_header_fields, "ProcessFlowNumber").text = "30" + return invoice_xml_filename, invoice_xml_content - # Skender: ePayment: Zahlungskondition für ePaymente in Abacus 9999 Tage Mahnungsfrist, da schon bezahlt - SubElement(sales_order_header_fields, "PaymentCode").text = "9999" - # Skender: Bestellzeitpunkt - SubElement( - sales_order_header_fields, "PurchaseOrderDate" - ).text = order_date.isoformat() +def create_customer_xml(checkout_information: CheckoutInformation): + customer = checkout_information.user.set_increment_abacus_debitor_number() - # Skender: ePayment: TRANSACTION-ID von Datatrans in Bestellreferenz - SubElement( - sales_order_header_fields, "ReferencePurchaseOrder" - ).text = datatrans_transaction_id + customer_xml_content = render_customer_xml( + abacus_debitor_number=customer.abacus_debitor_number, + last_name=checkout_information.last_name, + first_name=checkout_information.first_name, + company_name=checkout_information.company_name, + street=(checkout_information.company_street or checkout_information.street), + house_number=( + checkout_information.company_street_number + or checkout_information.street_number + ), + zip_code=( + checkout_information.company_postal_code or checkout_information.postal_code + ), + city=(checkout_information.company_city or checkout_information.city), + country=(checkout_information.company_country or checkout_information.country), + language=customer.language, + email=customer.email, + ) - # Skender: ePayment: OrderID. max 10 Ziffern, erste Ziffer abhängig von der Plattform (4 = LMS, 6 = myVBV, 7 = EduManager) - SubElement(sales_order_header_fields, "GroupingNumberAscii1").text = str( - abacus_order_id - ) + customer_xml_filename = f"myVBV_debi_{customer.abacus_debitor_number}.xml" - item_element = SubElement(sales_order_header, "Item", mode="SAVE") - item_fields = SubElement(item_element, "ItemFields", mode="SAVE") - SubElement(item_fields, "DeliveryDate").text = order_date.isoformat() - SubElement(item_fields, "ItemNumber").text = "1" - SubElement(item_fields, "ProductNumber").text = "30202" - SubElement(item_fields, "QuantityOrdered").text = "1" + return customer_xml_filename, customer_xml_content - item_text = SubElement(item_element, "ItemText", mode="SAVE") - item_text_fields = SubElement(item_text, "ItemTextFields", mode="SAVE") - SubElement(item_text_fields, "Text").text = item_description - return AbacusInvoiceCreator.create_xml_string(container) +def render_invoice_xml( + abacus_debitor_number: int, + abacus_order_id: int, + datatrans_transaction_id: str, + order_date: datetime.date, + item_description: str, +) -> str: + container = Element("AbaConnectContainer") + task = SubElement(container, "Task") + parameter = SubElement(task, "Parameter") + SubElement(parameter, "Application").text = "ORDE" + SubElement(parameter, "Id").text = "Verkaufsauftrag" + SubElement(parameter, "MapId").text = "AbaDefault" + SubElement(parameter, "Version").text = "2022.00" - @staticmethod - def render_customer_xml( - abacus_debitor_number: int, - last_name: str, - first_name: str, - company_name: str, - street: str, - house_number: str, - zip_code: str, - city: str, - country: str, - language: str, - email: str, - ): - container = Element("AbaConnectContainer") - task = SubElement(container, "Task") + transaction = SubElement(task, "Transaction") + sales_order_header = SubElement(transaction, "SalesOrderHeader", mode="SAVE") + sales_order_header_fields = SubElement( + sales_order_header, "SalesOrderHeaderFields", mode="SAVE" + ) - parameter = SubElement(task, "Parameter") - SubElement(parameter, "Application").text = "DEBI" - SubElement(parameter, "ID").text = "Kunden" - SubElement(parameter, "MapID").text = "AbaDefault" - SubElement(parameter, "Version").text = "2022.00" + # Skender: Kundennummer, erste Ziffer abhängig von der Plattform (4 = LMS, 6 = myVBV, 7 = EduManager), Plattform führt ein eigenständiges hochzählendes Mapping. + SubElement(sales_order_header_fields, "CustomerNumber").text = str( + abacus_debitor_number + ) - transaction = SubElement(task, "Transaction") - customer_element = SubElement(transaction, "Customer", mode="SAVE") + # Skender: ePayment: Ablaufnr. für ePayment Rechnung in Abacus + SubElement(sales_order_header_fields, "ProcessFlowNumber").text = "30" - SubElement(customer_element, "CustomerNumber").text = str(abacus_debitor_number) - SubElement(customer_element, "DefaultCurrency").text = "CHF" - SubElement(customer_element, "PaymentTermNumber").text = "1" - SubElement(customer_element, "ReminderProcedure").text = "NORM" + # Skender: ePayment: Zahlungskondition für ePaymente in Abacus 9999 Tage Mahnungsfrist, da schon bezahlt + SubElement(sales_order_header_fields, "PaymentCode").text = "9999" - address_data = SubElement(customer_element, "AddressData", mode="SAVE") - SubElement(address_data, "AddressNumber").text = str(abacus_debitor_number) - SubElement(address_data, "Name").text = last_name - SubElement(address_data, "FirstName").text = first_name - SubElement(address_data, "Text").text = company_name - SubElement(address_data, "Street").text = street - SubElement(address_data, "HouseNumber").text = house_number - SubElement(address_data, "ZIP").text = zip_code - SubElement(address_data, "City").text = city - SubElement(address_data, "Country").text = country - SubElement(address_data, "Language").text = language - SubElement(address_data, "Email").text = email + # Skender: Bestellzeitpunkt + SubElement( + sales_order_header_fields, "PurchaseOrderDate" + ).text = order_date.isoformat() - return AbacusInvoiceCreator.create_xml_string(container) + # Skender: ePayment: TRANSACTION-ID von Datatrans in Bestellreferenz + SubElement( + sales_order_header_fields, "ReferencePurchaseOrder" + ).text = datatrans_transaction_id - @staticmethod - def create_xml_string(container: Element, encoding: str = "utf-8") -> str: - xml_bytes = tostring(container, encoding) - xml_pretty_str = minidom.parseString(xml_bytes).toprettyxml( - indent=" ", encoding=encoding - ) - return xml_pretty_str.decode(encoding) + # Skender: ePayment: OrderID. max 10 Ziffern, erste Ziffer abhängig von der Plattform (4 = LMS, 6 = myVBV, 7 = EduManager) + SubElement(sales_order_header_fields, "GroupingNumberAscii1").text = str( + abacus_order_id + ) + + item_element = SubElement(sales_order_header, "Item", mode="SAVE") + item_fields = SubElement(item_element, "ItemFields", mode="SAVE") + SubElement(item_fields, "DeliveryDate").text = order_date.isoformat() + SubElement(item_fields, "ItemNumber").text = "1" + SubElement(item_fields, "ProductNumber").text = "30202" + SubElement(item_fields, "QuantityOrdered").text = "1" + + item_text = SubElement(item_element, "ItemText", mode="SAVE") + item_text_fields = SubElement(item_text, "ItemTextFields", mode="SAVE") + SubElement(item_text_fields, "Text").text = item_description + + return create_xml_string(container) + + +def render_customer_xml( + abacus_debitor_number: int, + last_name: str, + first_name: str, + company_name: str, + street: str, + house_number: str, + zip_code: str, + city: str, + country: str, + language: str, + email: str, +): + container = Element("AbaConnectContainer") + task = SubElement(container, "Task") + + parameter = SubElement(task, "Parameter") + SubElement(parameter, "Application").text = "DEBI" + SubElement(parameter, "ID").text = "Kunden" + SubElement(parameter, "MapID").text = "AbaDefault" + SubElement(parameter, "Version").text = "2022.00" + + transaction = SubElement(task, "Transaction") + customer_element = SubElement(transaction, "Customer", mode="SAVE") + + SubElement(customer_element, "CustomerNumber").text = str(abacus_debitor_number) + SubElement(customer_element, "DefaultCurrency").text = "CHF" + SubElement(customer_element, "PaymentTermNumber").text = "1" + SubElement(customer_element, "ReminderProcedure").text = "NORM" + + address_data = SubElement(customer_element, "AddressData", mode="SAVE") + SubElement(address_data, "AddressNumber").text = str(abacus_debitor_number) + SubElement(address_data, "Name").text = last_name + SubElement(address_data, "FirstName").text = first_name + SubElement(address_data, "Text").text = company_name + SubElement(address_data, "Street").text = street + SubElement(address_data, "HouseNumber").text = house_number + SubElement(address_data, "ZIP").text = zip_code + SubElement(address_data, "City").text = city + SubElement(address_data, "Country").text = country + SubElement(address_data, "Language").text = language + SubElement(address_data, "Email").text = email + + return create_xml_string(container) + + +def create_xml_string(container: Element, encoding: str = "utf-8") -> str: + xml_bytes = tostring(container, encoding) + xml_pretty_str = minidom.parseString(xml_bytes).toprettyxml( + indent=" ", encoding=encoding + ) + return xml_pretty_str.decode(encoding) diff --git a/server/vbv_lernwelt/shop/tests/factories.py b/server/vbv_lernwelt/shop/tests/factories.py index f8396de9..17869a68 100644 --- a/server/vbv_lernwelt/shop/tests/factories.py +++ b/server/vbv_lernwelt/shop/tests/factories.py @@ -11,3 +11,5 @@ class CheckoutInformationFactory(DjangoModelFactory): product_sku = VV_DE_PRODUCT_SKU product_price = 324_30 state = CheckoutState.PAID + product_name = "Versicherungsvermittler/-in VBV" + product_description = "Versicherungsvermittler/-in VBV DE" diff --git a/server/vbv_lernwelt/shop/tests/test_invoice.py b/server/vbv_lernwelt/shop/tests/test_invoice.py index 15c9c661..48ac5f4d 100644 --- a/server/vbv_lernwelt/shop/tests/test_invoice.py +++ b/server/vbv_lernwelt/shop/tests/test_invoice.py @@ -1,34 +1,74 @@ -from datetime import date +from datetime import date, datetime from unittest.mock import create_autospec from django.test import TestCase from vbv_lernwelt.core.admin import User -from vbv_lernwelt.shop.invoice.abacus import AbacusInvoiceCreator -from vbv_lernwelt.shop.invoice.creator import Item +from vbv_lernwelt.core.create_default_users import create_default_users +from vbv_lernwelt.shop.invoice.abacus import ( + AbacusInvoiceCreator, + render_invoice_xml, + render_customer_xml, + create_invoice_xml, + create_customer_xml, +) from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository from vbv_lernwelt.shop.models import CheckoutInformation +from vbv_lernwelt.shop.tests.factories import CheckoutInformationFactory USER_USERNAME = "testuser" USER_EMAIL = "test@example.com" USER_PASSWORD = "testpassword" -class InvoiceTestCase(TestCase): - def setUp(self) -> None: - self.user = User.objects.create_user( - username=USER_USERNAME, - email=USER_EMAIL, - password=USER_PASSWORD, - is_active=True, +class AbacusInvoiceTestCase(TestCase): + def setUp(self): + create_default_users() + + def test_create_invoice_xml(self): + # set abacus_number before + _pat = User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch") + _pat.abacus_debitor_number = 60000011 + _pat.save() + _ignore_checkout_information = CheckoutInformationFactory( + user=_pat, abacus_order_id=6_000_000_123 + ) + + feuz = User.objects.get(username="andreas.feuz@eiger-versicherungen.ch") + feuz_checkout_info = CheckoutInformationFactory( + user=feuz, + transaction_id="24021508331287484", + ) + feuz_checkout_info.created_at = datetime(2024, 2, 15, 8, 33, 12, 0) + invoice_xml_filename, invoice_xml_content = create_invoice_xml( + feuz_checkout_info + ) + + self.assertEqual( + invoice_xml_filename, "myVBV_orde_60000012_20240215083312_6000000124.xml" + ) + + # print(invoice_xml_content) + assert "60000012" in invoice_xml_content + assert ( + "2024-02-15" in invoice_xml_content + ) + assert ( + "6000000124" + in invoice_xml_content + ) + assert ( + "24021508331287484" + in invoice_xml_content + ) + assert "2024-02-15" in invoice_xml_content + assert ( + "Versicherungsvermittler/-in VBV - 2024-02-15 - Feuz Andreas" + in invoice_xml_content ) def test_render_invoice_xml(self): - # GIVEN - creator = AbacusInvoiceCreator(repository=create_autospec(InvoiceRepository)) - - # WHEN - invoice_xml = creator.render_invoice_xml( + invoice_xml = render_invoice_xml( abacus_debitor_number=60000012, abacus_order_id=6000000001, order_date=date(2024, 2, 15), @@ -75,17 +115,56 @@ class InvoiceTestCase(TestCase): """ - # THEN self.maxDiff = None self.assertXMLEqual(expected_xml, invoice_xml) - def test_render_customer_xml(self): - # GIVEN - creator = AbacusInvoiceCreator( - repository=create_autospec(InvoiceRepository)) + def test_create_customer_xml_with_company_address(self): + _pat = User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch") + _pat.abacus_debitor_number = 60000011 + _pat.save() + _ignore_checkout_information = CheckoutInformationFactory( + user=_pat, abacus_order_id=6_000_000_123 + ) - # WHEN - customer_xml = creator.render_customer_xml( + feuz = User.objects.get(username="andreas.feuz@eiger-versicherungen.ch") + feuz_checkout_info = CheckoutInformationFactory( + user=feuz, + transaction_id="24021508331287484", + first_name="Andreas", + last_name="Feuz", + street="Eggersmatt", + street_number="32", + postal_code="1719", + city="Zumholz", + country="209", + company_name="VBV", + company_street="Laupenstrasse", + company_street_number="10", + company_postal_code="3000", + company_city="Bern", + company_country="209", + ) + feuz_checkout_info.created_at = datetime(2024, 2, 15, 8, 33, 12, 0) + + customer_xml_filename, customer_xml_content = create_customer_xml( + checkout_information=feuz_checkout_info + ) + print(customer_xml_content) + print(customer_xml_filename) + + self.assertEqual("myVBV_debi_60000012.xml", customer_xml_filename) + assert "60000012" in customer_xml_content + assert ( + "andreas.feuz@eiger-versicherungen.ch" + in customer_xml_content + ) + assert "VBV" in customer_xml_content + + # FIXME refactor country + assert "209" in customer_xml_content + + def test_render_customer_xml(self): + customer_xml = render_customer_xml( abacus_debitor_number=60000012, last_name="Gebhart-Krasniqi", first_name="Skender", @@ -96,11 +175,9 @@ class InvoiceTestCase(TestCase): city="Bern", country="CH", language="de", - email="skender.krasniqi@vbv-afa.ch" + email="skender.krasniqi@vbv-afa.ch", ) - print(customer_xml) - # example from Skender expected_xml = """ @@ -136,7 +213,6 @@ class InvoiceTestCase(TestCase): """ - # THEN self.maxDiff = None self.assertXMLEqual(expected_xml, customer_xml)