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)
This commit is contained in:
@@ -25,6 +25,7 @@ from app.models.catalog import (
|
||||
from app.models.procurement import (
|
||||
ItemDecision,
|
||||
ItemReceipt,
|
||||
ItemReceiptAllocation,
|
||||
ItemTermsheet,
|
||||
LineItem,
|
||||
LineItemProjects,
|
||||
@@ -37,6 +38,7 @@ from app.models.procurement import (
|
||||
Termsheet,
|
||||
TermsheetScheduleStep,
|
||||
)
|
||||
from app.models.inventory import Warehouse
|
||||
from app.services.eav_service import flatten_eav_values
|
||||
from app.utils.serialization import to_date, to_iso
|
||||
|
||||
@@ -307,6 +309,7 @@ class _LineItemData:
|
||||
self.attr_defs: dict[uuid.UUID, AttributeDefinition] = {}
|
||||
self.decisions: dict[uuid.UUID, ItemDecision] = {}
|
||||
self.receipts_by_decision: dict[uuid.UUID, list[ItemReceipt]] = {}
|
||||
self.allocations_by_receipt: dict[uuid.UUID, list[dict]] = {}
|
||||
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]] = {}
|
||||
@@ -380,6 +383,26 @@ async def _load_line_item_data(db: AsyncSession, line_items: list[LineItem], exp
|
||||
by_dec.setdefault(r.item_decision_id, []).append(r)
|
||||
data.receipts_by_decision = by_dec
|
||||
|
||||
# Warehouse allocations per receipt — needed by the ledger's
|
||||
# «رسید شده» split (target warehouse per received part).
|
||||
alloc_rows = (
|
||||
await db.execute(
|
||||
select(ItemReceiptAllocation, Warehouse)
|
||||
.join(Warehouse, Warehouse.id == ItemReceiptAllocation.warehouse_id)
|
||||
.where(
|
||||
ItemReceiptAllocation.receipt_id.in_([r.id for r in receipts])
|
||||
if receipts
|
||||
else False
|
||||
)
|
||||
)
|
||||
).all()
|
||||
allocs_by_receipt: dict[uuid.UUID, list[dict]] = {}
|
||||
for alloc, wh in alloc_rows:
|
||||
allocs_by_receipt.setdefault(alloc.receipt_id, []).append(
|
||||
{"warehouse_id": str(wh.id), "warehouse_name": wh.name, "quantity": float(alloc.quantity)}
|
||||
)
|
||||
data.allocations_by_receipt = allocs_by_receipt
|
||||
|
||||
if unit_ids:
|
||||
units = (await db.execute(select(Unit).where(Unit.id.in_(unit_ids)))).scalars().all()
|
||||
data.units = {u.id: u for u in units}
|
||||
@@ -456,15 +479,20 @@ def _line_item_expanded(li: LineItem, expand: set[str], data: _LineItemData, inc
|
||||
d["accepted_termsheet_id"] = str(chosen_it.termsheet_id)
|
||||
if "receipt" in expand:
|
||||
# 1:N partial deliveries — emit the aggregate (sum of received qty,
|
||||
# latest receipt kept for the received_at contract).
|
||||
# latest receipt kept for the received_at contract) + the warehouse
|
||||
# allocations so the ledger can render the «رسید شده» split.
|
||||
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"))
|
||||
allocs: list[dict] = []
|
||||
for r in rs:
|
||||
allocs.extend(data.allocations_by_receipt.get(r.id, []))
|
||||
d["receipt"] = {
|
||||
**receipt_brief(latest),
|
||||
"received_qty": float(total),
|
||||
"receipt_count": len(rs),
|
||||
"allocations": allocs,
|
||||
}
|
||||
else:
|
||||
d["receipt"] = None
|
||||
|
||||
Reference in New Issue
Block a user