Stammdaten-Export/Import (JSON) + Lagerort-Eltern bleibt beim Anlegen
- Backend: services/master_data.py exportiert/importiert Kategorien (inkl. eigener Felder), Lagerorte, Einheiten und Gebinde als JSON – MIT IDs, damit gedruckte QR-Codes und Verweise nach dem Wiederherstellen passen. Import-Modus skip (Vorhandenes lassen) oder overwrite (per ID aktualisieren); Namens- Konflikte werden uebersprungen, nicht abgebrochen. Postgres-Sequenzen werden danach angehoben. Endpunkte GET /export/master-data, POST /import/master-data. 5 neue Tests, Suite 162 gruen. - Web: neue Karte "Stammdaten sichern & wiederherstellen (JSON)" in Import/Export. - Web: beim Lagerort-Anlegen bleibt der uebergeordnete Ort in der Auswahl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
91
backend/tests/test_master_data.py
Normal file
91
backend/tests/test_master_data.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Stammdaten-Export/Import (JSON) mit ID-Erhalt und Konfliktmodi."""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.database import Base
|
||||
from app.models import Category, FieldDefinition, Location, PackageType, Unit
|
||||
from app.services.master_data import (
|
||||
FORMAT_KEY,
|
||||
export_master_data,
|
||||
import_master_data,
|
||||
)
|
||||
|
||||
|
||||
def _fresh_db():
|
||||
engine = create_engine(
|
||||
"sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool
|
||||
)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
return sessionmaker(bind=engine, autoflush=False, autocommit=False)()
|
||||
|
||||
|
||||
def test_roundtrip_erhaelt_ids_und_baum(db):
|
||||
ober = Category(name="Elektronik", tracking="object")
|
||||
db.add(ober)
|
||||
db.flush()
|
||||
unter = Category(name="Kabel", parent_id=ober.id, tracking="object")
|
||||
db.add(unter)
|
||||
db.flush()
|
||||
db.add(FieldDefinition(category_id=ober.id, label="Kapazität", key="kapazitaet",
|
||||
field_type="number", unit="mAh"))
|
||||
haus = Location(name="Hedingen")
|
||||
db.add(haus)
|
||||
db.flush()
|
||||
keller = Location(name="Keller", parent_id=haus.id)
|
||||
db.add(keller)
|
||||
beutel = PackageType(singular="Beutel", plural="Beutel")
|
||||
db.add(beutel)
|
||||
db.commit()
|
||||
beutel_id = beutel.id
|
||||
|
||||
data = export_master_data(db)
|
||||
|
||||
ziel = _fresh_db()
|
||||
import_master_data(ziel, data, mode="skip")
|
||||
|
||||
assert ziel.get(Category, ober.id).name == "Elektronik"
|
||||
assert ziel.get(Category, unter.id).parent_id == ober.id # Baum erhalten
|
||||
assert ziel.get(Location, keller.id).parent_id == haus.id
|
||||
assert ziel.query(FieldDefinition).filter_by(category_id=ober.id).first().label == "Kapazität"
|
||||
assert ziel.get(PackageType, beutel_id).singular == "Beutel" # id erhalten
|
||||
|
||||
|
||||
def test_skip_laesst_vorhandenes_unangetastet(db):
|
||||
a = Location(name="Alt")
|
||||
db.add(a)
|
||||
db.commit()
|
||||
data = {FORMAT_KEY: 1, "locations": [{"id": a.id, "name": "Neu"}]}
|
||||
rep = import_master_data(db, data, mode="skip")
|
||||
assert db.get(Location, a.id).name == "Alt"
|
||||
assert rep["skipped"].get("locations") == 1
|
||||
|
||||
|
||||
def test_overwrite_aktualisiert_per_id(db):
|
||||
a = Location(name="Alt")
|
||||
db.add(a)
|
||||
db.commit()
|
||||
data = {FORMAT_KEY: 1, "locations": [{"id": a.id, "name": "Neu"}]}
|
||||
rep = import_master_data(db, data, mode="overwrite")
|
||||
assert db.get(Location, a.id).name == "Neu"
|
||||
assert rep["updated"].get("locations") == 1
|
||||
|
||||
|
||||
def test_falsche_datei_wirft():
|
||||
with pytest.raises(ValueError):
|
||||
import_master_data(_fresh_db(), {"foo": 1})
|
||||
|
||||
|
||||
def test_namenskonflikt_wird_uebersprungen_nicht_abgebrochen(db):
|
||||
# Vorhandene Einheit unter id X; Import bringt gleichen Namen unter anderer id.
|
||||
vorhanden = Unit(name="Spezialeinheit", kind="count", factor=1)
|
||||
db.add(vorhanden)
|
||||
db.commit()
|
||||
data = {FORMAT_KEY: 1, "units": [{"id": vorhanden.id + 999, "name": "Spezialeinheit",
|
||||
"kind": "count", "factor": 1}]}
|
||||
rep = import_master_data(db, data, mode="skip")
|
||||
# Gleicher Name unter neuer id -> Eindeutigkeit verletzt -> uebersprungen.
|
||||
assert rep["skipped"].get("units") == 1
|
||||
assert db.query(Unit).filter_by(name="Spezialeinheit").count() == 1
|
||||
Reference in New Issue
Block a user