# services/suppliers/app/schemas/suppliers.py """ Pydantic schemas for supplier-related API requests and responses """ from pydantic import BaseModel, Field, EmailStr from typing import List, Optional, Dict, Any, Union from uuid import UUID from datetime import datetime from decimal import Decimal from app.models.suppliers import ( SupplierType, SupplierStatus, PaymentTerms, QualityRating ) # NOTE: PO, Delivery, and Invoice schemas remain for backward compatibility # but the actual tables and functionality have moved to Procurement Service # TODO: These schemas should be removed once all clients migrate to Procurement Service # ============================================================================ # SUPPLIER SCHEMAS # ============================================================================ class SupplierCreate(BaseModel): """Schema for creating suppliers""" name: str = Field(..., min_length=1, max_length=255) supplier_code: Optional[str] = Field(None, max_length=50) tax_id: Optional[str] = Field(None, max_length=50) registration_number: Optional[str] = Field(None, max_length=100) supplier_type: SupplierType contact_person: Optional[str] = Field(None, max_length=200) email: Optional[EmailStr] = None phone: Optional[str] = Field(None, max_length=30) mobile: Optional[str] = Field(None, max_length=30) website: Optional[str] = Field(None, max_length=255) # Address address_line1: Optional[str] = Field(None, max_length=255) address_line2: Optional[str] = Field(None, max_length=255) city: Optional[str] = Field(None, max_length=100) state_province: Optional[str] = Field(None, max_length=100) postal_code: Optional[str] = Field(None, max_length=20) country: Optional[str] = Field(None, max_length=100) # Business terms payment_terms: PaymentTerms = PaymentTerms.net_30 credit_limit: Optional[Decimal] = Field(None, ge=0) currency: str = Field(default="EUR", max_length=3) standard_lead_time: int = Field(default=3, ge=0, le=365) minimum_order_amount: Optional[Decimal] = Field(None, ge=0) delivery_area: Optional[str] = Field(None, max_length=255) # Additional information notes: Optional[str] = None certifications: Optional[Union[Dict[str, Any], List[str]]] = None business_hours: Optional[Dict[str, Any]] = None specializations: Optional[Dict[str, Any]] = None class SupplierUpdate(BaseModel): """Schema for updating suppliers""" name: Optional[str] = Field(None, min_length=1, max_length=255) supplier_code: Optional[str] = Field(None, max_length=50) tax_id: Optional[str] = Field(None, max_length=50) registration_number: Optional[str] = Field(None, max_length=100) supplier_type: Optional[SupplierType] = None status: Optional[SupplierStatus] = None contact_person: Optional[str] = Field(None, max_length=200) email: Optional[EmailStr] = None phone: Optional[str] = Field(None, max_length=30) mobile: Optional[str] = Field(None, max_length=30) website: Optional[str] = Field(None, max_length=255) # Address address_line1: Optional[str] = Field(None, max_length=255) address_line2: Optional[str] = Field(None, max_length=255) city: Optional[str] = Field(None, max_length=100) state_province: Optional[str] = Field(None, max_length=100) postal_code: Optional[str] = Field(None, max_length=20) country: Optional[str] = Field(None, max_length=100) # Business terms payment_terms: Optional[PaymentTerms] = None credit_limit: Optional[Decimal] = Field(None, ge=0) currency: Optional[str] = Field(None, max_length=3) standard_lead_time: Optional[int] = Field(None, ge=0, le=365) minimum_order_amount: Optional[Decimal] = Field(None, ge=0) delivery_area: Optional[str] = Field(None, max_length=255) # Additional information notes: Optional[str] = None certifications: Optional[Union[Dict[str, Any], List[str]]] = None business_hours: Optional[Dict[str, Any]] = None specializations: Optional[Dict[str, Any]] = None class SupplierApproval(BaseModel): """Schema for supplier approval/rejection""" action: str = Field(..., pattern="^(approve|reject)$") notes: Optional[str] = None class SupplierResponse(BaseModel): """Schema for supplier responses""" id: UUID tenant_id: UUID name: str supplier_code: Optional[str] = None tax_id: Optional[str] = None registration_number: Optional[str] = None supplier_type: SupplierType status: SupplierStatus contact_person: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None mobile: Optional[str] = None website: Optional[str] = None # Address address_line1: Optional[str] = None address_line2: Optional[str] = None city: Optional[str] = None state_province: Optional[str] = None postal_code: Optional[str] = None country: Optional[str] = None # Business terms payment_terms: PaymentTerms credit_limit: Optional[Decimal] = None currency: str standard_lead_time: int minimum_order_amount: Optional[Decimal] = None delivery_area: Optional[str] = None # Performance metrics quality_rating: Optional[float] = None delivery_rating: Optional[float] = None total_orders: int total_amount: Decimal # Approval info approved_by: Optional[UUID] = None approved_at: Optional[datetime] = None rejection_reason: Optional[str] = None # Additional information notes: Optional[str] = None certifications: Optional[Union[Dict[str, Any], List[str]]] = None business_hours: Optional[Dict[str, Any]] = None specializations: Optional[Dict[str, Any]] = None # Audit fields created_at: datetime updated_at: datetime created_by: UUID updated_by: UUID class Config: from_attributes = True class SupplierSummary(BaseModel): """Schema for supplier summary (list view)""" id: UUID name: str supplier_code: Optional[str] = None supplier_type: SupplierType status: SupplierStatus contact_person: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None city: Optional[str] = None country: Optional[str] = None # Business terms - Added for list view payment_terms: PaymentTerms standard_lead_time: int minimum_order_amount: Optional[Decimal] = None # Performance metrics quality_rating: Optional[float] = None delivery_rating: Optional[float] = None total_orders: int total_amount: Decimal created_at: datetime class Config: from_attributes = True class SupplierDeletionSummary(BaseModel): """Schema for supplier deletion summary""" supplier_name: str deleted_price_lists: int = 0 deleted_quality_reviews: int = 0 deleted_performance_metrics: int = 0 deleted_alerts: int = 0 deleted_scorecards: int = 0 deletion_timestamp: datetime class Config: from_attributes = True # ============================================================================ # PURCHASE ORDER SCHEMAS # ============================================================================ class PurchaseOrderItemCreate(BaseModel): """Schema for creating purchase order items""" inventory_product_id: UUID product_code: Optional[str] = Field(None, max_length=100) ordered_quantity: int = Field(..., gt=0) unit_of_measure: str = Field(..., max_length=20) unit_price: Decimal = Field(..., gt=0) quality_requirements: Optional[str] = None item_notes: Optional[str] = None class PurchaseOrderItemUpdate(BaseModel): """Schema for updating purchase order items""" ordered_quantity: Optional[int] = Field(None, gt=0) unit_price: Optional[Decimal] = Field(None, gt=0) quality_requirements: Optional[str] = None item_notes: Optional[str] = None class PurchaseOrderItemResponse(BaseModel): """Schema for purchase order item responses""" id: UUID tenant_id: UUID purchase_order_id: UUID price_list_item_id: Optional[UUID] = None inventory_product_id: UUID product_code: Optional[str] = None ordered_quantity: int unit_of_measure: str unit_price: Decimal line_total: Decimal received_quantity: int remaining_quantity: int quality_requirements: Optional[str] = None item_notes: Optional[str] = None created_at: datetime updated_at: datetime class Config: from_attributes = True class PurchaseOrderCreate(BaseModel): """Schema for creating purchase orders""" supplier_id: UUID reference_number: Optional[str] = Field(None, max_length=100) priority: str = Field(default="normal", max_length=20) required_delivery_date: Optional[datetime] = None # Delivery information delivery_address: Optional[str] = None delivery_instructions: Optional[str] = None delivery_contact: Optional[str] = Field(None, max_length=200) delivery_phone: Optional[str] = Field(None, max_length=30) # Financial information tax_amount: Decimal = Field(default=0, ge=0) shipping_cost: Decimal = Field(default=0, ge=0) discount_amount: Decimal = Field(default=0, ge=0) # Additional information notes: Optional[str] = None internal_notes: Optional[str] = None terms_and_conditions: Optional[str] = None # Items items: List[PurchaseOrderItemCreate] = Field(..., min_items=1) class PurchaseOrderUpdate(BaseModel): """Schema for updating purchase orders""" reference_number: Optional[str] = Field(None, max_length=100) priority: Optional[str] = Field(None, max_length=20) required_delivery_date: Optional[datetime] = None estimated_delivery_date: Optional[datetime] = None # Delivery information delivery_address: Optional[str] = None delivery_instructions: Optional[str] = None delivery_contact: Optional[str] = Field(None, max_length=200) delivery_phone: Optional[str] = Field(None, max_length=30) # 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 internal_notes: Optional[str] = None terms_and_conditions: Optional[str] = None # Supplier communication supplier_reference: Optional[str] = Field(None, max_length=100) class PurchaseOrderStatusUpdate(BaseModel): """Schema for updating purchase order status""" status: str # PurchaseOrderStatus - moved to Procurement Service notes: Optional[str] = None class PurchaseOrderApproval(BaseModel): """Schema for purchase order approval/rejection""" action: str = Field(..., pattern="^(approve|reject)$") notes: Optional[str] = None class PurchaseOrderResponse(BaseModel): """Schema for purchase order responses""" id: UUID tenant_id: UUID supplier_id: UUID po_number: str reference_number: Optional[str] = None status: str # PurchaseOrderStatus priority: str order_date: datetime required_delivery_date: Optional[datetime] = None estimated_delivery_date: Optional[datetime] = None # Financial information subtotal: Decimal tax_amount: Decimal shipping_cost: Decimal discount_amount: Decimal total_amount: Decimal currency: str # Delivery information delivery_address: Optional[str] = None delivery_instructions: Optional[str] = None delivery_contact: Optional[str] = None delivery_phone: Optional[str] = None # Approval workflow requires_approval: bool approved_by: Optional[UUID] = None approved_at: Optional[datetime] = None rejection_reason: Optional[str] = None # Communication tracking sent_to_supplier_at: Optional[datetime] = None supplier_confirmation_date: Optional[datetime] = None supplier_reference: Optional[str] = None # Additional information notes: Optional[str] = None internal_notes: Optional[str] = None terms_and_conditions: Optional[str] = None # Audit fields created_at: datetime updated_at: datetime created_by: UUID updated_by: UUID # Related data (populated separately) supplier: Optional[SupplierSummary] = None items: Optional[List[PurchaseOrderItemResponse]] = None class Config: from_attributes = True class PurchaseOrderSummary(BaseModel): """Schema for purchase order summary (list view)""" id: UUID po_number: str supplier_id: UUID supplier_name: Optional[str] = None status: str # PurchaseOrderStatus priority: str order_date: datetime required_delivery_date: Optional[datetime] = None total_amount: Decimal currency: str created_at: datetime class Config: from_attributes = True # ============================================================================ # DELIVERY SCHEMAS # ============================================================================ class DeliveryItemCreate(BaseModel): """Schema for creating delivery items""" purchase_order_item_id: UUID inventory_product_id: UUID ordered_quantity: int = Field(..., gt=0) delivered_quantity: int = Field(..., ge=0) accepted_quantity: int = Field(..., ge=0) rejected_quantity: int = Field(default=0, ge=0) # Quality information batch_lot_number: Optional[str] = Field(None, max_length=100) expiry_date: Optional[datetime] = None 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(BaseModel): """Schema for delivery item responses""" id: UUID tenant_id: UUID delivery_id: UUID purchase_order_item_id: UUID inventory_product_id: UUID ordered_quantity: int delivered_quantity: int accepted_quantity: int rejected_quantity: int batch_lot_number: Optional[str] = None expiry_date: Optional[datetime] = None 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 Config: from_attributes = True class DeliveryCreate(BaseModel): """Schema for creating deliveries""" purchase_order_id: UUID supplier_id: UUID supplier_delivery_note: Optional[str] = Field(None, max_length=100) scheduled_date: Optional[datetime] = None estimated_arrival: Optional[datetime] = None # Delivery details delivery_address: Optional[str] = None delivery_contact: Optional[str] = Field(None, max_length=200) delivery_phone: Optional[str] = Field(None, max_length=30) 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_items=1) class DeliveryUpdate(BaseModel): """Schema for updating deliveries""" supplier_delivery_note: Optional[str] = Field(None, max_length=100) scheduled_date: Optional[datetime] = None estimated_arrival: Optional[datetime] = None actual_arrival: Optional[datetime] = None # Delivery details delivery_address: Optional[str] = None delivery_contact: Optional[str] = Field(None, max_length=200) delivery_phone: Optional[str] = Field(None, max_length=30) 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 DeliveryStatusUpdate(BaseModel): """Schema for updating delivery status""" status: str # DeliveryStatus notes: Optional[str] = None update_timestamps: bool = Field(default=True) class DeliveryReceiptConfirmation(BaseModel): """Schema for confirming delivery receipt""" inspection_passed: bool = True inspection_notes: Optional[str] = None quality_issues: Optional[Dict[str, Any]] = None notes: Optional[str] = None class DeliveryResponse(BaseModel): """Schema for delivery responses""" id: UUID tenant_id: UUID purchase_order_id: UUID supplier_id: UUID delivery_number: str supplier_delivery_note: Optional[str] = None status: str # DeliveryStatus # Timing scheduled_date: Optional[datetime] = None estimated_arrival: Optional[datetime] = None actual_arrival: Optional[datetime] = None completed_at: Optional[datetime] = None # Delivery details delivery_address: Optional[str] = None delivery_contact: Optional[str] = None delivery_phone: Optional[str] = None 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] = None received_at: Optional[datetime] = None # Additional information notes: Optional[str] = None photos: Optional[Dict[str, Any]] = None # Audit fields created_at: datetime updated_at: datetime created_by: UUID # Related data supplier: Optional[SupplierSummary] = None purchase_order: Optional[PurchaseOrderSummary] = None items: Optional[List[DeliveryItemResponse]] = None class Config: from_attributes = True class DeliverySummary(BaseModel): """Schema for delivery summary (list view)""" id: UUID delivery_number: str supplier_id: UUID supplier_name: Optional[str] = None purchase_order_id: UUID po_number: Optional[str] = None status: str # DeliveryStatus scheduled_date: Optional[datetime] = None actual_arrival: Optional[datetime] = None inspection_passed: Optional[bool] = None created_at: datetime class Config: from_attributes = True # ============================================================================ # SEARCH AND FILTER SCHEMAS # ============================================================================ class SupplierSearchParams(BaseModel): """Search parameters for suppliers""" search_term: Optional[str] = Field(None, max_length=100) supplier_type: Optional[SupplierType] = None status: Optional[SupplierStatus] = None limit: int = Field(default=50, ge=1, le=1000) offset: int = Field(default=0, ge=0) class PurchaseOrderSearchParams(BaseModel): """Search parameters for purchase orders""" supplier_id: Optional[UUID] = None status: Optional[str] = None # PurchaseOrderStatus priority: Optional[str] = None date_from: Optional[datetime] = None date_to: Optional[datetime] = None search_term: Optional[str] = Field(None, max_length=100) limit: int = Field(default=50, ge=1, le=1000) offset: int = Field(default=0, ge=0) class DeliverySearchParams(BaseModel): """Search parameters for deliveries""" supplier_id: Optional[UUID] = None status: Optional[str] = None # DeliveryStatus date_from: Optional[datetime] = None date_to: Optional[datetime] = None search_term: Optional[str] = Field(None, max_length=100) limit: int = Field(default=50, ge=1, le=1000) offset: int = Field(default=0, ge=0) # ============================================================================ # SUPPLIER PRICE LIST SCHEMAS # ============================================================================ class SupplierPriceListCreate(BaseModel): """Schema for creating supplier price list items""" inventory_product_id: UUID product_code: Optional[str] = Field(None, max_length=100) unit_price: Decimal = Field(..., gt=0) unit_of_measure: str = Field(..., max_length=20) minimum_order_quantity: Optional[int] = Field(None, ge=1) price_per_unit: Decimal = Field(..., gt=0) tier_pricing: Optional[Dict[str, Any]] = None # [{quantity: 100, price: 2.50}, ...] effective_date: Optional[datetime] = Field(default_factory=lambda: datetime.now()) expiry_date: Optional[datetime] = None is_active: bool = True brand: Optional[str] = Field(None, max_length=100) packaging_size: Optional[str] = Field(None, max_length=50) origin_country: Optional[str] = Field(None, max_length=100) shelf_life_days: Optional[int] = None storage_requirements: Optional[str] = None quality_specs: Optional[Dict[str, Any]] = None allergens: Optional[Dict[str, Any]] = None class SupplierPriceListUpdate(BaseModel): """Schema for updating supplier price list items""" unit_price: Optional[Decimal] = Field(None, gt=0) unit_of_measure: Optional[str] = Field(None, max_length=20) minimum_order_quantity: Optional[int] = Field(None, ge=1) tier_pricing: Optional[Dict[str, Any]] = None effective_date: Optional[datetime] = None expiry_date: Optional[datetime] = None is_active: Optional[bool] = None brand: Optional[str] = Field(None, max_length=100) packaging_size: Optional[str] = Field(None, max_length=50) origin_country: Optional[str] = Field(None, max_length=100) shelf_life_days: Optional[int] = None storage_requirements: Optional[str] = None quality_specs: Optional[Dict[str, Any]] = None allergens: Optional[Dict[str, Any]] = None class SupplierPriceListResponse(BaseModel): """Schema for supplier price list responses""" id: UUID tenant_id: UUID supplier_id: UUID inventory_product_id: UUID product_code: Optional[str] = None unit_price: Decimal unit_of_measure: str minimum_order_quantity: Optional[int] = None price_per_unit: Decimal tier_pricing: Optional[Dict[str, Any]] = None effective_date: datetime expiry_date: Optional[datetime] = None is_active: bool brand: Optional[str] = None packaging_size: Optional[str] = None origin_country: Optional[str] = None shelf_life_days: Optional[int] = None storage_requirements: Optional[str] = None quality_specs: Optional[Dict[str, Any]] = None allergens: Optional[Dict[str, Any]] = None created_at: datetime updated_at: datetime created_by: UUID updated_by: UUID class Config: from_attributes = True # ============================================================================ # STATISTICS AND REPORTING SCHEMAS # ============================================================================ class SupplierStatistics(BaseModel): """Schema for supplier statistics""" total_suppliers: int active_suppliers: int pending_suppliers: int avg_quality_rating: float avg_delivery_rating: float total_spend: float class PurchaseOrderStatistics(BaseModel): """Schema for purchase order statistics""" total_orders: int status_counts: Dict[str, int] this_month_orders: int this_month_spend: float avg_order_value: float overdue_count: int pending_approval: int class DeliveryPerformanceStats(BaseModel): """Schema for delivery performance statistics""" total_deliveries: int on_time_deliveries: int late_deliveries: int failed_deliveries: int on_time_percentage: float avg_delay_hours: float quality_pass_rate: float class DeliverySummaryStats(BaseModel): """Schema for delivery summary statistics""" todays_deliveries: int this_week_deliveries: int overdue_deliveries: int in_transit_deliveries: int