fix(backend): arrival-confirmation 500, receipt state, RBAC, Casdoor user sync
- pm.py/inventory.py: remove db.begin() on already-begun sessions — the scope dependencies read from the session first, so opening a new transaction raised InvalidRequestError -> 500 on every receipt/transaction (JWT path); get_db already commits/rolls back atomically - pm.py: receipt response returns the real derived state (was hardcoded 'arrived'); dev-user creation is race-free (insert on conflict) - expand_service: aggregate 1:N receipts per line item (received_qty sum + count) so the ledger can show partial-receipt totals - users.py: role changes are admin-only (RBAC); new POST /users/sync mirrors Casdoor org users into the app users table (identity only, roles untouched) - config/compose: CASDOOR_ADMIN_USER/PASSWORD for the sync endpoint - seed_casdoor: redirect URI updated for the Traefik edge
This commit is contained in:
@@ -8,6 +8,7 @@ queries per related table, then assembly from in-memory maps.
|
||||
"""
|
||||
import uuid
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -305,7 +306,7 @@ class _LineItemData:
|
||||
self.specs_by_line: dict[uuid.UUID, list[LineItemSpec]] = {}
|
||||
self.attr_defs: dict[uuid.UUID, AttributeDefinition] = {}
|
||||
self.decisions: dict[uuid.UUID, ItemDecision] = {}
|
||||
self.receipts_by_decision: dict[uuid.UUID, ItemReceipt] = {}
|
||||
self.receipts_by_decision: dict[uuid.UUID, list[ItemReceipt]] = {}
|
||||
self.item_termsheets_by_line: dict[uuid.UUID, list[ItemTermsheet]] = {}
|
||||
self.item_termsheets_by_id: dict[uuid.UUID, ItemTermsheet] = {}
|
||||
self.projects_by_line: dict[uuid.UUID, list[Project]] = {}
|
||||
@@ -374,7 +375,10 @@ async def _load_line_item_data(db: AsyncSession, line_items: list[LineItem], exp
|
||||
dec_ids = [d.id for d in decisions]
|
||||
if dec_ids:
|
||||
receipts = (await db.execute(select(ItemReceipt).where(ItemReceipt.item_decision_id.in_(dec_ids)))).scalars().all()
|
||||
data.receipts_by_decision = {r.item_decision_id: r for r in receipts}
|
||||
by_dec: dict[uuid.UUID, list[ItemReceipt]] = {}
|
||||
for r in receipts:
|
||||
by_dec.setdefault(r.item_decision_id, []).append(r)
|
||||
data.receipts_by_decision = by_dec
|
||||
|
||||
if unit_ids:
|
||||
units = (await db.execute(select(Unit).where(Unit.id.in_(unit_ids)))).scalars().all()
|
||||
@@ -451,8 +455,19 @@ def _line_item_expanded(li: LineItem, expand: set[str], data: _LineItemData, inc
|
||||
if chosen_it is not None:
|
||||
d["accepted_termsheet_id"] = str(chosen_it.termsheet_id)
|
||||
if "receipt" in expand:
|
||||
r = data.receipts_by_decision.get(decision.id) if decision is not None else None
|
||||
d["receipt"] = receipt_brief(r) if r is not None else None
|
||||
# 1:N partial deliveries — emit the aggregate (sum of received qty,
|
||||
# latest receipt kept for the received_at contract).
|
||||
rs = data.receipts_by_decision.get(decision.id) if decision is not None else None
|
||||
if rs:
|
||||
latest = rs[-1]
|
||||
total = sum((r.received_qty for r in rs), Decimal("0"))
|
||||
d["receipt"] = {
|
||||
**receipt_brief(latest),
|
||||
"received_qty": float(total),
|
||||
"receipt_count": len(rs),
|
||||
}
|
||||
else:
|
||||
d["receipt"] = None
|
||||
if include_termsheet_ids:
|
||||
d["termsheet_ids"] = [str(it.termsheet_id) for it in data.item_termsheets_by_line.get(li.id, [])]
|
||||
return d
|
||||
|
||||
Reference in New Issue
Block a user