The router loads the material row (starting a transaction) before calling
material_service.update_material, which then opened its own
async with db.begin() → SQLAlchemy 'A transaction is already begun on
this Session' → INTERNAL_ERROR on every PATCH (incl. changing the
reference unit from ضرایب تبدیل). Mutations now run inside the request
transaction committed by the get_db dependency.
- tools/seed.py: wipes app tables and seeds a clean realistic demo (3
projects x 2 warehouses, 4 suppliers, 12 materials across all 8 topic
categories, 4 quotes covering offered / in-transit / partial / completed
with receipts + matching stock); user roles bound to Casdoor identities
by username (seed_casdoor.py first)
- expand_service: the line-item receipt aggregate now carries the warehouse
allocations (target warehouse per received part) for the ledger's
«رسید شده» split
- inventory_service: FOUND («پیدا شده») is guarded so recovery cannot exceed
the missing balance for the same (warehouse, material, brand, specs) —
422 with the remaining balance; STOCK_COUNT semantics unchanged (absolute
counted qty, auto-diff)
- 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
- verify kid from the JWT header (pyjwt decode() returns claims, not the
header — the old lookup was always None and rejected every token)
- convert JWK entries to crypto keys explicitly (pyjwt 2.13 dropped dict
keys) and fetch the JWKS from /.well-known/jwks
- 401 for missing/invalid/expired/wrong-issuer/wrong-audience tokens
(was 403/400); 403 stays for valid-but-roleless users; 503 when Casdoor
is unreachable; new CASDOOR_ISSUER setting separates the in-network
endpoint from the browser-facing issuer
- dev-header fallback in inventory/pm scopes now requires an explicit
X-User-Role header (no auth info at all -> 401)
- new POST /api/v1/auth/login: password grant against Casdoor, local JWT
verification, auto-provisioning, returns token + profile with the role
resolved from the app users table