MHD wahlweise nur mit Monat und Jahr (Backend)

Auf vielen Verpackungen steht nur "09/2026". Bisher liess sich ausschliesslich
ein Tagesdatum erfassen, was zu erfundener Genauigkeit fuehrte.

Entwurf: best_before bleibt ein echtes DATE, damit FEFO, die Ablauf-Abfragen
und alle bestehenden Sortierungen unveraendert weiterlaufen. Eine Monatsangabe
wird auf den Monatsletzten gelegt - die uebliche Lesart bei Lebensmitteln, und
die sichere Richtung, weil nicht zu frueh aussortiert wird. Zusaetzlich merkt
sich jede Charge in best_before_precision, wie genau die Angabe war; davon
haengt allein die Anzeige ab. Am Produkt steht in date_precision, welche
Genauigkeit dort ueblich ist (Voreinstellung der Eingabe, z.B. Konserven).

Bewusst als VARCHAR statt als DB-Enum gespeichert: So laesst sich die Spalte
auf bestehenden Tabellen per ADD COLUMN IF NOT EXISTS nachziehen, ohne vorher
einen neuen Postgres-Typ anzulegen. Beide Spalten haben ein Server-Default
'day', damit vorhandene Chargen unveraendert gueltig bleiben.

Export/Import bleiben verlustfrei: Das JSON-Backup fuehrt beide Felder mit. Die
CSV bekommt bewusst keine neue Spalte, damit die Datei in Excel unveraendert
bedienbar bleibt - stattdessen wird eine Monatsangabe als "09/2026" geschrieben
und beim Import an der Schreibweise wieder erkannt ("09/2026", "2026-09",
"09.2026"); tagesgenaue Formate werden weiterhin gelesen.

Beim Korrigieren einer Charge werden MHD und Genauigkeit gemeinsam ausgewertet,
sonst bliebe ein auf Monat umgestellter Wert auf dem alten Tag stehen.

Getestet: 40 pytest-Tests gruen (17 neue zu Monatsletztem, Schaltjahr,
Einlagern, Export-Schreibweise und Import-Erkennung). Zusaetzlich ein
Durchlauf ueber die echte API: Produkt mit Monatsvorgabe anlegen und aendern,
Sammel-Einlagern mit gemischten Genauigkeiten, Chargen wieder auslesen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-22 17:48:11 +02:00
parent d680aeff0e
commit eed7c9e1de
11 changed files with 247 additions and 7 deletions

View File

@@ -0,0 +1,36 @@
"""Genauigkeit von Mindesthaltbarkeitsdaten.
Auf vielen Verpackungen steht nur "09/2026" statt eines Tagesdatums. Damit die
FEFO-Sortierung und alle Ablauf-Abfragen weiter mit einem echten DATE arbeiten
können, wird ein solcher Monatswert auf den **Monatsletzten** gelegt das ist
die übliche Lesart bei Lebensmitteln. Wie genau die Angabe ursprünglich war,
merkt sich die Charge zusätzlich in ``best_before_precision``; nur davon hängt
ab, ob in der Anzeige "30.09.2026" oder "09/2026" steht.
"""
from __future__ import annotations
from calendar import monthrange
from datetime import date
from ..models import DatePrecision
DAY = DatePrecision.day.value
MONTH = DatePrecision.month.value
VALID_PRECISIONS = (DAY, MONTH)
def normalize_best_before(value: date | None, precision: str | None) -> date | None:
"""Legt ein Monats-MHD auf den letzten Tag dieses Monats.
Tagesgenaue Angaben und ``None`` bleiben unverändert.
"""
if value is None or precision != MONTH:
return value
return date(value.year, value.month, monthrange(value.year, value.month)[1])
def clean_precision(precision: str | None) -> str:
"""Unbekannte oder fehlende Angaben gelten als tagesgenau."""
return precision if precision in VALID_PRECISIONS else DAY

View File

@@ -7,8 +7,9 @@ from datetime import date
from sqlalchemy import asc
from sqlalchemy.orm import Session
from ..models import Lot, Movement, MovementType, Product, User
from ..models import DatePrecision, Lot, Movement, MovementType, Product, User
from .conversion import to_base
from .dates import clean_precision, normalize_best_before
class StockError(ValueError):
@@ -32,14 +33,22 @@ def check_in(
location_id: int | None,
user: User | None,
note: str | None = None,
best_before_precision: str | None = DatePrecision.day.value,
) -> Lot:
"""Legt eine neue Charge an und protokolliert die Bewegung."""
"""Legt eine neue Charge an und protokolliert die Bewegung.
Ist nur Monat/Jahr angegeben, wird das MHD auf den Monatsletzten gelegt;
die Genauigkeit wird an der Charge vermerkt, damit die Anzeige "09/2026"
statt "30.09.2026" schreiben kann.
"""
quantity_base = to_base(db, product, quantity, unit)
precision = clean_precision(best_before_precision)
lot = Lot(
product_id=product.id,
quantity=quantity_base,
best_before=best_before,
best_before=normalize_best_before(best_before, precision),
best_before_precision=precision,
location_id=location_id,
)
db.add(lot)