Add more services

This commit is contained in:
Urtzi Alfaro
2025-08-21 20:28:14 +02:00
parent d6fd53e461
commit c6dd6fd1de
85 changed files with 17842 additions and 1828 deletions

View File

@@ -0,0 +1,144 @@
# ================================================================
# services/orders/app/models/alerts.py
# ================================================================
"""
Alert system database models for Orders Service
"""
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Optional
from sqlalchemy import Column, String, Boolean, DateTime, Numeric, Text, Integer
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.sql import func
from app.core.database import Base
class OrderAlert(Base):
"""Alert system for orders and procurement issues"""
__tablename__ = "order_alerts"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
alert_code = Column(String(50), nullable=False, index=True)
# Alert categorization
alert_type = Column(String(50), nullable=False, index=True)
# Alert types: order_issue, procurement_shortage, payment_problem, delivery_delay,
# quality_concern, high_value_order, rush_order, customer_issue, supplier_problem
severity = Column(String(20), nullable=False, default="medium", index=True)
# Severity levels: critical, high, medium, low
category = Column(String(50), nullable=False, index=True)
# Categories: operational, financial, quality, customer, supplier, compliance
# Alert source and context
source_entity_type = Column(String(50), nullable=False) # order, customer, procurement_plan, etc.
source_entity_id = Column(UUID(as_uuid=True), nullable=False, index=True)
source_entity_reference = Column(String(100), nullable=True) # Human-readable reference
# Alert content
title = Column(String(200), nullable=False)
description = Column(Text, nullable=False)
detailed_message = Column(Text, nullable=True)
# Alert conditions and triggers
trigger_condition = Column(String(200), nullable=True)
threshold_value = Column(Numeric(15, 4), nullable=True)
actual_value = Column(Numeric(15, 4), nullable=True)
variance = Column(Numeric(15, 4), nullable=True)
# Context data
alert_data = Column(JSONB, nullable=True) # Additional context-specific data
business_impact = Column(Text, nullable=True)
customer_impact = Column(Text, nullable=True)
financial_impact = Column(Numeric(12, 2), nullable=True)
# Alert status and lifecycle
status = Column(String(50), nullable=False, default="active", index=True)
# Status values: active, acknowledged, in_progress, resolved, dismissed, expired
alert_state = Column(String(50), nullable=False, default="new") # new, escalated, recurring
# Resolution and follow-up
resolution_action = Column(String(200), nullable=True)
resolution_notes = Column(Text, nullable=True)
resolution_cost = Column(Numeric(10, 2), nullable=True)
# Timing and escalation
first_occurred_at = Column(DateTime(timezone=True), nullable=False, index=True)
last_occurred_at = Column(DateTime(timezone=True), nullable=False)
acknowledged_at = Column(DateTime(timezone=True), nullable=True)
resolved_at = Column(DateTime(timezone=True), nullable=True)
expires_at = Column(DateTime(timezone=True), nullable=True)
# Occurrence tracking
occurrence_count = Column(Integer, nullable=False, default=1)
is_recurring = Column(Boolean, nullable=False, default=False)
recurrence_pattern = Column(String(100), nullable=True)
# Responsibility and assignment
assigned_to = Column(UUID(as_uuid=True), nullable=True)
assigned_role = Column(String(50), nullable=True) # orders_manager, procurement_manager, etc.
escalated_to = Column(UUID(as_uuid=True), nullable=True)
escalation_level = Column(Integer, nullable=False, default=0)
# Notification tracking
notification_sent = Column(Boolean, nullable=False, default=False)
notification_methods = Column(JSONB, nullable=True) # [email, sms, whatsapp, dashboard]
notification_recipients = Column(JSONB, nullable=True) # List of recipients
last_notification_sent = Column(DateTime(timezone=True), nullable=True)
# Customer communication
customer_notified = Column(Boolean, nullable=False, default=False)
customer_notification_method = Column(String(50), nullable=True)
customer_message = Column(Text, nullable=True)
# Recommended actions
recommended_actions = Column(JSONB, nullable=True) # List of suggested actions
automated_actions_taken = Column(JSONB, nullable=True) # Actions performed automatically
manual_actions_required = Column(JSONB, nullable=True) # Actions requiring human intervention
# Priority and urgency
priority_score = Column(Integer, nullable=False, default=50) # 1-100 scale
urgency = Column(String(20), nullable=False, default="normal") # immediate, urgent, normal, low
business_priority = Column(String(20), nullable=False, default="normal")
# Related entities
related_orders = Column(JSONB, nullable=True) # Related order IDs
related_customers = Column(JSONB, nullable=True) # Related customer IDs
related_suppliers = Column(JSONB, nullable=True) # Related supplier IDs
related_alerts = Column(JSONB, nullable=True) # Related alert IDs
# Performance tracking
detection_time = Column(DateTime(timezone=True), nullable=True) # When issue was detected
response_time_minutes = Column(Integer, nullable=True) # Time to acknowledge
resolution_time_minutes = Column(Integer, nullable=True) # Time to resolve
# Quality and feedback
alert_accuracy = Column(Boolean, nullable=True) # Was this a valid alert?
false_positive = Column(Boolean, nullable=False, default=False)
feedback_notes = Column(Text, nullable=True)
# Compliance and audit
compliance_related = Column(Boolean, nullable=False, default=False)
audit_trail = Column(JSONB, nullable=True) # Changes and actions taken
regulatory_impact = Column(String(200), nullable=True)
# Integration and external systems
external_system_reference = Column(String(100), nullable=True)
external_ticket_number = Column(String(50), nullable=True)
erp_reference = Column(String(100), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
created_by = Column(UUID(as_uuid=True), nullable=True)
updated_by = Column(UUID(as_uuid=True), nullable=True)
# Additional metadata
alert_metadata = Column(JSONB, nullable=True)

View File

@@ -0,0 +1,123 @@
# ================================================================
# services/orders/app/models/customer.py
# ================================================================
"""
Customer-related database models for Orders Service
"""
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import Column, String, Boolean, DateTime, Numeric, Text, ForeignKey, Integer
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
class Customer(Base):
"""Customer model for managing customer information"""
__tablename__ = "customers"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
customer_code = Column(String(50), nullable=False, index=True) # Human-readable code
# Basic information
name = Column(String(200), nullable=False)
business_name = Column(String(200), nullable=True)
customer_type = Column(String(50), nullable=False, default="individual") # individual, business, central_bakery
# Contact information
email = Column(String(255), nullable=True)
phone = Column(String(50), nullable=True)
# Address information
address_line1 = Column(String(255), nullable=True)
address_line2 = Column(String(255), nullable=True)
city = Column(String(100), nullable=True)
state = Column(String(100), nullable=True)
postal_code = Column(String(20), nullable=True)
country = Column(String(100), nullable=False, default="US")
# Business information
tax_id = Column(String(50), nullable=True)
business_license = Column(String(100), nullable=True)
# Customer status and preferences
is_active = Column(Boolean, nullable=False, default=True)
preferred_delivery_method = Column(String(50), nullable=False, default="delivery") # delivery, pickup
payment_terms = Column(String(50), nullable=False, default="immediate") # immediate, net_30, net_60
credit_limit = Column(Numeric(10, 2), nullable=True)
discount_percentage = Column(Numeric(5, 2), nullable=False, default=Decimal("0.00"))
# Customer categorization
customer_segment = Column(String(50), nullable=False, default="regular") # vip, regular, wholesale
priority_level = Column(String(20), nullable=False, default="normal") # high, normal, low
# Preferences and special requirements
special_instructions = Column(Text, nullable=True)
delivery_preferences = Column(JSONB, nullable=True) # Time windows, special requirements
product_preferences = Column(JSONB, nullable=True) # Favorite products, allergies
# Customer metrics
total_orders = Column(Integer, nullable=False, default=0)
total_spent = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
average_order_value = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
last_order_date = Column(DateTime(timezone=True), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
created_by = Column(UUID(as_uuid=True), nullable=True)
updated_by = Column(UUID(as_uuid=True), nullable=True)
# Relationships
contacts = relationship("CustomerContact", back_populates="customer", cascade="all, delete-orphan")
orders = relationship("CustomerOrder", back_populates="customer")
class CustomerContact(Base):
"""Additional contact persons for business customers"""
__tablename__ = "customer_contacts"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
customer_id = Column(UUID(as_uuid=True), ForeignKey("customers.id", ondelete="CASCADE"), nullable=False)
# Contact information
name = Column(String(200), nullable=False)
title = Column(String(100), nullable=True)
department = Column(String(100), nullable=True)
# Contact details
email = Column(String(255), nullable=True)
phone = Column(String(50), nullable=True)
mobile = Column(String(50), nullable=True)
# Contact preferences
is_primary = Column(Boolean, nullable=False, default=False)
contact_for_orders = Column(Boolean, nullable=False, default=True)
contact_for_delivery = Column(Boolean, nullable=False, default=False)
contact_for_billing = Column(Boolean, nullable=False, default=False)
contact_for_support = Column(Boolean, nullable=False, default=False)
# Preferred contact methods
preferred_contact_method = Column(String(50), nullable=False, default="email") # email, phone, sms
contact_time_preferences = Column(JSONB, nullable=True) # Time windows for contact
# Notes and special instructions
notes = Column(Text, nullable=True)
# Status
is_active = Column(Boolean, nullable=False, default=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
# Relationships
customer = relationship("Customer", back_populates="contacts")

View File

@@ -0,0 +1,218 @@
# ================================================================
# services/orders/app/models/order.py
# ================================================================
"""
Order-related database models for Orders Service
"""
import uuid
from datetime import datetime
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import Column, String, Boolean, DateTime, Numeric, Text, ForeignKey, Integer
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
class CustomerOrder(Base):
"""Customer order model for tracking orders throughout their lifecycle"""
__tablename__ = "customer_orders"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
order_number = Column(String(50), nullable=False, unique=True, index=True)
# Customer information
customer_id = Column(UUID(as_uuid=True), ForeignKey("customers.id"), nullable=False, index=True)
# Order status and lifecycle
status = Column(String(50), nullable=False, default="pending", index=True)
# Status values: pending, confirmed, in_production, ready, out_for_delivery, delivered, cancelled, failed
order_type = Column(String(50), nullable=False, default="standard") # standard, rush, recurring, special
priority = Column(String(20), nullable=False, default="normal") # high, normal, low
# Order timing
order_date = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
requested_delivery_date = Column(DateTime(timezone=True), nullable=False)
confirmed_delivery_date = Column(DateTime(timezone=True), nullable=True)
actual_delivery_date = Column(DateTime(timezone=True), nullable=True)
# Delivery information
delivery_method = Column(String(50), nullable=False, default="delivery") # delivery, pickup
delivery_address = Column(JSONB, nullable=True) # Complete delivery address
delivery_instructions = Column(Text, nullable=True)
delivery_window_start = Column(DateTime(timezone=True), nullable=True)
delivery_window_end = Column(DateTime(timezone=True), nullable=True)
# Financial information
subtotal = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
discount_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
discount_percentage = Column(Numeric(5, 2), nullable=False, default=Decimal("0.00"))
tax_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
delivery_fee = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
total_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
# Payment information
payment_status = Column(String(50), nullable=False, default="pending") # pending, partial, paid, failed, refunded
payment_method = Column(String(50), nullable=True) # cash, card, bank_transfer, account
payment_terms = Column(String(50), nullable=False, default="immediate")
payment_due_date = Column(DateTime(timezone=True), nullable=True)
# Special requirements and customizations
special_instructions = Column(Text, nullable=True)
custom_requirements = Column(JSONB, nullable=True) # Special dietary requirements, decorations
allergen_warnings = Column(JSONB, nullable=True) # Allergen information
# Business model detection
business_model = Column(String(50), nullable=True) # individual_bakery, central_bakery (auto-detected)
estimated_business_model = Column(String(50), nullable=True) # Based on order patterns
# Order source and channel
order_source = Column(String(50), nullable=False, default="manual") # manual, online, phone, app, api
sales_channel = Column(String(50), nullable=False, default="direct") # direct, wholesale, retail
order_origin = Column(String(100), nullable=True) # Website, app, store location
# Fulfillment tracking
production_batch_id = Column(UUID(as_uuid=True), nullable=True) # Link to production batch
fulfillment_location = Column(String(100), nullable=True) # Which location fulfills this order
estimated_preparation_time = Column(Integer, nullable=True) # Minutes
actual_preparation_time = Column(Integer, nullable=True) # Minutes
# Customer communication
customer_notified_confirmed = Column(Boolean, nullable=False, default=False)
customer_notified_ready = Column(Boolean, nullable=False, default=False)
customer_notified_delivered = Column(Boolean, nullable=False, default=False)
communication_preferences = Column(JSONB, nullable=True)
# Quality and feedback
quality_score = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
customer_rating = Column(Integer, nullable=True) # 1-5 stars
customer_feedback = Column(Text, nullable=True)
# Cancellation and refunds
cancellation_reason = Column(String(200), nullable=True)
cancelled_at = Column(DateTime(timezone=True), nullable=True)
cancelled_by = Column(UUID(as_uuid=True), nullable=True)
refund_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
refund_processed_at = Column(DateTime(timezone=True), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
created_by = Column(UUID(as_uuid=True), nullable=True)
updated_by = Column(UUID(as_uuid=True), nullable=True)
# Additional metadata
order_metadata = Column(JSONB, nullable=True) # Flexible field for additional data
# Relationships
customer = relationship("Customer", back_populates="orders")
items = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan")
status_history = relationship("OrderStatusHistory", back_populates="order", cascade="all, delete-orphan")
class OrderItem(Base):
"""Individual items within a customer order"""
__tablename__ = "order_items"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
order_id = Column(UUID(as_uuid=True), ForeignKey("customer_orders.id", ondelete="CASCADE"), nullable=False)
# Product information
product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to products service
product_name = Column(String(200), nullable=False)
product_sku = Column(String(100), nullable=True)
product_category = Column(String(100), nullable=True)
# Quantity and units
quantity = Column(Numeric(10, 3), nullable=False)
unit_of_measure = Column(String(50), nullable=False, default="each")
weight = Column(Numeric(10, 3), nullable=True) # For weight-based products
# Pricing information
unit_price = Column(Numeric(10, 2), nullable=False)
line_discount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
line_total = Column(Numeric(10, 2), nullable=False)
# Product specifications and customizations
product_specifications = Column(JSONB, nullable=True) # Size, flavor, decorations
customization_details = Column(Text, nullable=True)
special_instructions = Column(Text, nullable=True)
# Production requirements
recipe_id = Column(UUID(as_uuid=True), nullable=True) # Reference to recipes service
production_requirements = Column(JSONB, nullable=True) # Ingredients, equipment needed
estimated_production_time = Column(Integer, nullable=True) # Minutes
# Fulfillment tracking
status = Column(String(50), nullable=False, default="pending") # pending, in_production, ready, delivered
production_started_at = Column(DateTime(timezone=True), nullable=True)
production_completed_at = Column(DateTime(timezone=True), nullable=True)
quality_checked = Column(Boolean, nullable=False, default=False)
quality_score = Column(Numeric(3, 1), nullable=True)
# Cost tracking
ingredient_cost = Column(Numeric(10, 2), nullable=True)
labor_cost = Column(Numeric(10, 2), nullable=True)
overhead_cost = Column(Numeric(10, 2), nullable=True)
total_cost = Column(Numeric(10, 2), nullable=True)
margin = Column(Numeric(10, 2), nullable=True)
# Inventory impact
reserved_inventory = Column(Boolean, nullable=False, default=False)
inventory_allocated_at = Column(DateTime(timezone=True), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
# Additional metadata
customer_metadata = Column(JSONB, nullable=True)
# Relationships
order = relationship("CustomerOrder", back_populates="items")
class OrderStatusHistory(Base):
"""Track status changes and important events in order lifecycle"""
__tablename__ = "order_status_history"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
order_id = Column(UUID(as_uuid=True), ForeignKey("customer_orders.id", ondelete="CASCADE"), nullable=False)
# Status change information
from_status = Column(String(50), nullable=True)
to_status = Column(String(50), nullable=False)
change_reason = Column(String(200), nullable=True)
# Event details
event_type = Column(String(50), nullable=False, default="status_change")
# Event types: status_change, payment_received, production_started, delivery_scheduled, etc.
event_description = Column(Text, nullable=True)
event_data = Column(JSONB, nullable=True) # Additional event-specific data
# Who made the change
changed_by = Column(UUID(as_uuid=True), nullable=True)
change_source = Column(String(50), nullable=False, default="manual") # manual, automatic, system, api
# Timing
changed_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
# Customer communication
customer_notified = Column(Boolean, nullable=False, default=False)
notification_method = Column(String(50), nullable=True) # email, sms, phone, app
notification_sent_at = Column(DateTime(timezone=True), nullable=True)
# Additional notes
notes = Column(Text, nullable=True)
# Relationships
order = relationship("CustomerOrder", back_populates="status_history")

View File

@@ -0,0 +1,217 @@
# ================================================================
# services/orders/app/models/procurement.py
# ================================================================
"""
Procurement planning database models for Orders Service
"""
import uuid
from datetime import datetime, date
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import Column, String, Boolean, DateTime, Date, Numeric, Text, Integer, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
class ProcurementPlan(Base):
"""Master procurement plan for coordinating supply needs across orders and production"""
__tablename__ = "procurement_plans"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
plan_number = Column(String(50), nullable=False, unique=True, index=True)
# Plan scope and timing
plan_date = Column(Date, nullable=False, index=True)
plan_period_start = Column(Date, nullable=False)
plan_period_end = Column(Date, nullable=False)
planning_horizon_days = Column(Integer, nullable=False, default=14)
# Plan status and lifecycle
status = Column(String(50), nullable=False, default="draft", index=True)
# Status values: draft, pending_approval, approved, in_execution, completed, cancelled
plan_type = Column(String(50), nullable=False, default="regular") # regular, emergency, seasonal
priority = Column(String(20), nullable=False, default="normal") # high, normal, low
# Business model context
business_model = Column(String(50), nullable=True) # individual_bakery, central_bakery
procurement_strategy = Column(String(50), nullable=False, default="just_in_time") # just_in_time, bulk, mixed
# Plan totals and summary
total_requirements = Column(Integer, nullable=False, default=0)
total_estimated_cost = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
total_approved_cost = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
cost_variance = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
# Demand analysis
total_demand_orders = Column(Integer, nullable=False, default=0)
total_demand_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
total_production_requirements = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
safety_stock_buffer = Column(Numeric(5, 2), nullable=False, default=Decimal("20.00")) # Percentage
# Supplier coordination
primary_suppliers_count = Column(Integer, nullable=False, default=0)
backup_suppliers_count = Column(Integer, nullable=False, default=0)
supplier_diversification_score = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
# Risk assessment
supply_risk_level = Column(String(20), nullable=False, default="low") # low, medium, high, critical
demand_forecast_confidence = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
seasonality_adjustment = Column(Numeric(5, 2), nullable=False, default=Decimal("0.00"))
# Execution tracking
approved_at = Column(DateTime(timezone=True), nullable=True)
approved_by = Column(UUID(as_uuid=True), nullable=True)
execution_started_at = Column(DateTime(timezone=True), nullable=True)
execution_completed_at = Column(DateTime(timezone=True), nullable=True)
# Performance metrics
fulfillment_rate = Column(Numeric(5, 2), nullable=True) # Percentage
on_time_delivery_rate = Column(Numeric(5, 2), nullable=True) # Percentage
cost_accuracy = Column(Numeric(5, 2), nullable=True) # Percentage
quality_score = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
# Integration data
source_orders = Column(JSONB, nullable=True) # Orders that drove this plan
production_schedules = Column(JSONB, nullable=True) # Associated production schedules
inventory_snapshots = Column(JSONB, nullable=True) # Inventory levels at planning time
# Communication and collaboration
stakeholder_notifications = Column(JSONB, nullable=True) # Who was notified and when
approval_workflow = Column(JSONB, nullable=True) # Approval chain and status
# Special considerations
special_requirements = Column(Text, nullable=True)
seasonal_adjustments = Column(JSONB, nullable=True)
emergency_provisions = Column(JSONB, nullable=True)
# External references
erp_reference = Column(String(100), nullable=True)
supplier_portal_reference = Column(String(100), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
created_by = Column(UUID(as_uuid=True), nullable=True)
updated_by = Column(UUID(as_uuid=True), nullable=True)
# Additional metadata
plan_metadata = Column(JSONB, nullable=True)
# Relationships
requirements = relationship("ProcurementRequirement", back_populates="plan", cascade="all, delete-orphan")
class ProcurementRequirement(Base):
"""Individual procurement requirements within a procurement plan"""
__tablename__ = "procurement_requirements"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plan_id = Column(UUID(as_uuid=True), ForeignKey("procurement_plans.id", ondelete="CASCADE"), nullable=False)
requirement_number = Column(String(50), nullable=False, index=True)
# Product/ingredient information
product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to products/ingredients
product_name = Column(String(200), nullable=False)
product_sku = Column(String(100), nullable=True)
product_category = Column(String(100), nullable=True)
product_type = Column(String(50), nullable=False, default="ingredient") # ingredient, packaging, supplies
# Requirement details
required_quantity = Column(Numeric(12, 3), nullable=False)
unit_of_measure = Column(String(50), nullable=False)
safety_stock_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
total_quantity_needed = Column(Numeric(12, 3), nullable=False)
# Current inventory situation
current_stock_level = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
reserved_stock = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
available_stock = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
net_requirement = Column(Numeric(12, 3), nullable=False)
# Demand breakdown
order_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
production_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
forecast_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
buffer_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
# Supplier information
preferred_supplier_id = Column(UUID(as_uuid=True), nullable=True)
backup_supplier_id = Column(UUID(as_uuid=True), nullable=True)
supplier_name = Column(String(200), nullable=True)
supplier_lead_time_days = Column(Integer, nullable=True)
minimum_order_quantity = Column(Numeric(12, 3), nullable=True)
# Pricing and cost
estimated_unit_cost = Column(Numeric(10, 4), nullable=True)
estimated_total_cost = Column(Numeric(12, 2), nullable=True)
last_purchase_cost = Column(Numeric(10, 4), nullable=True)
cost_variance = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
# Timing requirements
required_by_date = Column(Date, nullable=False)
lead_time_buffer_days = Column(Integer, nullable=False, default=1)
suggested_order_date = Column(Date, nullable=False)
latest_order_date = Column(Date, nullable=False)
# Quality and specifications
quality_specifications = Column(JSONB, nullable=True)
special_requirements = Column(Text, nullable=True)
storage_requirements = Column(String(200), nullable=True)
shelf_life_days = Column(Integer, nullable=True)
# Requirement status
status = Column(String(50), nullable=False, default="pending")
# Status values: pending, approved, ordered, partially_received, received, cancelled
priority = Column(String(20), nullable=False, default="normal") # critical, high, normal, low
risk_level = Column(String(20), nullable=False, default="low") # low, medium, high, critical
# Purchase order tracking
purchase_order_id = Column(UUID(as_uuid=True), nullable=True)
purchase_order_number = Column(String(50), nullable=True)
ordered_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
ordered_at = Column(DateTime(timezone=True), nullable=True)
# Delivery tracking
expected_delivery_date = Column(Date, nullable=True)
actual_delivery_date = Column(Date, nullable=True)
received_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
delivery_status = Column(String(50), nullable=False, default="pending")
# Performance tracking
fulfillment_rate = Column(Numeric(5, 2), nullable=True) # Percentage
on_time_delivery = Column(Boolean, nullable=True)
quality_rating = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
# Source traceability
source_orders = Column(JSONB, nullable=True) # Orders that contributed to this requirement
source_production_batches = Column(JSONB, nullable=True) # Production batches needing this
demand_analysis = Column(JSONB, nullable=True) # Detailed demand breakdown
# Approval and authorization
approved_quantity = Column(Numeric(12, 3), nullable=True)
approved_cost = Column(Numeric(12, 2), nullable=True)
approved_at = Column(DateTime(timezone=True), nullable=True)
approved_by = Column(UUID(as_uuid=True), nullable=True)
# Notes and communication
procurement_notes = Column(Text, nullable=True)
supplier_communication = Column(JSONB, nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
# Additional metadata
requirement_metadata = Column(JSONB, nullable=True)
# Relationships
plan = relationship("ProcurementPlan", back_populates="requirements")