"""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 delivered_to: 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