feat: invoice customer
This commit is contained in:
parent
5d21fd0f42
commit
1281be2221
|
|
@ -6,6 +6,7 @@ from xml.etree.ElementTree import Element, SubElement, tostring
|
||||||
|
|
||||||
from vbv_lernwelt.shop.invoice.creator import InvoiceCreator, Item
|
from vbv_lernwelt.shop.invoice.creator import InvoiceCreator, Item
|
||||||
from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository
|
from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository
|
||||||
|
from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
|
|
||||||
|
|
||||||
class AbacusInvoiceCreator(InvoiceCreator):
|
class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
|
|
@ -14,18 +15,25 @@ class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
|
|
||||||
def create_invoice(
|
def create_invoice(
|
||||||
self,
|
self,
|
||||||
customer_number: str,
|
checkout_information: CheckoutInformation,
|
||||||
purchase_order_date: datetime.date,
|
|
||||||
delivery_date: datetime.date,
|
|
||||||
reference_purchase_order: str,
|
|
||||||
unic_id: str,
|
|
||||||
items: List[Item],
|
|
||||||
filename: str = None,
|
filename: str = None,
|
||||||
):
|
):
|
||||||
invoice = self.render_invoice(
|
customer_number = checkout_information.transaction_id
|
||||||
|
order_date = checkout_information.created_at.date()
|
||||||
|
reference_purchase_order = str(checkout_information.id)
|
||||||
|
unic_id = checkout_information.transaction_id
|
||||||
|
|
||||||
|
items = [
|
||||||
|
Item(
|
||||||
|
product_number=checkout_information.product_sku,
|
||||||
|
quantity=1,
|
||||||
|
description=checkout_information.product_description,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
invoice = self.invoice_xml(
|
||||||
customer_number,
|
customer_number,
|
||||||
purchase_order_date,
|
order_date,
|
||||||
delivery_date,
|
|
||||||
reference_purchase_order,
|
reference_purchase_order,
|
||||||
unic_id,
|
unic_id,
|
||||||
items,
|
items,
|
||||||
|
|
@ -37,10 +45,9 @@ class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
self.repository.upload_invoice(invoice, filename)
|
self.repository.upload_invoice(invoice, filename)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def render_invoice(
|
def invoice_xml(
|
||||||
customer_number: str,
|
customer_number: str,
|
||||||
purchase_order_date: datetime.date,
|
order_date: datetime.date,
|
||||||
delivery_date: datetime.date,
|
|
||||||
reference_purchase_order: str,
|
reference_purchase_order: str,
|
||||||
unic_id: str,
|
unic_id: str,
|
||||||
items: List[Item],
|
items: List[Item],
|
||||||
|
|
@ -62,10 +69,10 @@ class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
SubElement(sales_order_header_fields, "CustomerNumber").text = customer_number
|
SubElement(sales_order_header_fields, "CustomerNumber").text = customer_number
|
||||||
SubElement(
|
SubElement(
|
||||||
sales_order_header_fields, "PurchaseOrderDate"
|
sales_order_header_fields, "PurchaseOrderDate"
|
||||||
).text = purchase_order_date.isoformat()
|
).text = order_date.isoformat()
|
||||||
SubElement(
|
SubElement(
|
||||||
sales_order_header_fields, "DeliveryDate"
|
sales_order_header_fields, "DeliveryDate"
|
||||||
).text = delivery_date.isoformat()
|
).text = order_date.isoformat()
|
||||||
SubElement(
|
SubElement(
|
||||||
sales_order_header_fields, "ReferencePurchaseOrder"
|
sales_order_header_fields, "ReferencePurchaseOrder"
|
||||||
).text = reference_purchase_order
|
).text = reference_purchase_order
|
||||||
|
|
@ -76,7 +83,7 @@ class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
item_fields = SubElement(item_element, "ItemFields", mode="SAVE")
|
item_fields = SubElement(item_element, "ItemFields", mode="SAVE")
|
||||||
SubElement(item_fields, "ItemNumber").text = str(index)
|
SubElement(item_fields, "ItemNumber").text = str(index)
|
||||||
SubElement(item_fields, "ProductNumber").text = item.product_number
|
SubElement(item_fields, "ProductNumber").text = item.product_number
|
||||||
SubElement(item_fields, "QuantityOrdered").text = item.quantity
|
SubElement(item_fields, "QuantityOrdered").text = str(item.quantity)
|
||||||
|
|
||||||
item_text = SubElement(item_element, "ItemText", mode="SAVE")
|
item_text = SubElement(item_element, "ItemText", mode="SAVE")
|
||||||
item_text_fields = SubElement(item_text, "ItemTextFields", mode="SAVE")
|
item_text_fields = SubElement(item_text, "ItemTextFields", mode="SAVE")
|
||||||
|
|
@ -84,6 +91,52 @@ class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
|
|
||||||
return AbacusInvoiceCreator.create_xml_string(container)
|
return AbacusInvoiceCreator.create_xml_string(container)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def customer_xml(
|
||||||
|
customer_number: str,
|
||||||
|
name: str,
|
||||||
|
first_name: str,
|
||||||
|
address_text: 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 = customer_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 = customer_number
|
||||||
|
SubElement(address_data, "Name").text = name
|
||||||
|
SubElement(address_data, "FirstName").text = first_name
|
||||||
|
SubElement(address_data, "Text").text = address_text
|
||||||
|
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 AbacusInvoiceCreator.create_xml_string(container)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_xml_string(container: Element, encoding: str = "UTF-8") -> str:
|
def create_xml_string(container: Element, encoding: str = "UTF-8") -> str:
|
||||||
xml_bytes = tostring(container, encoding)
|
xml_bytes = tostring(container, encoding)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import datetime
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List
|
|
||||||
|
from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Item:
|
class Item:
|
||||||
product_number: str
|
product_number: str
|
||||||
quantity: str
|
quantity: int
|
||||||
description: str
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,11 +15,7 @@ class InvoiceCreator(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create_invoice(
|
def create_invoice(
|
||||||
self,
|
self,
|
||||||
customer_number: str,
|
checkout_information: CheckoutInformation,
|
||||||
purchase_order_date: datetime.date,
|
filename: str = None,
|
||||||
delivery_date: datetime.date,
|
|
||||||
reference_purchase_order: str,
|
|
||||||
unic_id: str,
|
|
||||||
items: List[Item],
|
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class InvoiceRepository(ABC):
|
class InvoiceRepository(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -35,6 +39,6 @@ class SFTPInvoiceRepository(InvoiceRepository):
|
||||||
sftp_client.putfo(invoice_io, f"uploads/{filename}")
|
sftp_client.putfo(invoice_io, f"uploads/{filename}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"An error occurred: {e}")
|
logger.error("Could not upload invoice", exc_info=e)
|
||||||
finally:
|
finally:
|
||||||
ssh_client.close()
|
ssh_client.close()
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,41 @@
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from unittest.mock import create_autospec
|
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.abacus import AbacusInvoiceCreator
|
||||||
from vbv_lernwelt.shop.invoice.creator import Item
|
from vbv_lernwelt.shop.invoice.creator import Item
|
||||||
from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository
|
from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository
|
||||||
|
from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
|
|
||||||
|
USER_USERNAME = "testuser"
|
||||||
|
USER_EMAIL = "test@example.com"
|
||||||
|
USER_PASSWORD = "testpassword"
|
||||||
|
|
||||||
|
|
||||||
def test_render_invoice():
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_render_invoice(self):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
creator = AbacusInvoiceCreator(repository=create_autospec(InvoiceRepository))
|
creator = AbacusInvoiceCreator(repository=create_autospec(InvoiceRepository))
|
||||||
items = [Item(product_number="001", quantity="10", description="Test Item")]
|
items = [Item(product_number="001", quantity=1, description="Test Item")]
|
||||||
customer_number = "12345"
|
customer_number = "12345"
|
||||||
purchase_order_date = date(2023, 1, 1)
|
order_date = date(2023, 1, 1)
|
||||||
delivery_date = date(2023, 1, 10)
|
|
||||||
reference_purchase_order = "PO12345678"
|
reference_purchase_order = "PO12345678"
|
||||||
unic_id = "UNIC001"
|
unic_id = "UNIC001"
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
invoice_xml = creator.render_invoice(
|
invoice_xml = creator.invoice_xml(
|
||||||
customer_number,
|
customer_number,
|
||||||
purchase_order_date,
|
order_date,
|
||||||
delivery_date,
|
|
||||||
reference_purchase_order,
|
reference_purchase_order,
|
||||||
unic_id,
|
unic_id,
|
||||||
items,
|
items,
|
||||||
|
|
@ -30,42 +45,42 @@ def test_render_invoice():
|
||||||
assert "<CustomerNumber>12345</CustomerNumber>" in invoice_xml
|
assert "<CustomerNumber>12345</CustomerNumber>" in invoice_xml
|
||||||
assert "<ItemNumber>1</ItemNumber>" in invoice_xml
|
assert "<ItemNumber>1</ItemNumber>" in invoice_xml
|
||||||
assert "<ProductNumber>001</ProductNumber>" in invoice_xml
|
assert "<ProductNumber>001</ProductNumber>" in invoice_xml
|
||||||
assert "<QuantityOrdered>10</QuantityOrdered>" in invoice_xml
|
assert "<QuantityOrdered>1</QuantityOrdered>" in invoice_xml
|
||||||
assert "<Text>Test Item</Text>" in invoice_xml
|
assert "<Text>Test Item</Text>" in invoice_xml
|
||||||
|
|
||||||
|
def test_create_invoice_calls_upload(self):
|
||||||
def test_create_invoice_calls_upload():
|
|
||||||
# GIVEN
|
# GIVEN
|
||||||
repository_mock = create_autospec(InvoiceRepository)
|
repository_mock = create_autospec(InvoiceRepository)
|
||||||
|
|
||||||
creator = AbacusInvoiceCreator(repository=repository_mock)
|
creator = AbacusInvoiceCreator(repository=repository_mock)
|
||||||
items = [Item(product_number="001", quantity="10", description="Test Item")]
|
|
||||||
customer_number = "12345"
|
|
||||||
purchase_order_date = date(2023, 1, 1)
|
|
||||||
delivery_date = date(2023, 1, 10)
|
|
||||||
reference_purchase_order = "PO12345678"
|
|
||||||
unic_id = "UNIC001"
|
|
||||||
|
|
||||||
expected_filename = "test.xml"
|
expected_filename = "test.xml"
|
||||||
|
|
||||||
|
checkout_information = CheckoutInformation.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
transaction_id="12345",
|
||||||
|
product_sku="001",
|
||||||
|
product_name="Test Product",
|
||||||
|
product_description="Test Product Description",
|
||||||
|
product_price=1000,
|
||||||
|
state="initialized",
|
||||||
|
)
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
creator.create_invoice(
|
creator.create_invoice(
|
||||||
customer_number,
|
checkout_information=checkout_information,
|
||||||
purchase_order_date,
|
|
||||||
delivery_date,
|
|
||||||
reference_purchase_order,
|
|
||||||
unic_id,
|
|
||||||
items,
|
|
||||||
filename=expected_filename,
|
filename=expected_filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
repository_mock.upload_invoice.assert_called_once()
|
repository_mock.upload_invoice.assert_called_once()
|
||||||
uploaded_invoice, uploaded_filename = repository_mock.upload_invoice.call_args[0]
|
uploaded_invoice, uploaded_filename = repository_mock.upload_invoice.call_args[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
|
||||||
assert uploaded_filename == expected_filename
|
assert uploaded_filename == expected_filename
|
||||||
assert "<CustomerNumber>12345</CustomerNumber>" in uploaded_invoice
|
assert "<CustomerNumber>12345</CustomerNumber>" in uploaded_invoice
|
||||||
assert "<ItemNumber>1</ItemNumber>" in uploaded_invoice
|
assert "<ItemNumber>1</ItemNumber>" in uploaded_invoice
|
||||||
assert "<ProductNumber>001</ProductNumber>" in uploaded_invoice
|
assert "<ProductNumber>001</ProductNumber>" in uploaded_invoice
|
||||||
assert "<QuantityOrdered>10</QuantityOrdered>" in uploaded_invoice
|
assert "<QuantityOrdered>1</QuantityOrdered>" in uploaded_invoice
|
||||||
assert "<Text>Test Item</Text>" in uploaded_invoice
|
assert "<Text>Test Product Description</Text>" in uploaded_invoice
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue