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:
@@ -4,7 +4,7 @@ from datetime import date, datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from .models import BaseUnit, Role, UnitKind
|
||||
from .models import BaseUnit, DatePrecision, Role, UnitKind
|
||||
|
||||
|
||||
# ---- Units ----
|
||||
@@ -127,6 +127,8 @@ class ProductBase(BaseModel):
|
||||
package_size: float | None = Field(default=None, gt=0)
|
||||
# Bezeichnung eines Gebindes ("Packung", "Glas", "Tüte", …)
|
||||
package_label: str | None = Field(default=None, max_length=32)
|
||||
# Voreingestellte MHD-Genauigkeit dieses Produkts (z.B. Konserven: nur Monat).
|
||||
date_precision: DatePrecision = DatePrecision.day
|
||||
group_id: int | None = None
|
||||
min_stock: float | None = Field(default=None, ge=0) # immer in Basiseinheiten
|
||||
# Nur für die Anzeige: in welcher Einheit der Mindestbestand erfasst wurde.
|
||||
@@ -147,6 +149,7 @@ class ProductUpdate(BaseModel):
|
||||
unit_id: int | None = None
|
||||
package_size: float | None = Field(default=None, gt=0)
|
||||
package_label: str | None = Field(default=None, max_length=32)
|
||||
date_precision: DatePrecision | None = None
|
||||
group_id: int | None = None
|
||||
min_stock: float | None = Field(default=None, ge=0)
|
||||
min_stock_unit_id: int | None = None
|
||||
@@ -164,6 +167,7 @@ class ProductOut(BaseModel):
|
||||
display_unit_id: int | None = None
|
||||
package_size: float | None
|
||||
package_label: str | None = None
|
||||
date_precision: DatePrecision = DatePrecision.day
|
||||
group_id: int | None
|
||||
min_stock: float | None
|
||||
min_stock_unit_id: int | None = None
|
||||
@@ -204,6 +208,8 @@ class CheckInRequest(BaseModel):
|
||||
quantity: float = Field(gt=0)
|
||||
unit: str
|
||||
best_before: date | None = None
|
||||
# "month" legt das MHD auf den Monatsletzten (siehe services/dates.py).
|
||||
best_before_precision: DatePrecision = DatePrecision.day
|
||||
location_id: int | None = None
|
||||
note: str | None = None
|
||||
|
||||
@@ -224,6 +230,7 @@ class LotOut(BaseModel):
|
||||
product_id: int
|
||||
quantity: float
|
||||
best_before: date | None
|
||||
best_before_precision: DatePrecision = DatePrecision.day
|
||||
location_id: int | None
|
||||
created_at: datetime
|
||||
|
||||
@@ -232,6 +239,7 @@ class LotUpdate(BaseModel):
|
||||
"""Korrektur einer Charge (Vertipper beim Einlagern o.ä.)."""
|
||||
quantity: float | None = Field(default=None, gt=0)
|
||||
best_before: date | None = None
|
||||
best_before_precision: DatePrecision | None = None
|
||||
location_id: int | None = None
|
||||
|
||||
|
||||
@@ -244,6 +252,7 @@ class CheckInLine(BaseModel):
|
||||
"""Eine Charge innerhalb eines Sammel-Einlagerns (Menge + eigenes MHD)."""
|
||||
quantity: float = Field(gt=0)
|
||||
best_before: date | None = None
|
||||
best_before_precision: DatePrecision = DatePrecision.day
|
||||
location_id: int | None = None
|
||||
|
||||
|
||||
@@ -283,6 +292,7 @@ class ExpiringItem(BaseModel):
|
||||
quantity: float # in Basiseinheiten
|
||||
base_unit: BaseUnit
|
||||
best_before: date
|
||||
best_before_precision: DatePrecision = DatePrecision.day
|
||||
days_left: int
|
||||
# Für die Anzeige in Artikeleinheiten:
|
||||
package_size: float | None = None
|
||||
|
||||
Reference in New Issue
Block a user