- Show all available inventory items immediately when selecting a warehouse in withdrawal form - Add vertical scrolling container with max height to prevent layout overflow for large item lists - Add Material column to the inventory rows table for clear item identification - Add required 'delivered_to' recipient field in withdrawal form with frontend validation - Track delivered_to in database, models, schemas, and API - Display recipient badge on withdrawal cards in daily control page - Group daily control withdrawals by inventory ID and recipient
87 lines
3.1 KiB
Python
87 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
|
|
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
|