Warehouses now belong strictly and exclusively to the project they were created under; cross-project reassignment is removed end-to-end.
Backend: PATCH /inventory/warehouses/{id} no longer applies project_id — any attempt to move a warehouse to a different project returns 400 with «پروژهی انبار قابل تغییر نیست — انبار برای همیشه به پروژهی والدش تعلق دارد» (sending the current project stays a harmless no-op). The WarehouseUpdate schema documents project_id as accept-only-to-reject; creation still requires project_id and delete is unchanged.
Frontend: the Organization page's per-warehouse project dropdown («انتقال به پروژه دیگر») and its moveWarehouse handler are removed — warehouses render statically inside their project card with only the dashboard link and delete; the warehouses.update API type no longer accepts project_id. WarehousesPage already had no move UI (rename/describe only).
Verified end-to-end against the running stack: rename 200; same-project project_id no-op 200; cross-project reassignment 400 with the guard message; warehouse remains in its original project; test data cleaned up. python compile + api/frontend builds clean.
86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
"""Pydantic v2 request schemas — inventory domain (fork's contract, unchanged)."""
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import Literal
|
|
from uuid import UUID
|
|
|
|
from pydantic import Field, model_validator
|
|
|
|
from app.models.inventory import INVENTORY_TRANSACTION_TYPES
|
|
from app.schemas.common import APIModel
|
|
|
|
TransactionType = Literal[
|
|
"PURCHASE",
|
|
"WITHDRAWAL",
|
|
"RETURN",
|
|
"TRANSFER_IN",
|
|
"TRANSFER_OUT",
|
|
"DAMAGE",
|
|
"WASTE",
|
|
"LOSS",
|
|
"FOUND",
|
|
"STOCK_COUNT",
|
|
]
|
|
|
|
|
|
class WarehouseCreate(APIModel):
|
|
project_id: UUID
|
|
name: str = Field(min_length=1, max_length=255)
|
|
description: str | None = None
|
|
|
|
|
|
class WarehouseUpdate(APIModel):
|
|
"""Rename/re-describe a warehouse.
|
|
|
|
`project_id` is accepted only to be rejected: a warehouse belongs
|
|
permanently to its parent project and cannot be reassigned (the router
|
|
answers 400 unless the value equals the current project).
|
|
"""
|
|
|
|
name: str | None = Field(default=None, min_length=1, max_length=255)
|
|
description: str | None = None
|
|
project_id: UUID | None = None
|
|
|
|
|
|
class InventoryTransactionCreate(APIModel):
|
|
"""One inventory operation.
|
|
|
|
- For all types except STOCK_COUNT: `quantity` is the amount to add/remove.
|
|
- STOCK_COUNT uses `physical_qty` (the counted quantity); the difference
|
|
against the recorded quantity is computed and applied by the service.
|
|
- TRANSFER_OUT requires `dest_warehouse_id`; the service creates the linked
|
|
TRANSFER_IN row in the destination warehouse (transfer_pair_id).
|
|
- `specs` is a dict of technical specifications (dynamic multi-spec entries).
|
|
- `transaction_date` defaults to server now() when omitted.
|
|
"""
|
|
|
|
transaction_type: TransactionType
|
|
warehouse_id: UUID
|
|
dest_warehouse_id: UUID | None = None
|
|
material_id: UUID
|
|
brand_id: UUID | None = None
|
|
quantity: Decimal | None = Field(default=None, gt=0)
|
|
physical_qty: Decimal | None = Field(default=None, ge=0)
|
|
unit_id: int | None = None
|
|
specs: dict | None = None
|
|
note: str | None = None
|
|
transaction_date: datetime | None = None
|
|
|
|
@model_validator(mode="after")
|
|
def validate_type_rules(self):
|
|
t = self.transaction_type
|
|
if t == "STOCK_COUNT":
|
|
if self.physical_qty is None:
|
|
raise ValueError("برای شمارش موجودی، تعداد شمارششده (physical_qty) الزامی است")
|
|
else:
|
|
if self.quantity is None:
|
|
raise ValueError("تعداد (quantity) الزامی است")
|
|
if t == "TRANSFER_OUT":
|
|
if self.dest_warehouse_id is None:
|
|
raise ValueError("برای انتقال خروجی، انبار مقصد (dest_warehouse_id) الزامی است")
|
|
if self.dest_warehouse_id == self.warehouse_id:
|
|
raise ValueError("انبار مبدأ و مقصد نمیتوانند یکسان باشند")
|
|
if t not in INVENTORY_TRANSACTION_TYPES:
|
|
raise ValueError(f"نوع تراکنش نامعتبر است: {t}")
|
|
return self
|