Initial commit - production deployment

This commit is contained in:
2026-01-21 17:17:16 +01:00
commit c23d00dd92
2289 changed files with 638440 additions and 0 deletions

View File

@@ -0,0 +1 @@
# services/suppliers/app/schemas/__init__.py

View File

@@ -0,0 +1,385 @@
# ================================================================
# services/suppliers/app/schemas/performance.py
# ================================================================
"""
Performance Tracking and Alert Schemas for Suppliers Service
"""
from datetime import datetime
from typing import List, Optional, Dict, Any
from uuid import UUID
from pydantic import BaseModel, Field, validator
from decimal import Decimal
from app.models.performance import (
AlertSeverity, AlertType, AlertStatus, PerformanceMetricType,
PerformancePeriod
)
# ===== Base Schemas =====
class PerformanceMetricBase(BaseModel):
"""Base schema for performance metrics"""
metric_type: PerformanceMetricType
period: PerformancePeriod
period_start: datetime
period_end: datetime
metric_value: float = Field(ge=0, le=100)
target_value: Optional[float] = None
total_orders: int = Field(ge=0, default=0)
total_deliveries: int = Field(ge=0, default=0)
on_time_deliveries: int = Field(ge=0, default=0)
late_deliveries: int = Field(ge=0, default=0)
quality_issues: int = Field(ge=0, default=0)
total_amount: Decimal = Field(ge=0, default=0)
notes: Optional[str] = None
class PerformanceMetricCreate(PerformanceMetricBase):
"""Schema for creating performance metrics"""
supplier_id: UUID
metrics_data: Optional[Dict[str, Any]] = None
external_factors: Optional[Dict[str, Any]] = None
class PerformanceMetricUpdate(BaseModel):
"""Schema for updating performance metrics"""
metric_value: Optional[float] = Field(None, ge=0, le=100)
target_value: Optional[float] = None
notes: Optional[str] = None
metrics_data: Optional[Dict[str, Any]] = None
external_factors: Optional[Dict[str, Any]] = None
class PerformanceMetric(PerformanceMetricBase):
"""Complete performance metric schema"""
id: UUID
tenant_id: UUID
supplier_id: UUID
previous_value: Optional[float] = None
trend_direction: Optional[str] = None
trend_percentage: Optional[float] = None
metrics_data: Optional[Dict[str, Any]] = None
external_factors: Optional[Dict[str, Any]] = None
calculated_at: datetime
class Config:
orm_mode = True
# ===== Alert Schemas =====
class AlertBase(BaseModel):
"""Base schema for alerts"""
alert_type: AlertType
severity: AlertSeverity
title: str = Field(max_length=255)
message: str
description: Optional[str] = None
trigger_value: Optional[float] = None
threshold_value: Optional[float] = None
metric_type: Optional[PerformanceMetricType] = None
recommended_actions: Optional[List[Dict[str, Any]]] = None
auto_resolve: bool = False
class AlertCreate(AlertBase):
"""Schema for creating alerts"""
supplier_id: UUID
purchase_order_id: Optional[UUID] = None
delivery_id: Optional[UUID] = None
performance_metric_id: Optional[UUID] = None
priority_score: int = Field(ge=1, le=100, default=50)
business_impact: Optional[str] = None
tags: Optional[List[str]] = None
class AlertUpdate(BaseModel):
"""Schema for updating alerts"""
status: Optional[AlertStatus] = None
actions_taken: Optional[List[Dict[str, Any]]] = None
resolution_notes: Optional[str] = None
escalated: Optional[bool] = None
class Alert(AlertBase):
"""Complete alert schema"""
id: UUID
tenant_id: UUID
supplier_id: UUID
status: AlertStatus
purchase_order_id: Optional[UUID] = None
delivery_id: Optional[UUID] = None
performance_metric_id: Optional[UUID] = None
triggered_at: datetime
acknowledged_at: Optional[datetime] = None
acknowledged_by: Optional[UUID] = None
resolved_at: Optional[datetime] = None
resolved_by: Optional[UUID] = None
actions_taken: Optional[List[Dict[str, Any]]] = None
resolution_notes: Optional[str] = None
escalated: bool = False
escalated_at: Optional[datetime] = None
notification_sent: bool = False
priority_score: int
business_impact: Optional[str] = None
tags: Optional[List[str]] = None
created_at: datetime
class Config:
orm_mode = True
# ===== Scorecard Schemas =====
class ScorecardBase(BaseModel):
"""Base schema for supplier scorecards"""
scorecard_name: str = Field(max_length=255)
period: PerformancePeriod
period_start: datetime
period_end: datetime
overall_score: float = Field(ge=0, le=100)
quality_score: float = Field(ge=0, le=100)
delivery_score: float = Field(ge=0, le=100)
cost_score: float = Field(ge=0, le=100)
service_score: float = Field(ge=0, le=100)
on_time_delivery_rate: float = Field(ge=0, le=100)
quality_rejection_rate: float = Field(ge=0, le=100)
order_accuracy_rate: float = Field(ge=0, le=100)
response_time_hours: float = Field(ge=0)
cost_variance_percentage: float
total_orders_processed: int = Field(ge=0, default=0)
total_amount_processed: Decimal = Field(ge=0, default=0)
average_order_value: Decimal = Field(ge=0, default=0)
cost_savings_achieved: Decimal = Field(default=0)
class ScorecardCreate(ScorecardBase):
"""Schema for creating scorecards"""
supplier_id: UUID
strengths: Optional[List[str]] = None
improvement_areas: Optional[List[str]] = None
recommended_actions: Optional[List[Dict[str, Any]]] = None
notes: Optional[str] = None
class ScorecardUpdate(BaseModel):
"""Schema for updating scorecards"""
overall_score: Optional[float] = Field(None, ge=0, le=100)
quality_score: Optional[float] = Field(None, ge=0, le=100)
delivery_score: Optional[float] = Field(None, ge=0, le=100)
cost_score: Optional[float] = Field(None, ge=0, le=100)
service_score: Optional[float] = Field(None, ge=0, le=100)
strengths: Optional[List[str]] = None
improvement_areas: Optional[List[str]] = None
recommended_actions: Optional[List[Dict[str, Any]]] = None
notes: Optional[str] = None
is_final: Optional[bool] = None
class Scorecard(ScorecardBase):
"""Complete scorecard schema"""
id: UUID
tenant_id: UUID
supplier_id: UUID
overall_rank: Optional[int] = None
category_rank: Optional[int] = None
total_suppliers_evaluated: Optional[int] = None
score_trend: Optional[str] = None
score_change_percentage: Optional[float] = None
strengths: Optional[List[str]] = None
improvement_areas: Optional[List[str]] = None
recommended_actions: Optional[List[Dict[str, Any]]] = None
is_final: bool = False
approved_by: Optional[UUID] = None
approved_at: Optional[datetime] = None
notes: Optional[str] = None
attachments: Optional[List[Dict[str, Any]]] = None
generated_at: datetime
generated_by: UUID
class Config:
orm_mode = True
# ===== Dashboard Schemas =====
class PerformanceDashboardSummary(BaseModel):
"""Performance dashboard summary schema"""
total_suppliers: int
active_suppliers: int
suppliers_above_threshold: int
suppliers_below_threshold: int
average_overall_score: float
average_delivery_rate: float
average_quality_rate: float
total_active_alerts: int
critical_alerts: int
high_priority_alerts: int
recent_scorecards_generated: int
cost_savings_this_month: Decimal
# Performance trends
performance_trend: str # improving, declining, stable
delivery_trend: str
quality_trend: str
# Business model insights
detected_business_model: str # individual_bakery, central_bakery, hybrid
model_confidence: float
business_model_metrics: Dict[str, Any]
class SupplierPerformanceInsights(BaseModel):
"""Supplier performance insights schema"""
supplier_id: UUID
supplier_name: str
current_overall_score: float
previous_score: Optional[float] = None
score_change_percentage: Optional[float] = None
performance_rank: Optional[int] = None
# Key performance indicators
delivery_performance: float
quality_performance: float
cost_performance: float
service_performance: float
# Recent metrics
orders_last_30_days: int
average_delivery_time: float
quality_issues_count: int
cost_variance: float
# Alert summary
active_alerts: int
resolved_alerts_last_30_days: int
alert_trend: str
# Performance categorization
performance_category: str # excellent, good, acceptable, needs_improvement, poor
risk_level: str # low, medium, high, critical
# Recommendations
top_strengths: List[str]
improvement_priorities: List[str]
recommended_actions: List[Dict[str, Any]]
class PerformanceAnalytics(BaseModel):
"""Advanced performance analytics schema"""
period_start: datetime
period_end: datetime
total_suppliers_analyzed: int
# Performance distribution
performance_distribution: Dict[str, int] # excellent, good, etc.
score_ranges: Dict[str, List[float]] # min, max, avg per range
# Trend analysis
overall_trend: Dict[str, float] # month-over-month changes
delivery_trends: Dict[str, float]
quality_trends: Dict[str, float]
cost_trends: Dict[str, float]
# Comparative analysis
top_performers: List[SupplierPerformanceInsights]
underperformers: List[SupplierPerformanceInsights]
most_improved: List[SupplierPerformanceInsights]
biggest_declines: List[SupplierPerformanceInsights]
# Risk analysis
high_risk_suppliers: List[Dict[str, Any]]
contract_renewals_due: List[Dict[str, Any]]
certification_expiries: List[Dict[str, Any]]
# Financial impact
total_procurement_value: Decimal
cost_savings_achieved: Decimal
cost_avoidance: Decimal
financial_risk_exposure: Decimal
class AlertSummary(BaseModel):
"""Alert summary schema"""
alert_type: AlertType
severity: AlertSeverity
count: int
avg_resolution_time_hours: Optional[float] = None
oldest_alert_age_hours: Optional[float] = None
trend_percentage: Optional[float] = None
class DashboardFilter(BaseModel):
"""Dashboard filter schema"""
supplier_ids: Optional[List[UUID]] = None
supplier_categories: Optional[List[str]] = None
performance_categories: Optional[List[str]] = None
date_from: Optional[datetime] = None
date_to: Optional[datetime] = None
include_inactive: bool = False
class AlertFilter(BaseModel):
"""Alert filter schema"""
alert_types: Optional[List[AlertType]] = None
severities: Optional[List[AlertSeverity]] = None
statuses: Optional[List[AlertStatus]] = None
supplier_ids: Optional[List[UUID]] = None
date_from: Optional[datetime] = None
date_to: Optional[datetime] = None
metric_types: Optional[List[PerformanceMetricType]] = None
# ===== Business Model Detection =====
class BusinessModelInsights(BaseModel):
"""Business model detection and insights schema"""
detected_model: str # individual_bakery, central_bakery, hybrid
confidence_score: float
model_characteristics: Dict[str, Any]
# Model-specific metrics
supplier_diversity_score: float
procurement_volume_patterns: Dict[str, Any]
delivery_frequency_patterns: Dict[str, Any]
order_size_patterns: Dict[str, Any]
# Recommendations
optimization_opportunities: List[Dict[str, Any]]
recommended_supplier_mix: Dict[str, Any]
cost_optimization_potential: Decimal
risk_mitigation_suggestions: List[str]
# Benchmarking
industry_comparison: Dict[str, float]
peer_comparison: Optional[Dict[str, float]] = None
# ===== Export and Reporting =====
class PerformanceReportRequest(BaseModel):
"""Performance report generation request"""
report_type: str # scorecard, analytics, alerts, comprehensive
format: str = Field(pattern="^(pdf|excel|csv|json)$")
period: PerformancePeriod
date_from: datetime
date_to: datetime
supplier_ids: Optional[List[UUID]] = None
include_charts: bool = True
include_recommendations: bool = True
include_benchmarks: bool = True
custom_metrics: Optional[List[str]] = None
class ExportDataResponse(BaseModel):
"""Export data response schema"""
export_id: UUID
format: str
file_url: Optional[str] = None
file_size_bytes: Optional[int] = None
generated_at: datetime
expires_at: datetime
status: str # generating, ready, expired, failed
error_message: Optional[str] = None

View File

@@ -0,0 +1,732 @@
# 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
# The primary implementation has moved to Procurement Service (services/procurement/)
# These schemas support legacy endpoints in suppliers service (app/api/purchase_orders.py)
#
# Migration Status:
# - ✅ Procurement Service fully operational with enhanced features
# - ⚠️ Supplier service endpoints still active for backward compatibility
# - 📋 Deprecation Timeline: Q2 2026 (after 6-month dual-operation period)
#
# Action Required:
# 1. All new integrations should use Procurement Service endpoints
# 2. Update client applications to use ProcurementServiceClient
# 3. Monitor usage of supplier service PO endpoints via logs
# 4. Plan migration of remaining clients by Q1 2026
# ============================================================================
# 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