From 973c090a95bf4fd58c73f7f722632925d65ee942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Fri, 18 Oct 2024 12:46:56 +0200 Subject: [PATCH] fix(invoice): store full invoice number in a database This is needed for lookups. --- .../migrations/0004_invoice_number.py | 46 +++++++++++++++++++ weblate_web/invoices/models.py | 29 +++++++----- 2 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 weblate_web/invoices/migrations/0004_invoice_number.py diff --git a/weblate_web/invoices/migrations/0004_invoice_number.py b/weblate_web/invoices/migrations/0004_invoice_number.py new file mode 100644 index 0000000000..252c171734 --- /dev/null +++ b/weblate_web/invoices/migrations/0004_invoice_number.py @@ -0,0 +1,46 @@ +# Generated by Django 5.1.2 on 2024-10-18 10:58 + +import django.db.models.expressions +import django.db.models.functions.comparison +import django.db.models.functions.datetime +import django.db.models.functions.text +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("invoices", "0003_alter_invoice_kind"), + ] + + operations = [ + migrations.AddField( + model_name="invoice", + name="number", + field=models.GeneratedField( + db_persist=True, + expression=django.db.models.functions.text.Concat( + django.db.models.functions.comparison.Cast( + "kind", models.CharField() + ), + django.db.models.functions.comparison.Cast( + django.db.models.expressions.CombinedExpression( + django.db.models.functions.datetime.Extract( + "issue_date", "year" + ), + "%%", + models.Value(2000), + ), + models.CharField(), + ), + django.db.models.functions.text.LPad( + django.db.models.functions.comparison.Cast( + "sequence", models.CharField() + ), + 6, + models.Value("0"), + ), + ), + output_field=models.CharField(max_length=20), + ), + ), + ] diff --git a/weblate_web/invoices/models.py b/weblate_web/invoices/models.py index 0bcf9c5f92..f8057a9f94 100644 --- a/weblate_web/invoices/models.py +++ b/weblate_web/invoices/models.py @@ -25,7 +25,7 @@ from django.conf import settings from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from django.db.models.functions import Extract +from django.db.models.functions import Cast, Concat, Extract, LPad from django.template.loader import render_to_string from django.utils.functional import cached_property from django.utils.translation import override @@ -82,6 +82,15 @@ def display_percents(self) -> str: class Invoice(models.Model): sequence = models.IntegerField(editable=False) + number = models.GeneratedField( + expression=Concat( + Cast("kind", models.CharField()), + Cast(Extract("issue_date", "year") % 2000, models.CharField()), + LPad(Cast("sequence", models.CharField()), 6, models.Value("0")), + ), + output_field=models.CharField(max_length=20), + db_persist=True, + ) issue_date = models.DateField(default=datetime.date.today) due_date = models.DateField(blank=True) kind = models.IntegerField(choices=InvoiceKindChoices) @@ -115,15 +124,15 @@ def __str__(self) -> str: def save( self, *, - force_insert=False, - force_update=False, + force_insert: bool = False, + force_update: bool = False, using=None, update_fields=None, ): + extra_fields: list[str] = [] if not self.due_date: self.due_date = self.issue_date + datetime.timedelta(days=14) - if update_fields is not None: - update_fields = ("due_date", *update_fields) + extra_fields.append("due_date") if not self.sequence: try: self.sequence = ( @@ -136,8 +145,10 @@ def save( ) except IndexError: self.sequence = 1 - if update_fields is not None: - update_fields = ("sequence", *update_fields) + extra_fields.append("sequence") + if extra_fields and update_fields is not None: + update_fields = tuple(set(update_fields).union(extra_fields)) + super().save( force_insert=force_insert, force_update=force_update, @@ -145,10 +156,6 @@ def save( update_fields=update_fields, ) - @property - def number(self) -> str: - return f"{self.kind}{self.issue_date.year % 2000}{self.sequence:05d}" - def render_amount(self, amount: int | Decimal) -> str: if self.currency == CurrencyChoices.EUR: return f"€{amount}"