Files
bakery-ia/services/procurement/app/schemas/purchase_order_schemas.py

396 lines
13 KiB
Python
Raw Normal View History

2025-10-30 21:08:07 +01:00
# ================================================================
# 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
2025-10-31 18:57:58 +01:00
product_name: Optional[str] = None
2025-10-30 21:08:07 +01:00
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
2025-10-31 18:57:58 +01:00
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
2025-10-30 21:08:07 +01:00
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
2025-11-20 22:10:16 +01:00
# AI/ML reasoning for procurement decisions (JTBD dashboard support)
reasoning_data: Optional[Dict[str, Any]] = None
2025-10-30 21:08:07 +01:00
# 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] = []
2025-10-31 18:57:58 +01:00
class PurchaseOrderWithSupplierResponse(PurchaseOrderResponse):
"""Schema for purchase order responses with supplier information"""
supplier: Optional[SupplierSummary] = None
2025-10-30 21:08:07 +01:00
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