Files
MABEA/backend/app/services/objekte.py
T
patrickandClaude Sonnet 5 67cebbbf48
CI / backend-tests (push) Failing after 28s
Test-Engine pro Test statt Modul-Global erzeugen (echter Fix für Event-Loop-Konflikt)
Der vorherige Fix (asyncio_default_fixture_loop_scope/asyncio_default_test_loop_scope)
griff nicht - letztere Option existiert in der gepinnten pytest-asyncio-Version
vermutlich noch nicht, Tests liefen weiterhin auf function-scoped Loops während die
Engine session-weit global war. Echte Ursache behoben: db_session erzeugt jetzt eine
frische AsyncEngine pro Test (und disposed sie danach), sodass Engine/Pool immer auf
derselben Loop laufen wie der Test selbst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
2026-09-03 23:27:06 +02:00

103 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from decimal import Decimal
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.objekt import Objekt
from app.models.objektposition import Objektposition, ObjektpositionStatus
from app.models.vorlage import Beladungsvorlage, Vorlagenposition
async def sollmenge_map(db: AsyncSession, vorlage_id: int | None) -> dict[int, Decimal]:
"""material_id -> Sollmenge laut Vorlage (Prompt 10: Basis für die dynamische
Auflösung, wenn eine Objektposition keinen Override hat)."""
if vorlage_id is None:
return {}
result = await db.execute(
select(Vorlagenposition.material_id, Vorlagenposition.sollmenge).where(
Vorlagenposition.vorlage_id == vorlage_id
)
)
return {material_id: sollmenge for material_id, sollmenge in result.all()}
def sollmenge_effektiv(position: Objektposition, vorlagen_sollmengen: dict[int, Decimal]) -> Decimal:
if position.sollmenge_override is not None:
return position.sollmenge_override
return vorlagen_sollmengen.get(position.material_id, Decimal(0))
async def objekt_aus_vorlage(
db: AsyncSession,
*,
code: str,
name: str,
standort_id: int,
vorlage: Beladungsvorlage,
zustaendiger_server_id: int,
) -> Objekt:
"""Prompt 09 (Neuanlage, nicht Duplizieren): Objektpositionen werden aus den
Vorlagenpositionen initialisiert, Ist-Menge startet bei 0 ("nicht kontrolliert")."""
objekt = Objekt(
code=code,
name=name,
objekttyp_id=vorlage.objekttyp_id,
vorlage_id=vorlage.id,
standort_id=standort_id,
zustaendiger_server_id=zustaendiger_server_id,
)
db.add(objekt)
await db.flush()
result = await db.execute(select(Vorlagenposition).where(Vorlagenposition.vorlage_id == vorlage.id))
for vorlagenposition in result.scalars().all():
db.add(
Objektposition(
objekt_id=objekt.id,
material_id=vorlagenposition.material_id,
ist_status=ObjektpositionStatus.aktiv,
istmenge=Decimal(0),
)
)
await db.flush()
return objekt
async def dupliziere_objekt(
db: AsyncSession,
*,
quelle: Objekt,
neuer_code: str,
neuer_name: str,
neuer_standort_id: int,
zustaendiger_server_id: int,
) -> Objekt:
"""Prompt 09: Struktur/Sollmengen (inkl. individueller Overrides) werden kopiert,
Zustandsdaten (Ist-Menge, SN, Ablauf, Charge) NICHT neues Objekt startet
"nicht kontrolliert". Fehlbestände/Historie existieren für ein neues Objekt
ohnehin nicht (kein Kopierschritt nötig)."""
neues_objekt = Objekt(
code=neuer_code,
name=neuer_name,
objekttyp_id=quelle.objekttyp_id,
vorlage_id=quelle.vorlage_id,
standort_id=neuer_standort_id,
zustaendiger_server_id=zustaendiger_server_id,
)
db.add(neues_objekt)
await db.flush()
result = await db.execute(select(Objektposition).where(Objektposition.objekt_id == quelle.id))
for quellposition in result.scalars().all():
db.add(
Objektposition(
objekt_id=neues_objekt.id,
material_id=quellposition.material_id,
sollmenge_override=quellposition.sollmenge_override,
ist_status=quellposition.ist_status,
istmenge=Decimal(0),
)
)
await db.flush()
return neues_objekt