Commit Graph
5 Commits
Author SHA1 Message Date
whqreza 8adfd2267f refactor(catalog)!: decouple materials from categories (v2.7)
Materials are now global and category-free. The category lives only on the supplier (suppliers.category_id); every category-facing query goes through the supplier edge instead of the material row.

Schema (migration.sql + init_db DDL):
- drop the materials.category_id column, its FK, and the v2.5 first-link-wins automation (fn_derive_material_category + trg_supplier_materials_derive_category) — the DDL runs idempotently at API startup

Backend:
- GET /materials?category_id=… now filters via EXISTS(supplier_materials ⋈ suppliers.category_id)
- quote category filters (quotes list) use the quote's supplier category
- /pm/on-the-way derives the display category from the supplier of the quote
- supplier_materials links no longer derive/return a material category
- expand_service: material expand=category resolves to null (kept for contract compatibility)
- material create/update no longer validates or stores category_id; seed.py aligned

Frontend:
- material creation no longer sends category_id
- formatMaterialTitle reworked to a strict order with « . » separator: [Brand] . [Material] . [Spec 1] . [Spec 2], brand deduped when the stored name already starts with it; specs separator switched from « · » to « . »

Validated: python compile, api + frontend image builds (tsc + vite) clean; branch cut from the tip of prod_v4 (= origin/production, 5a9ef40 — the team's merge made them identical).
2026-09-19 13:19:26 +03:30
whqreza 5084623be5 feat(quotes): lock quotes with posted receipts; per-item target project + title dedup
Quotes that already have warehouse receipts can no longer be edited or rolled back:
- backend: quote_has_receipts() guard — replace_quote_children and every backward status transition now return 409 with «این پیشنهاد به‌دلیل ثبت رسید در انبار، غیرقابل ویرایش یا بازگشت است.»
- reopening to «offered» additionally clears item_decisions.project_id (the purchased-project link belongs to acceptance, not to a reopened offer)
- frontend EditModal: hasReceipts locks the whole form — disabled inputs and date pickers (JalaliDatePicker/NumberInput/MilestoneRow/MultiSelectCombobox gained disabled support), add/remove controls for items, termsheets and milestones hidden, status change blocked, amber warning banner; quote deletion now goes through a confirmation dialog (useDeleteQuote)
- date-only semantics: JalaliDatePicker keeps/emits YYYY-MM-DD when withTime=false (typing, «امروز», day selection) and toDateOnly no longer mangles pure date strings

Per-item target project (candidate projects stay intact):
- expand_service exposes the decision target project (decision.project + line_item.target_project)
- the accept payload derives item_decisions.project_id from that target project and sends item_projects: [] so line_item_projects keeps the original candidates untouched
- AcceptModal/EditModal select the target project per line item

Material titles:
- quoteUtils.formatMaterialTitle() composes one normalized, deduplicated «name + brand + specs» title, replacing materialLabel/materialSpecs/specsLabel usage across dashboard cards, quote detail and the modals

Verified: python compile + api/frontend image builds (tsc + vite) clean.
2026-09-16 19:14:06 +03:30
ali_mohaghegh ca84d8664e feat(backend): demo seeder, receipt allocations in expand, FOUND ceiling guard
- 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)
2026-09-02 18:15:11 +03:30
ali_mohaghegh 83f4fb0747 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
2026-09-01 17:28:06 +03:30
ali_mohaghegh 170aed66ea feat(backend): add FastAPI backend — procurement, inventory domain, Casdoor auth, PM arrival endpoints 2026-08-25 19:08:14 +03:30