396 lines
13 KiB
Python
396 lines
13 KiB
Python
# ================================================================
|
|
# 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
|