Ablauf/Charge/SN gehören zur Kontroll-Erfassung, nicht nur zur Admin-Pflege
Nutzer-Vorgabe: "Ablauf/Charge/SN gehört mit zu den Kontrollen wie auch die
Menge". PUT .../positionen/{material_id} akzeptiert jetzt optional
ablaufdatum/chargennummer/seriennummer und schreibt sie auf die
Objektposition (Vier-Kernbegriffe bleiben unberührt: nur diese drei Felder,
niemals istmenge - das bleibt exklusiv Nachfüllung vorbehalten). Offline-
Queue und PositionCard erweitert: Felder erscheinen nur passend zum
Materialtyp (ablauf_charge -> Datum+Charge, geraet_sn -> SN). Admin-Panel
(ObjektPositionenPanel) bleibt zusätzlich für nachträgliche Korrekturen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
@@ -134,6 +134,9 @@ async def erfasse_kontrollposition(
|
||||
material_id=material_id,
|
||||
istmenge=payload.istmenge,
|
||||
zustaendiger_server_id=zustaendiger_server_id,
|
||||
ablaufdatum=payload.ablaufdatum,
|
||||
chargennummer=payload.chargennummer,
|
||||
seriennummer=payload.seriennummer,
|
||||
)
|
||||
return KontrollpositionRead(
|
||||
id=kontrollposition.id,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
@@ -24,6 +24,14 @@ class KontrolleRead(BaseModel):
|
||||
|
||||
class KontrollpositionInput(BaseModel):
|
||||
istmenge: Decimal
|
||||
# Nutzer-Vorgabe: Ablauf/Charge/SN gehören zur Erfassung während der
|
||||
# Kontrolle dazu, wie die Menge - werden hier optional mitgegeben und auf
|
||||
# der Objektposition gespeichert (Vier-Kernbegriffe bleiben unberührt:
|
||||
# nur Objektposition.istmenge darf ausschließlich Nachfüllung ändern,
|
||||
# diese drei Felder sind unabhängig davon).
|
||||
ablaufdatum: date | None = None
|
||||
chargennummer: str | None = None
|
||||
seriennummer: str | None = None
|
||||
|
||||
|
||||
class KontrollpositionRead(BaseModel):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import select
|
||||
@@ -22,12 +22,20 @@ async def erfasse_position(
|
||||
material_id: int,
|
||||
istmenge: Decimal,
|
||||
zustaendiger_server_id: int,
|
||||
ablaufdatum: date | None = None,
|
||||
chargennummer: str | None = None,
|
||||
seriennummer: str | None = None,
|
||||
) -> tuple[Kontrollposition, uuid.UUID | None]:
|
||||
"""Prompt 02.3/09 Vier-Kernbegriffe: Kontrolle erfasst nur, ändert nie die
|
||||
persistente Objektposition.istmenge. Bei Ist < Soll entsteht automatisch ein
|
||||
Fehlbestand (nie bei Überbestand, Prompt 02.4). Gibt zusätzlich die
|
||||
Fehlbestand-ID zurück (falls einer aktiv ist), damit die UI direkt eine
|
||||
Sofort-Nachfüllung (Karte 07) anbieten kann, ohne separat nachzufragen."""
|
||||
Sofort-Nachfüllung (Karte 07) anbieten kann, ohne separat nachzufragen.
|
||||
|
||||
Ablaufdatum/Chargennummer/Seriennummer sind KEINE Kernbegriffe-Verletzung:
|
||||
sie beschreiben das physische Exemplar, nicht die Ist-Menge, und werden
|
||||
daher hier - auf Wunsch als Teil der normalen Kontroll-Erfassung, nicht nur
|
||||
als separate Admin-Pflege - direkt mitgepflegt."""
|
||||
vorlagen_sollmengen = await sollmenge_map(db, objekt.vorlage_id)
|
||||
result = await db.execute(
|
||||
select(Objektposition).where(
|
||||
@@ -37,6 +45,14 @@ async def erfasse_position(
|
||||
objektposition = result.scalar_one_or_none()
|
||||
sollmenge = sollmenge_effektiv(objektposition, vorlagen_sollmengen) if objektposition else Decimal(0)
|
||||
|
||||
if objektposition is not None:
|
||||
if ablaufdatum is not None:
|
||||
objektposition.ablaufdatum = ablaufdatum
|
||||
if chargennummer is not None:
|
||||
objektposition.chargennummer = chargennummer
|
||||
if seriennummer is not None:
|
||||
objektposition.seriennummer = seriennummer
|
||||
|
||||
abweichung = istmenge != sollmenge
|
||||
|
||||
result = await db.execute(
|
||||
|
||||
@@ -3,6 +3,7 @@ from sqlalchemy import select
|
||||
|
||||
from app.models.fehlbestand import Fehlbestand
|
||||
from app.models.kontrolle import Kontrolle, KontrollStatus, Kontrollposition
|
||||
from app.models.objektposition import Objektposition
|
||||
from tests.conftest import auth_header, login
|
||||
|
||||
|
||||
@@ -37,6 +38,35 @@ async def test_fehlbestand_entsteht_bei_abweichung(client, db_session, objekt_mi
|
||||
assert fehlbestand.status.value == "offen"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ablauf_charge_werden_bei_erfassung_mitgepflegt(client, db_session, objekt_mit_position, mitarbeiter_user):
|
||||
"""Nutzer-Vorgabe: Ablauf/Charge/SN werden während der Kontroll-Erfassung
|
||||
mitgepflegt, wie die Menge - nicht nur separat über die Admin-Pflege."""
|
||||
objekt, material = objekt_mit_position
|
||||
token = await login(client, "mitarbeiter1")
|
||||
start = await _starte(client, token, objekt.id)
|
||||
kontrolle_id = start.json()["id"]
|
||||
|
||||
put_response = await client.put(
|
||||
f"/api/v1/kontrollen/{kontrolle_id}/positionen/{material.id}",
|
||||
json={"istmenge": "10", "ablaufdatum": "2027-06-01", "chargennummer": "LOT-1"},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert put_response.status_code == 200
|
||||
|
||||
result = await db_session.execute(
|
||||
select(Objektposition).where(
|
||||
Objektposition.objekt_id == objekt.id, Objektposition.material_id == material.id
|
||||
)
|
||||
)
|
||||
position = result.scalar_one()
|
||||
assert str(position.ablaufdatum) == "2027-06-01"
|
||||
assert position.chargennummer == "LOT-1"
|
||||
# Vier-Kernbegriffe bleiben unberührt: istmenge auf Objektposition wird von
|
||||
# der Kontrolle weiterhin NICHT gesetzt (nur von Nachfüllung).
|
||||
assert position.istmenge == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ueberbestand_erzeugt_keinen_fehlbestand(client, db_session, objekt_mit_position, mitarbeiter_user):
|
||||
objekt, material = objekt_mit_position
|
||||
|
||||
Reference in New Issue
Block a user