562 lines
24 KiB
Python
562 lines
24 KiB
Python
# services/suppliers/app/models/suppliers.py
|
|
"""
|
|
Supplier & Procurement management models for Suppliers Service
|
|
Comprehensive supplier management, purchase orders, deliveries, and vendor relationships
|
|
"""
|
|
|
|
from sqlalchemy import Column, String, DateTime, Float, Integer, Text, Index, Boolean, Numeric, ForeignKey, Enum as SQLEnum
|
|
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
|
from sqlalchemy.orm import relationship
|
|
import uuid
|
|
import enum
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, Any, Optional, List
|
|
from decimal import Decimal
|
|
|
|
from shared.database.base import Base
|
|
|
|
|
|
class SupplierType(enum.Enum):
|
|
"""Types of suppliers"""
|
|
INGREDIENTS = "ingredients" # Raw materials supplier
|
|
PACKAGING = "packaging" # Packaging materials
|
|
EQUIPMENT = "equipment" # Bakery equipment
|
|
SERVICES = "services" # Service providers
|
|
UTILITIES = "utilities" # Utilities (gas, electricity)
|
|
MULTI = "multi" # Multi-category supplier
|
|
|
|
|
|
class SupplierStatus(enum.Enum):
|
|
"""Supplier lifecycle status"""
|
|
ACTIVE = "active"
|
|
INACTIVE = "inactive"
|
|
PENDING_APPROVAL = "pending_approval"
|
|
SUSPENDED = "suspended"
|
|
BLACKLISTED = "blacklisted"
|
|
|
|
|
|
class PaymentTerms(enum.Enum):
|
|
"""Payment terms with suppliers"""
|
|
CASH_ON_DELIVERY = "cod"
|
|
NET_15 = "net_15"
|
|
NET_30 = "net_30"
|
|
NET_45 = "net_45"
|
|
NET_60 = "net_60"
|
|
PREPAID = "prepaid"
|
|
CREDIT_TERMS = "credit_terms"
|
|
|
|
|
|
class PurchaseOrderStatus(enum.Enum):
|
|
"""Purchase order lifecycle status"""
|
|
DRAFT = "draft"
|
|
PENDING_APPROVAL = "pending_approval"
|
|
APPROVED = "approved"
|
|
SENT_TO_SUPPLIER = "sent_to_supplier"
|
|
CONFIRMED = "confirmed"
|
|
PARTIALLY_RECEIVED = "partially_received"
|
|
COMPLETED = "completed"
|
|
CANCELLED = "cancelled"
|
|
DISPUTED = "disputed"
|
|
|
|
|
|
class DeliveryStatus(enum.Enum):
|
|
"""Delivery status tracking"""
|
|
SCHEDULED = "scheduled"
|
|
IN_TRANSIT = "in_transit"
|
|
OUT_FOR_DELIVERY = "out_for_delivery"
|
|
DELIVERED = "delivered"
|
|
PARTIALLY_DELIVERED = "partially_delivered"
|
|
FAILED_DELIVERY = "failed_delivery"
|
|
RETURNED = "returned"
|
|
|
|
|
|
class QualityRating(enum.Enum):
|
|
"""Quality rating scale"""
|
|
EXCELLENT = 5
|
|
GOOD = 4
|
|
AVERAGE = 3
|
|
POOR = 2
|
|
VERY_POOR = 1
|
|
|
|
|
|
class DeliveryRating(enum.Enum):
|
|
"""Delivery performance rating scale"""
|
|
EXCELLENT = 5
|
|
GOOD = 4
|
|
AVERAGE = 3
|
|
POOR = 2
|
|
VERY_POOR = 1
|
|
|
|
|
|
class InvoiceStatus(enum.Enum):
|
|
"""Invoice processing status"""
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
PAID = "paid"
|
|
OVERDUE = "overdue"
|
|
DISPUTED = "disputed"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class Supplier(Base):
|
|
"""Master supplier information"""
|
|
__tablename__ = "suppliers"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
|
|
# Basic supplier information
|
|
name = Column(String(255), nullable=False, index=True)
|
|
supplier_code = Column(String(50), nullable=True, index=True) # Internal reference code
|
|
tax_id = Column(String(50), nullable=True) # VAT/Tax ID
|
|
registration_number = Column(String(100), nullable=True) # Business registration number
|
|
|
|
# Supplier classification
|
|
supplier_type = Column(SQLEnum(SupplierType), nullable=False, index=True)
|
|
status = Column(SQLEnum(SupplierStatus), nullable=False, default=SupplierStatus.PENDING_APPROVAL, index=True)
|
|
|
|
# Contact information
|
|
contact_person = Column(String(200), nullable=True)
|
|
email = Column(String(254), nullable=True)
|
|
phone = Column(String(30), nullable=True)
|
|
mobile = Column(String(30), nullable=True)
|
|
website = Column(String(255), 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_province = Column(String(100), nullable=True)
|
|
postal_code = Column(String(20), nullable=True)
|
|
country = Column(String(100), nullable=True)
|
|
|
|
# Business terms
|
|
payment_terms = Column(SQLEnum(PaymentTerms), nullable=False, default=PaymentTerms.NET_30)
|
|
credit_limit = Column(Numeric(12, 2), nullable=True)
|
|
currency = Column(String(3), nullable=False, default="EUR") # ISO currency code
|
|
|
|
# Lead times (in days)
|
|
standard_lead_time = Column(Integer, nullable=False, default=3)
|
|
minimum_order_amount = Column(Numeric(10, 2), nullable=True)
|
|
delivery_area = Column(String(255), nullable=True)
|
|
|
|
# Quality and performance metrics
|
|
quality_rating = Column(Float, nullable=True, default=0.0) # Average quality rating (1-5)
|
|
delivery_rating = Column(Float, nullable=True, default=0.0) # Average delivery rating (1-5)
|
|
total_orders = Column(Integer, nullable=False, default=0)
|
|
total_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
|
|
|
|
# Onboarding and approval
|
|
approved_by = Column(UUID(as_uuid=True), nullable=True) # User who approved
|
|
approved_at = Column(DateTime(timezone=True), nullable=True)
|
|
rejection_reason = Column(Text, nullable=True)
|
|
|
|
# Additional information
|
|
notes = Column(Text, nullable=True)
|
|
certifications = Column(JSONB, nullable=True) # Quality certifications, licenses
|
|
business_hours = Column(JSONB, nullable=True) # Operating hours by day
|
|
specializations = Column(JSONB, nullable=True) # Product categories, special services
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
created_by = Column(UUID(as_uuid=True), nullable=False)
|
|
updated_by = Column(UUID(as_uuid=True), nullable=False)
|
|
|
|
# Relationships
|
|
price_lists = relationship("SupplierPriceList", back_populates="supplier", cascade="all, delete-orphan")
|
|
purchase_orders = relationship("PurchaseOrder", back_populates="supplier")
|
|
quality_reviews = relationship("SupplierQualityReview", back_populates="supplier", cascade="all, delete-orphan")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_suppliers_tenant_name', 'tenant_id', 'name'),
|
|
Index('ix_suppliers_tenant_status', 'tenant_id', 'status'),
|
|
Index('ix_suppliers_tenant_type', 'tenant_id', 'supplier_type'),
|
|
Index('ix_suppliers_quality_rating', 'quality_rating'),
|
|
)
|
|
|
|
|
|
class SupplierPriceList(Base):
|
|
"""Product pricing from suppliers"""
|
|
__tablename__ = "supplier_price_lists"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
|
|
|
|
# Product identification (references inventory service)
|
|
inventory_product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to inventory products
|
|
product_code = Column(String(100), nullable=True) # Supplier's product code
|
|
|
|
# Pricing information
|
|
unit_price = Column(Numeric(10, 4), nullable=False)
|
|
unit_of_measure = Column(String(20), nullable=False) # kg, g, l, ml, units, etc.
|
|
minimum_order_quantity = Column(Integer, nullable=True, default=1)
|
|
price_per_unit = Column(Numeric(10, 4), nullable=False) # Calculated field
|
|
|
|
# Pricing tiers (volume discounts)
|
|
tier_pricing = Column(JSONB, nullable=True) # [{quantity: 100, price: 2.50}, ...]
|
|
|
|
# Validity and terms
|
|
effective_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
expiry_date = Column(DateTime(timezone=True), nullable=True)
|
|
is_active = Column(Boolean, nullable=False, default=True)
|
|
|
|
# Additional product details
|
|
brand = Column(String(100), nullable=True)
|
|
packaging_size = Column(String(50), nullable=True)
|
|
origin_country = Column(String(100), nullable=True)
|
|
shelf_life_days = Column(Integer, nullable=True)
|
|
storage_requirements = Column(Text, nullable=True)
|
|
|
|
# Quality specifications
|
|
quality_specs = Column(JSONB, nullable=True) # Quality parameters, certifications
|
|
allergens = Column(JSONB, nullable=True) # Allergen information
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
created_by = Column(UUID(as_uuid=True), nullable=False)
|
|
updated_by = Column(UUID(as_uuid=True), nullable=False)
|
|
|
|
# Relationships
|
|
supplier = relationship("Supplier", back_populates="price_lists")
|
|
purchase_order_items = relationship("PurchaseOrderItem", back_populates="price_list_item")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_price_lists_tenant_supplier', 'tenant_id', 'supplier_id'),
|
|
Index('ix_price_lists_inventory_product', 'inventory_product_id'),
|
|
Index('ix_price_lists_active', 'is_active'),
|
|
Index('ix_price_lists_effective_date', 'effective_date'),
|
|
)
|
|
|
|
|
|
class PurchaseOrder(Base):
|
|
"""Purchase orders to suppliers"""
|
|
__tablename__ = "purchase_orders"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
|
|
|
|
# Order identification
|
|
po_number = Column(String(50), nullable=False, index=True) # Human-readable PO number
|
|
reference_number = Column(String(100), nullable=True) # Internal reference
|
|
|
|
# Order status and workflow
|
|
status = Column(SQLEnum(PurchaseOrderStatus), nullable=False, default=PurchaseOrderStatus.DRAFT, index=True)
|
|
priority = Column(String(20), nullable=False, default="normal") # urgent, high, normal, low
|
|
|
|
# Order details
|
|
order_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
required_delivery_date = Column(DateTime(timezone=True), nullable=True)
|
|
estimated_delivery_date = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Financial information
|
|
subtotal = Column(Numeric(12, 2), nullable=False, default=0.0)
|
|
tax_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
|
|
shipping_cost = Column(Numeric(10, 2), nullable=False, default=0.0)
|
|
discount_amount = Column(Numeric(10, 2), nullable=False, default=0.0)
|
|
total_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
|
|
currency = Column(String(3), nullable=False, default="EUR")
|
|
|
|
# Delivery information
|
|
delivery_address = Column(Text, nullable=True) # Override default address
|
|
delivery_instructions = Column(Text, nullable=True)
|
|
delivery_contact = Column(String(200), nullable=True)
|
|
delivery_phone = Column(String(30), nullable=True)
|
|
|
|
# Approval workflow
|
|
requires_approval = Column(Boolean, nullable=False, default=False)
|
|
approved_by = Column(UUID(as_uuid=True), nullable=True)
|
|
approved_at = Column(DateTime(timezone=True), nullable=True)
|
|
rejection_reason = Column(Text, nullable=True)
|
|
|
|
# Communication tracking
|
|
sent_to_supplier_at = Column(DateTime(timezone=True), nullable=True)
|
|
supplier_confirmation_date = Column(DateTime(timezone=True), nullable=True)
|
|
supplier_reference = Column(String(100), nullable=True) # Supplier's order reference
|
|
|
|
# Additional information
|
|
notes = Column(Text, nullable=True)
|
|
internal_notes = Column(Text, nullable=True) # Not shared with supplier
|
|
terms_and_conditions = Column(Text, nullable=True)
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
created_by = Column(UUID(as_uuid=True), nullable=False)
|
|
updated_by = Column(UUID(as_uuid=True), nullable=False)
|
|
|
|
# Relationships
|
|
supplier = relationship("Supplier", back_populates="purchase_orders")
|
|
items = relationship("PurchaseOrderItem", back_populates="purchase_order", cascade="all, delete-orphan")
|
|
deliveries = relationship("Delivery", back_populates="purchase_order")
|
|
invoices = relationship("SupplierInvoice", back_populates="purchase_order")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_purchase_orders_tenant_supplier', 'tenant_id', 'supplier_id'),
|
|
Index('ix_purchase_orders_tenant_status', 'tenant_id', 'status'),
|
|
Index('ix_purchase_orders_po_number', 'po_number'),
|
|
Index('ix_purchase_orders_order_date', 'order_date'),
|
|
Index('ix_purchase_orders_delivery_date', 'required_delivery_date'),
|
|
)
|
|
|
|
|
|
class PurchaseOrderItem(Base):
|
|
"""Individual items within purchase orders"""
|
|
__tablename__ = "purchase_order_items"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
purchase_order_id = Column(UUID(as_uuid=True), ForeignKey('purchase_orders.id'), nullable=False, index=True)
|
|
price_list_item_id = Column(UUID(as_uuid=True), ForeignKey('supplier_price_lists.id'), nullable=True, index=True)
|
|
|
|
# Product identification
|
|
inventory_product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to inventory products
|
|
product_code = Column(String(100), nullable=True) # Supplier's product code
|
|
|
|
# Order quantities
|
|
ordered_quantity = Column(Integer, nullable=False)
|
|
unit_of_measure = Column(String(20), nullable=False)
|
|
unit_price = Column(Numeric(10, 4), nullable=False)
|
|
line_total = Column(Numeric(12, 2), nullable=False)
|
|
|
|
# Delivery tracking
|
|
received_quantity = Column(Integer, nullable=False, default=0)
|
|
remaining_quantity = Column(Integer, nullable=False, default=0)
|
|
|
|
# Quality and notes
|
|
quality_requirements = Column(Text, nullable=True)
|
|
item_notes = Column(Text, nullable=True)
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
|
|
# Relationships
|
|
purchase_order = relationship("PurchaseOrder", back_populates="items")
|
|
price_list_item = relationship("SupplierPriceList", back_populates="purchase_order_items")
|
|
delivery_items = relationship("DeliveryItem", back_populates="purchase_order_item")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_po_items_tenant_po', 'tenant_id', 'purchase_order_id'),
|
|
Index('ix_po_items_inventory_product', 'inventory_product_id'),
|
|
)
|
|
|
|
|
|
class Delivery(Base):
|
|
"""Delivery tracking for purchase orders"""
|
|
__tablename__ = "deliveries"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
purchase_order_id = Column(UUID(as_uuid=True), ForeignKey('purchase_orders.id'), nullable=False, index=True)
|
|
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
|
|
|
|
# Delivery identification
|
|
delivery_number = Column(String(50), nullable=False, index=True)
|
|
supplier_delivery_note = Column(String(100), nullable=True) # Supplier's delivery reference
|
|
|
|
# Delivery status and tracking
|
|
status = Column(SQLEnum(DeliveryStatus), nullable=False, default=DeliveryStatus.SCHEDULED, index=True)
|
|
|
|
# Scheduling and timing
|
|
scheduled_date = Column(DateTime(timezone=True), nullable=True)
|
|
estimated_arrival = Column(DateTime(timezone=True), nullable=True)
|
|
actual_arrival = Column(DateTime(timezone=True), nullable=True)
|
|
completed_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Delivery details
|
|
delivery_address = Column(Text, nullable=True)
|
|
delivery_contact = Column(String(200), nullable=True)
|
|
delivery_phone = Column(String(30), nullable=True)
|
|
carrier_name = Column(String(200), nullable=True)
|
|
tracking_number = Column(String(100), nullable=True)
|
|
|
|
# Quality inspection
|
|
inspection_passed = Column(Boolean, nullable=True)
|
|
inspection_notes = Column(Text, nullable=True)
|
|
quality_issues = Column(JSONB, nullable=True) # Documented quality problems
|
|
|
|
# Received by information
|
|
received_by = Column(UUID(as_uuid=True), nullable=True) # User who received the delivery
|
|
received_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Additional information
|
|
notes = Column(Text, nullable=True)
|
|
photos = Column(JSONB, nullable=True) # Photo URLs for documentation
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
created_by = Column(UUID(as_uuid=True), nullable=False)
|
|
|
|
# Relationships
|
|
purchase_order = relationship("PurchaseOrder", back_populates="deliveries")
|
|
supplier = relationship("Supplier")
|
|
items = relationship("DeliveryItem", back_populates="delivery", cascade="all, delete-orphan")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_deliveries_tenant_status', 'tenant_id', 'status'),
|
|
Index('ix_deliveries_scheduled_date', 'scheduled_date'),
|
|
Index('ix_deliveries_delivery_number', 'delivery_number'),
|
|
)
|
|
|
|
|
|
class DeliveryItem(Base):
|
|
"""Individual items within deliveries"""
|
|
__tablename__ = "delivery_items"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
delivery_id = Column(UUID(as_uuid=True), ForeignKey('deliveries.id'), nullable=False, index=True)
|
|
purchase_order_item_id = Column(UUID(as_uuid=True), ForeignKey('purchase_order_items.id'), nullable=False, index=True)
|
|
|
|
# Product identification
|
|
inventory_product_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
|
|
# Delivery quantities
|
|
ordered_quantity = Column(Integer, nullable=False)
|
|
delivered_quantity = Column(Integer, nullable=False)
|
|
accepted_quantity = Column(Integer, nullable=False)
|
|
rejected_quantity = Column(Integer, nullable=False, default=0)
|
|
|
|
# Quality information
|
|
batch_lot_number = Column(String(100), nullable=True)
|
|
expiry_date = Column(DateTime(timezone=True), nullable=True)
|
|
quality_grade = Column(String(20), nullable=True)
|
|
|
|
# Issues and notes
|
|
quality_issues = Column(Text, nullable=True)
|
|
rejection_reason = Column(Text, nullable=True)
|
|
item_notes = Column(Text, nullable=True)
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
|
|
# Relationships
|
|
delivery = relationship("Delivery", back_populates="items")
|
|
purchase_order_item = relationship("PurchaseOrderItem", back_populates="delivery_items")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_delivery_items_tenant_delivery', 'tenant_id', 'delivery_id'),
|
|
Index('ix_delivery_items_inventory_product', 'inventory_product_id'),
|
|
)
|
|
|
|
|
|
class SupplierQualityReview(Base):
|
|
"""Quality and performance reviews for suppliers"""
|
|
__tablename__ = "supplier_quality_reviews"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
|
|
purchase_order_id = Column(UUID(as_uuid=True), ForeignKey('purchase_orders.id'), nullable=True, index=True)
|
|
delivery_id = Column(UUID(as_uuid=True), ForeignKey('deliveries.id'), nullable=True, index=True)
|
|
|
|
# Review details
|
|
review_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
review_type = Column(String(50), nullable=False) # delivery, monthly, annual, incident
|
|
|
|
# Ratings (1-5 scale)
|
|
quality_rating = Column(SQLEnum(QualityRating), nullable=False)
|
|
delivery_rating = Column(SQLEnum(DeliveryRating), nullable=False)
|
|
communication_rating = Column(Integer, nullable=False) # 1-5
|
|
overall_rating = Column(Float, nullable=False) # Calculated average
|
|
|
|
# Detailed feedback
|
|
quality_comments = Column(Text, nullable=True)
|
|
delivery_comments = Column(Text, nullable=True)
|
|
communication_comments = Column(Text, nullable=True)
|
|
improvement_suggestions = Column(Text, nullable=True)
|
|
|
|
# Issues and corrective actions
|
|
quality_issues = Column(JSONB, nullable=True) # Documented issues
|
|
corrective_actions = Column(Text, nullable=True)
|
|
follow_up_required = Column(Boolean, nullable=False, default=False)
|
|
follow_up_date = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Review status
|
|
is_final = Column(Boolean, nullable=False, default=True)
|
|
approved_by = Column(UUID(as_uuid=True), nullable=True)
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
reviewed_by = Column(UUID(as_uuid=True), nullable=False)
|
|
|
|
# Relationships
|
|
supplier = relationship("Supplier", back_populates="quality_reviews")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_quality_reviews_tenant_supplier', 'tenant_id', 'supplier_id'),
|
|
Index('ix_quality_reviews_date', 'review_date'),
|
|
Index('ix_quality_reviews_overall_rating', 'overall_rating'),
|
|
)
|
|
|
|
|
|
class SupplierInvoice(Base):
|
|
"""Invoices from suppliers"""
|
|
__tablename__ = "supplier_invoices"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
|
|
purchase_order_id = Column(UUID(as_uuid=True), ForeignKey('purchase_orders.id'), nullable=True, index=True)
|
|
|
|
# Invoice identification
|
|
invoice_number = Column(String(50), nullable=False, index=True)
|
|
supplier_invoice_number = Column(String(100), nullable=False)
|
|
|
|
# Invoice status and dates
|
|
status = Column(SQLEnum(InvoiceStatus), nullable=False, default=InvoiceStatus.PENDING, index=True)
|
|
invoice_date = Column(DateTime(timezone=True), nullable=False)
|
|
due_date = Column(DateTime(timezone=True), nullable=False)
|
|
received_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
|
|
# Financial information
|
|
subtotal = Column(Numeric(12, 2), nullable=False)
|
|
tax_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
|
|
shipping_cost = Column(Numeric(10, 2), nullable=False, default=0.0)
|
|
discount_amount = Column(Numeric(10, 2), nullable=False, default=0.0)
|
|
total_amount = Column(Numeric(12, 2), nullable=False)
|
|
currency = Column(String(3), nullable=False, default="EUR")
|
|
|
|
# Payment tracking
|
|
paid_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
|
|
payment_date = Column(DateTime(timezone=True), nullable=True)
|
|
payment_reference = Column(String(100), nullable=True)
|
|
|
|
# Invoice validation
|
|
approved_by = Column(UUID(as_uuid=True), nullable=True)
|
|
approved_at = Column(DateTime(timezone=True), nullable=True)
|
|
rejection_reason = Column(Text, nullable=True)
|
|
|
|
# Additional information
|
|
notes = Column(Text, nullable=True)
|
|
invoice_document_url = Column(String(500), nullable=True) # PDF storage location
|
|
|
|
# Audit fields
|
|
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
|
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
|
created_by = Column(UUID(as_uuid=True), nullable=False)
|
|
|
|
# Relationships
|
|
supplier = relationship("Supplier")
|
|
purchase_order = relationship("PurchaseOrder", back_populates="invoices")
|
|
|
|
# Indexes
|
|
__table_args__ = (
|
|
Index('ix_invoices_tenant_supplier', 'tenant_id', 'supplier_id'),
|
|
Index('ix_invoices_tenant_status', 'tenant_id', 'status'),
|
|
Index('ix_invoices_due_date', 'due_date'),
|
|
Index('ix_invoices_invoice_number', 'invoice_number'),
|
|
) |