# ================================================================ # services/procurement/app/schemas/purchase_order_schemas.py # ================================================================ """ Purchase Order Schemas - Request/response models for purchase orders Migrated from Suppliers Service with procurement-specific additions """ import uuid from datetime import datetime, date from decimal import Decimal from typing import Optional, List, Dict, Any from pydantic import BaseModel, Field, ConfigDict # ================================================================ # BASE SCHEMAS # ================================================================ class PurchaseOrderBase(BaseModel): """Base schema for purchase order entities""" model_config = ConfigDict(from_attributes=True, str_strip_whitespace=True) # ================================================================ # PURCHASE ORDER ITEM SCHEMAS # ================================================================ class PurchaseOrderItemCreate(PurchaseOrderBase): """Schema for creating purchase order items""" inventory_product_id: uuid.UUID # Changed from ingredient_id to match model ordered_quantity: Decimal = Field(..., gt=0) unit_price: Decimal = Field(..., gt=0) unit_of_measure: str = Field(..., max_length=50) quality_requirements: Optional[str] = None item_notes: Optional[str] = None class PurchaseOrderItemUpdate(PurchaseOrderBase): """Schema for updating purchase order items""" ordered_quantity: Optional[Decimal] = Field(None, gt=0) unit_price: Optional[Decimal] = Field(None, gt=0) quality_requirements: Optional[str] = None item_notes: Optional[str] = None class PurchaseOrderItemResponse(PurchaseOrderBase): """Schema for purchase order item responses""" id: uuid.UUID tenant_id: uuid.UUID purchase_order_id: uuid.UUID inventory_product_id: uuid.UUID # Changed from ingredient_id to match model product_name: Optional[str] = None ordered_quantity: Decimal received_quantity: Decimal unit_price: Decimal unit_of_measure: str line_total: Decimal quality_requirements: Optional[str] = None item_notes: Optional[str] = None created_at: datetime updated_at: datetime # ================================================================ # PURCHASE ORDER SCHEMAS # ================================================================ class PurchaseOrderCreate(PurchaseOrderBase): """Schema for creating purchase orders""" supplier_id: uuid.UUID required_delivery_date: datetime # Use datetime with timezone priority: str = Field(default="normal", pattern="^(low|normal|high|critical)$") # Financial information tax_amount: Decimal = Field(default=Decimal("0"), ge=0) shipping_cost: Decimal = Field(default=Decimal("0"), ge=0) discount_amount: Decimal = Field(default=Decimal("0"), ge=0) subtotal: Decimal = Field(..., ge=0) # Additional information notes: Optional[str] = None # NEW: Procurement-specific fields procurement_plan_id: Optional[uuid.UUID] = None # Items items: List[PurchaseOrderItemCreate] = Field(..., min_length=1) class PurchaseOrderUpdate(PurchaseOrderBase): """Schema for updating purchase orders""" required_delivery_date: Optional[datetime] = None # Use datetime with timezone priority: Optional[str] = Field(None, pattern="^(low|normal|high|critical)$") # Financial information tax_amount: Optional[Decimal] = Field(None, ge=0) shipping_cost: Optional[Decimal] = Field(None, ge=0) discount_amount: Optional[Decimal] = Field(None, ge=0) # Additional information notes: Optional[str] = None class PurchaseOrderApproval(PurchaseOrderBase): """Schema for purchase order approval/rejection""" action: str = Field(..., pattern="^(approve|reject)$") notes: Optional[str] = None approved_by: Optional[uuid.UUID] = None class SupplierSummary(PurchaseOrderBase): """Schema for supplier summary - matches the structure returned by suppliers service""" id: str name: str supplier_code: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None contact_person: Optional[str] = None address_line1: Optional[str] = None city: Optional[str] = None country: Optional[str] = None supplier_type: Optional[str] = None status: Optional[str] = None mobile: Optional[str] = None website: Optional[str] = None payment_terms: Optional[str] = None standard_lead_time: Optional[int] = None quality_rating: Optional[float] = None delivery_rating: Optional[float] = None total_orders: Optional[int] = None total_amount: Optional[float] = None class PurchaseOrderResponse(PurchaseOrderBase): """Schema for purchase order responses""" id: uuid.UUID tenant_id: uuid.UUID supplier_id: uuid.UUID supplier_name: Optional[str] = None po_number: str status: str priority: str order_date: datetime required_delivery_date: Optional[datetime] = None # Use datetime with timezone estimated_delivery_date: Optional[datetime] = None # Use datetime with timezone actual_delivery_date: Optional[datetime] = None # Use datetime with timezone # Financial information subtotal: Decimal tax_amount: Decimal shipping_cost: Decimal discount_amount: Decimal total_amount: Decimal currency: str # Approval workflow approved_by: Optional[uuid.UUID] = None approved_at: Optional[datetime] = None rejection_reason: Optional[str] = None # NEW: Procurement-specific fields procurement_plan_id: Optional[uuid.UUID] = None auto_approved: bool = False auto_approval_rule_id: Optional[uuid.UUID] = None # Additional information notes: Optional[str] = None # AI/ML reasoning for procurement decisions (JTBD dashboard support) reasoning_data: Optional[Dict[str, Any]] = None # Audit fields created_at: datetime updated_at: datetime created_by: Optional[uuid.UUID] = None updated_by: Optional[uuid.UUID] = None # Related data items: List[PurchaseOrderItemResponse] = [] class PurchaseOrderWithSupplierResponse(PurchaseOrderResponse): """Schema for purchase order responses with supplier information""" supplier: Optional[SupplierSummary] = None class PurchaseOrderSummary(PurchaseOrderBase): """Schema for purchase order summary (list view)""" id: uuid.UUID po_number: str supplier_id: uuid.UUID supplier_name: Optional[str] = None status: str priority: str order_date: datetime required_delivery_date: datetime # Use datetime with timezone total_amount: Decimal currency: str auto_approved: bool = False created_at: datetime # ================================================================ # DELIVERY SCHEMAS # ================================================================ class DeliveryItemCreate(PurchaseOrderBase): """Schema for creating delivery items""" purchase_order_item_id: uuid.UUID inventory_product_id: uuid.UUID # Changed from ingredient_id to match model ordered_quantity: Decimal = Field(..., gt=0) delivered_quantity: Decimal = Field(..., ge=0) accepted_quantity: Decimal = Field(..., ge=0) rejected_quantity: Decimal = Field(default=Decimal("0"), ge=0) # Quality information batch_lot_number: Optional[str] = Field(None, max_length=100) expiry_date: Optional[datetime] = None # Use datetime with timezone quality_grade: Optional[str] = Field(None, max_length=20) # Issues and notes quality_issues: Optional[str] = None rejection_reason: Optional[str] = None item_notes: Optional[str] = None class DeliveryItemResponse(PurchaseOrderBase): """Schema for delivery item responses""" id: uuid.UUID tenant_id: uuid.UUID delivery_id: uuid.UUID purchase_order_item_id: uuid.UUID inventory_product_id: uuid.UUID # Changed from ingredient_id to match model ingredient_name: Optional[str] = None ordered_quantity: Decimal delivered_quantity: Decimal accepted_quantity: Decimal rejected_quantity: Decimal batch_lot_number: Optional[str] = None expiry_date: Optional[datetime] = None # Use datetime with timezone quality_grade: Optional[str] = None quality_issues: Optional[str] = None rejection_reason: Optional[str] = None item_notes: Optional[str] = None created_at: datetime updated_at: datetime class DeliveryCreate(PurchaseOrderBase): """Schema for creating deliveries""" purchase_order_id: uuid.UUID supplier_id: uuid.UUID supplier_delivery_note: Optional[str] = Field(None, max_length=100) scheduled_date: Optional[datetime] = None # Use datetime with timezone estimated_arrival: Optional[datetime] = None # Delivery details carrier_name: Optional[str] = Field(None, max_length=200) tracking_number: Optional[str] = Field(None, max_length=100) # Additional information notes: Optional[str] = None # Items items: List[DeliveryItemCreate] = Field(..., min_length=1) class DeliveryUpdate(PurchaseOrderBase): """Schema for updating deliveries""" supplier_delivery_note: Optional[str] = Field(None, max_length=100) scheduled_date: Optional[datetime] = None # Use datetime with timezone estimated_arrival: Optional[datetime] = None actual_arrival: Optional[datetime] = None # Delivery details carrier_name: Optional[str] = Field(None, max_length=200) tracking_number: Optional[str] = Field(None, max_length=100) # Quality inspection inspection_passed: Optional[bool] = None inspection_notes: Optional[str] = None quality_issues: Optional[Dict[str, Any]] = None # Additional information notes: Optional[str] = None class DeliveryResponse(PurchaseOrderBase): """Schema for delivery responses""" id: uuid.UUID tenant_id: uuid.UUID purchase_order_id: uuid.UUID supplier_id: uuid.UUID supplier_name: Optional[str] = None delivery_number: str supplier_delivery_note: Optional[str] = None status: str # Timing scheduled_date: Optional[datetime] = None # Use datetime with timezone estimated_arrival: Optional[datetime] = None actual_arrival: Optional[datetime] = None completed_at: Optional[datetime] = None # Delivery details carrier_name: Optional[str] = None tracking_number: Optional[str] = None # Quality inspection inspection_passed: Optional[bool] = None inspection_notes: Optional[str] = None quality_issues: Optional[Dict[str, Any]] = None # Receipt information received_by: Optional[uuid.UUID] = None received_at: Optional[datetime] = None # Additional information notes: Optional[str] = None # Audit fields created_at: datetime updated_at: datetime created_by: uuid.UUID # Related data items: List[DeliveryItemResponse] = [] # ================================================================ # INVOICE SCHEMAS # ================================================================ class SupplierInvoiceCreate(PurchaseOrderBase): """Schema for creating supplier invoices""" purchase_order_id: uuid.UUID supplier_id: uuid.UUID invoice_number: str = Field(..., max_length=100) invoice_date: datetime # Use datetime with timezone due_date: datetime # Use datetime with timezone # Financial information subtotal: Decimal = Field(..., ge=0) tax_amount: Decimal = Field(default=Decimal("0"), ge=0) shipping_cost: Decimal = Field(default=Decimal("0"), ge=0) discount_amount: Decimal = Field(default=Decimal("0"), ge=0) # Additional information notes: Optional[str] = None payment_reference: Optional[str] = Field(None, max_length=100) class SupplierInvoiceUpdate(PurchaseOrderBase): """Schema for updating supplier invoices""" due_date: Optional[datetime] = None # Use datetime with timezone payment_reference: Optional[str] = Field(None, max_length=100) notes: Optional[str] = None class SupplierInvoiceResponse(PurchaseOrderBase): """Schema for supplier invoice responses""" id: uuid.UUID tenant_id: uuid.UUID purchase_order_id: uuid.UUID supplier_id: uuid.UUID supplier_name: Optional[str] = None invoice_number: str status: str invoice_date: datetime # Use datetime with timezone due_date: datetime # Use datetime with timezone # Financial information subtotal: Decimal tax_amount: Decimal shipping_cost: Decimal discount_amount: Decimal total_amount: Decimal currency: str # Payment tracking paid_amount: Decimal remaining_amount: Decimal payment_date: Optional[datetime] = None # Use datetime with timezone payment_reference: Optional[str] = None # Additional information notes: Optional[str] = None # Audit fields created_at: datetime updated_at: datetime created_by: uuid.UUID updated_by: uuid.UUID