feat(material): Material-Dubletten zusammenführen
Verwendung je Material einsehbar (Objektposition/Vorlagenposition/
Bestand/Kontrollposition/Fehlbestand/Ausgabe/Nachfüllung/
Materialbewegung), POST /materialien/{id}/ersetzen hängt alle
Referenzen auf ein Ziel-Material um und löscht die Quelle. Bricht bei
Konflikten ab (z.B. dasselbe Objekt hätte danach zwei Positionen für
dasselbe Material) statt Daten stillschweigend zu verlieren. Admin-UI:
"Zusammenführen" je Material zeigt Verwendung + Ziel-Auswahl.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -8,6 +8,7 @@ from app.models.auth import RolleTyp
|
||||
from app.models.objekt import Objekt
|
||||
from app.models.stammdaten import Bereich, Fach, Kategorie, Material, Objekttyp, Standort
|
||||
from app.models.vorlage import Beladungsvorlage, Vorlagenposition
|
||||
from app.services.material_merge import finde_konflikte, fuehre_zusammen, hole_verwendung
|
||||
from app.schemas.stammdaten import (
|
||||
BereichCreate,
|
||||
BereichRead,
|
||||
@@ -23,6 +24,8 @@ from app.schemas.stammdaten import (
|
||||
MaterialCreate,
|
||||
MaterialRead,
|
||||
MaterialUpdate,
|
||||
MaterialZusammenfuehren,
|
||||
MaterialZusammenfuehrenErgebnis,
|
||||
ObjekttypCreate,
|
||||
ObjekttypRead,
|
||||
ObjekttypUpdate,
|
||||
@@ -365,3 +368,44 @@ async def aendere_material(
|
||||
setattr(material, feld, wert)
|
||||
await db.flush()
|
||||
return material
|
||||
|
||||
|
||||
@router.get("/materialien/{material_id}/verwendung", response_model=dict[str, int])
|
||||
async def hole_material_verwendung(
|
||||
material_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_=Depends(require_roles(RolleTyp.administration)),
|
||||
) -> dict[str, int]:
|
||||
if await db.get(Material, material_id) is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Material nicht gefunden")
|
||||
return await hole_verwendung(db, material_id)
|
||||
|
||||
|
||||
@router.post("/materialien/{material_id}/ersetzen", response_model=MaterialZusammenfuehrenErgebnis)
|
||||
async def ersetze_material(
|
||||
material_id: int,
|
||||
payload: MaterialZusammenfuehren,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_=Depends(require_roles(RolleTyp.administration)),
|
||||
) -> MaterialZusammenfuehrenErgebnis:
|
||||
"""Führt zwei Material-Datensätze zusammen (z. B. Import-Dubletten):
|
||||
quelle_id (aus der URL) wird gelöscht, alle Referenzen zeigen danach auf
|
||||
ziel_material_id. Bricht bei Konflikten ab (z. B. dasselbe Objekt hätte
|
||||
danach zwei Positionen für dasselbe Material) statt Daten zu verlieren."""
|
||||
quelle = await db.get(Material, material_id)
|
||||
if quelle is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Material nicht gefunden")
|
||||
if payload.ziel_material_id == material_id:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Quelle und Ziel sind identisch")
|
||||
ziel = await db.get(Material, payload.ziel_material_id)
|
||||
if ziel is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ziel-Material nicht gefunden")
|
||||
|
||||
konflikte = await finde_konflikte(db, quelle_id=material_id, ziel_id=payload.ziel_material_id)
|
||||
if konflikte:
|
||||
return MaterialZusammenfuehrenErgebnis(erfolgreich=False, konflikte=konflikte)
|
||||
|
||||
await fuehre_zusammen(db, quelle_id=material_id, ziel_id=payload.ziel_material_id)
|
||||
await db.delete(quelle)
|
||||
await db.flush()
|
||||
return MaterialZusammenfuehrenErgebnis(erfolgreich=True, konflikte=[])
|
||||
|
||||
Reference in New Issue
Block a user