fix(db): fill item_receipts.confirmed_by in office paths; resilient per-statement v2.6 upgrade (root cause of 500 on receipt)
This commit is contained in:
+43
-39
@@ -344,42 +344,46 @@ BASE_UNITS: list[dict] = [
|
||||
|
||||
|
||||
async def ensure_v26(engine: AsyncEngine) -> None:
|
||||
"""Idempotent v2.6 upgrade: inventory domain + users + arrival flow + v2.5 deltas."""
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
for ddl in _V26_DDL:
|
||||
await conn.execute(text(ddl))
|
||||
# Seed the 8 fixed topic categories (ON CONFLICT keeps existing rows).
|
||||
for cat in TOPIC_CATEGORIES:
|
||||
await conn.execute(
|
||||
text(
|
||||
"INSERT INTO material_categories (slug, name) VALUES (:slug, :name) "
|
||||
"ON CONFLICT (slug) DO NOTHING"
|
||||
),
|
||||
{"slug": cat["slug"], "name": cat["name"]},
|
||||
)
|
||||
# Seed base units (idempotent) so materials/line items can be created.
|
||||
for u in BASE_UNITS:
|
||||
await conn.execute(
|
||||
text(
|
||||
"INSERT INTO units (code, name_fa, dimension, si_factor, is_base) "
|
||||
"VALUES (:code, :name_fa, :dimension, :si_factor, :is_base) "
|
||||
"ON CONFLICT (code) DO NOTHING"
|
||||
),
|
||||
u,
|
||||
)
|
||||
# v2.5 automation triggers (idempotent — DROP + CREATE keeps them in sync).
|
||||
await conn.execute(text("DROP TRIGGER IF EXISTS trg_supplier_materials_derive_category ON supplier_materials"))
|
||||
await conn.execute(text("""
|
||||
CREATE TRIGGER trg_supplier_materials_derive_category
|
||||
AFTER INSERT ON supplier_materials
|
||||
FOR EACH ROW EXECUTE FUNCTION fn_derive_material_category()
|
||||
"""))
|
||||
await conn.execute(text("DROP TRIGGER IF EXISTS trg_item_decisions_sync_price ON item_decisions"))
|
||||
await conn.execute(text("""
|
||||
CREATE TRIGGER trg_item_decisions_sync_price
|
||||
AFTER INSERT OR UPDATE OF status ON item_decisions
|
||||
FOR EACH ROW EXECUTE FUNCTION fn_sync_last_quoted_price()
|
||||
"""))
|
||||
except Exception: # pragma: no cover - DB unavailable at startup
|
||||
logger.exception("Could not ensure v2.6 schema — is the database ready?")
|
||||
"""Idempotent v2.6 upgrade: inventory domain + users + arrival flow + v2.5 deltas.
|
||||
|
||||
Every statement runs in its OWN transaction: one failure (e.g. duplicate
|
||||
material names blocking uq_material_name on an old volume) can never roll
|
||||
back the rest of the upgrade — otherwise `item_receipts.confirmed_by` and
|
||||
friends would stay missing and the arrival flow would crash with 500s.
|
||||
"""
|
||||
async def run_statement(ddl: str, params: dict | None = None) -> None:
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
await conn.execute(text(ddl), params or {})
|
||||
except Exception: # pragma: no cover - pre-existing data conflicts
|
||||
first_line = ddl.splitlines()[0][:100] if ddl.splitlines() else ddl[:100]
|
||||
logger.exception("v2.6 statement skipped (continuing): %s", first_line)
|
||||
|
||||
for ddl in _V26_DDL:
|
||||
await run_statement(ddl)
|
||||
# Seed the 8 fixed topic categories (ON CONFLICT keeps existing rows).
|
||||
for cat in TOPIC_CATEGORIES:
|
||||
await run_statement(
|
||||
"INSERT INTO material_categories (slug, name) VALUES (:slug, :name) ON CONFLICT (slug) DO NOTHING",
|
||||
{"slug": cat["slug"], "name": cat["name"]},
|
||||
)
|
||||
# Seed base units (idempotent) so materials/line items can be created.
|
||||
for u in BASE_UNITS:
|
||||
await run_statement(
|
||||
"INSERT INTO units (code, name_fa, dimension, si_factor, is_base) "
|
||||
"VALUES (:code, :name_fa, :dimension, :si_factor, :is_base) ON CONFLICT (code) DO NOTHING",
|
||||
u,
|
||||
)
|
||||
# v2.5 automation triggers (idempotent — DROP + CREATE keeps them in sync).
|
||||
await run_statement("DROP TRIGGER IF EXISTS trg_supplier_materials_derive_category ON supplier_materials")
|
||||
await run_statement("""
|
||||
CREATE TRIGGER trg_supplier_materials_derive_category
|
||||
AFTER INSERT ON supplier_materials
|
||||
FOR EACH ROW EXECUTE FUNCTION fn_derive_material_category()
|
||||
""")
|
||||
await run_statement("DROP TRIGGER IF EXISTS trg_item_decisions_sync_price ON item_decisions")
|
||||
await run_statement("""
|
||||
CREATE TRIGGER trg_item_decisions_sync_price
|
||||
AFTER INSERT OR UPDATE OF status ON item_decisions
|
||||
FOR EACH ROW EXECUTE FUNCTION fn_sync_last_quoted_price()
|
||||
""")
|
||||
|
||||
Reference in New Issue
Block a user