Skip to content

Commit

Permalink
feat: basic support for multiple currencies
Browse files Browse the repository at this point in the history
  • Loading branch information
nijel committed Oct 24, 2024
1 parent 35b6d08 commit 927cc38
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 12 deletions.
18 changes: 17 additions & 1 deletion weblate_web/invoices/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class QuantityUnit(models.IntegerChoices):
class Currency(models.IntegerChoices):
EUR = 0, "EUR"
CZK = 1, "CZK"
USD = 2, "USD"
GBP = 3, "GBP"


class InvoiceKind(models.IntegerChoices):
Expand Down Expand Up @@ -220,10 +222,19 @@ def render_amount(self, amount: int | Decimal) -> str:

@cached_property
def exchange_rate_czk(self) -> Decimal:
"""Exchange rate from currency to CZK."""
return DecimalRates.get(
self.issue_date.isoformat(), self.get_currency_display()
)

@cached_property
def exchange_rate_eur(self) -> Decimal:
"""Exchange rate from currency to EUR."""
return (
DecimalRates.get(self.issue_date.isoformat(), "EUR")
/ self.exchange_rate_czk
)

@cached_property
def total_items_amount(self) -> Decimal:
return sum(
Expand Down Expand Up @@ -547,7 +558,12 @@ def save( # type: ignore[override]

if self.package:
if not self.unit_price:
self.unit_price = self.package.price
if self.invoice.currency == Currency.EUR:
self.unit_price = self.package.price
else:
self.unit_price = round(
self.package.price * self.invoice.exchange_rate_eur, 0
)
extra_fields.append("unit_price")
if not self.description:
if (start_date := self.invoice.extra.get("start_date")) and (
Expand Down
24 changes: 22 additions & 2 deletions weblate_web/invoices/templates/invoice-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,17 @@ <h2>Issued by</h2>
{% else %}
Bank transfer
<br />
IBAN: CZ30 2010 0000 0023 0290 7395
{% if invoice.get_currency_display == "CZK" %}
Bank account: 2002907393 / 2010
{% elif invoice.get_currency_display == "EUR" %}
IBAN: CZ30 2010 0000 0023 0290 7395
{% elif invoice.get_currency_display == "USD" %}
Bank account: 2603015278
BIC/SWIFT: FIOBCZPPXXX
{% elif invoice.get_currency_display == "GBP" %}
Bank account: 2803015280
BIC/SWIFT: FIOBCZPPXXX
{% endif %}
<br />
Reference: {{ invoice.number }}
<br />
Expand Down Expand Up @@ -210,7 +220,17 @@ <h2>Issued by</h2>
</tr>
<tr>
<td>Weblate s.r.o.</td>
<td>2302907395 / 2010</td>
<td>
{% if invoice.get_currency_display == "CZK" %}
2002907393 / 2010
{% elif invoice.get_currency_display == "EUR" %}
2302907395 / 2010
{% elif invoice.get_currency_display == "USD" %}
2603015278 / 2010
{% elif invoice.get_currency_display == "GBP" %}
2803015280 / 2010
{% endif %}
</td>
<td>FIOBCZPPXXX</td>
<td>Fio banka, a.s., Na Florenci 2139/2, 11000 Praha, Czechia</td>
<td class="thanks">♥ Thank you!</td>
Expand Down
55 changes: 48 additions & 7 deletions weblate_web/invoices/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from django.test import TestCase
from lxml import etree

from weblate_web.models import Package, PackageCategory
from weblate_web.payments.models import Customer

from .models import Discount, Invoice, InvoiceKind, QuantityUnit
from .models import Currency, Discount, Invoice, InvoiceKind, QuantityUnit

S3_SCHEMA_PATH = (
Path(__file__).parent.parent.parent / "schemas" / "money-s3" / "_Document.xsd"
Expand All @@ -17,8 +18,7 @@


class InvoiceTestCase(TestCase):
@staticmethod
def create_customer(vat: str = ""):
def create_customer(self, vat: str = "") -> Customer:
return Customer.objects.create(
name="Zkušební zákazník",
address="Street 42",
Expand All @@ -29,27 +29,58 @@ def create_customer(vat: str = ""):
vat=vat,
)

def create_invoice(
def create_invoice_base(
self,
discount: Discount | None = None,
vat_rate: int = 0,
customer_reference: str = "",
vat: str = "",
):
invoice = Invoice.objects.create(
currency: Currency = Currency.EUR,
) -> Invoice:
return Invoice.objects.create(
customer=self.create_customer(vat=vat),
discount=discount,
vat_rate=vat_rate,
kind=InvoiceKind.INVOICE,
customer_reference=customer_reference,
currency=currency,
)

def create_invoice_package(
self,
discount: Discount | None = None,
currency: Currency = Currency.EUR,
) -> Invoice:
invoice = self.create_invoice_base(discount=discount, currency=currency)
package = Package.objects.create(
name="hosting",
verbose="Weblate hosting",
price=100,
category=PackageCategory.PACKAGE_DEDICATED,
)
invoice.invoiceitem_set.create(package=package)
return invoice

def create_invoice(
self,
discount: Discount | None = None,
vat_rate: int = 0,
customer_reference: str = "",
vat: str = "",
) -> Invoice:
invoice = self.create_invoice_base(
discount=discount,
vat_rate=vat_rate,
customer_reference=customer_reference,
vat=vat,
)
invoice.invoiceitem_set.create(
description="Test item",
unit_price=100,
)
return invoice

def validate_invoice(self, invoice: Invoice):
def validate_invoice(self, invoice: Invoice) -> None:
invoice.generate_files()
self.assertNotEqual(str(invoice), "")
if invoice.discount:
Expand Down Expand Up @@ -115,3 +146,13 @@ def test_discount_vat(self):
)
self.assertEqual(invoice.total_amount, Decimal("60.50"))
self.validate_invoice(invoice)

def test_package(self):
invoice = self.create_invoice_package()
self.assertEqual(invoice.total_amount, Decimal(100))
self.validate_invoice(invoice)

def test_package_usd(self):
invoice = self.create_invoice_package(currency=Currency.USD)
self.assertEqual(invoice.total_amount, Decimal(100))
self.validate_invoice(invoice)
8 changes: 6 additions & 2 deletions weblate_web/payments/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,16 +500,20 @@ def get_invoice_kwargs(self):
def get_instructions(self) -> list[tuple[StrOrPromise, StrOrPromise]]:
invoice = self.get_proforma()
return [
(gettext("Issuing bank"), invoice.bank["bank"]),
(
gettext("Issuing bank"),
"Fio banka, a.s., Na Florenci 2139/2, 11000 Praha, Czechia",
),
(gettext("Account holder"), invoice.bank["holder"]),
(gettext("Account number"), invoice.bank["account"]),
(gettext("SWIFT code"), invoice.bank["swift"]),
(gettext("SWIFT code"), "FIOBCZPPXXX"),
(gettext("IBAN"), invoice.bank["iban"]),
(gettext("Reference"), invoice.invoiceid),
]

@classmethod
def fetch_payments(cls, from_date=None) -> None:
# TODO: support token per currency
client = fiobank.FioBank(token=settings.FIO_TOKEN)
for entry in client.last(from_date=from_date):
matches = []
Expand Down

0 comments on commit 927cc38

Please sign in to comment.