Files
bakery-ia/services/suppliers/app/schemas/suppliers.py
2025-10-30 21:08:07 +01:00

722 lines
24 KiB
Python

# 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