Spaces:
Sleeping
Sleeping
| """ | |
| Inventory Transfer Models - Agent-to-Agent Equipment Transfers | |
| WORKFLOW: | |
| 1. Agent A initiates transfer to Agent B | |
| 2. Optional: PM/Dispatcher approves (if requires_approval=true) | |
| 3. Agent B accepts transfer | |
| 4. System creates new assignment for Agent B | |
| 5. Original assignment marked as transferred | |
| 6. Transfer marked as completed | |
| USE CASES: | |
| - Equipment shortage: Agent with extra equipment helps another agent | |
| - Job reassignment: Transfer equipment when job is reassigned | |
| - Emergency: Urgent need for equipment, faster than going to hub | |
| """ | |
| from sqlalchemy import Column, String, Boolean, Integer, Text, DateTime, Numeric, ForeignKey, CheckConstraint, Index | |
| from sqlalchemy.dialects.postgresql import UUID, JSONB | |
| from sqlalchemy.orm import relationship | |
| from datetime import datetime | |
| from decimal import Decimal | |
| from app.models.base import BaseModel | |
| class InventoryTransfer(BaseModel): | |
| """ | |
| Agent-to-agent inventory transfers | |
| Tracks peer-to-peer equipment transfers between field agents. | |
| Supports approval workflow and acceptance by recipient. | |
| """ | |
| __tablename__ = "inventory_transfers" | |
| # Source and destination | |
| from_assignment_id = Column(UUID(as_uuid=True), ForeignKey("inventory_assignments.id", ondelete="RESTRICT"), nullable=False) | |
| from_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="RESTRICT"), nullable=False) | |
| to_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="RESTRICT"), nullable=False) | |
| to_assignment_id = Column(UUID(as_uuid=True), ForeignKey("inventory_assignments.id", ondelete="SET NULL")) | |
| # Transfer details | |
| transfer_reason = Column(Text, nullable=False) | |
| quantity = Column(Numeric(10, 2), nullable=False, default=1) | |
| unit_identifier = Column(Text, nullable=False) | |
| # Approval workflow | |
| status = Column(String, nullable=False, default="pending") # TransferStatus enum | |
| requires_approval = Column(Boolean, nullable=False, default=True) | |
| approved_by_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL")) | |
| approved_at = Column(DateTime(timezone=True)) | |
| approval_notes = Column(Text) | |
| # Rejection | |
| rejected_by_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL")) | |
| rejected_at = Column(DateTime(timezone=True)) | |
| rejection_reason = Column(Text) | |
| # Acceptance by recipient | |
| accepted_by_to_user_at = Column(DateTime(timezone=True)) | |
| # Completion | |
| completed_at = Column(DateTime(timezone=True)) | |
| # Location tracking | |
| transfer_latitude = Column(Numeric(10, 7)) | |
| transfer_longitude = Column(Numeric(10, 7)) | |
| transfer_location_name = Column(Text) | |
| location_verified = Column(Boolean, default=False) | |
| # Metadata | |
| notes = Column(Text) | |
| additional_metadata = Column(JSONB, default={}) | |
| # Audit | |
| initiated_by_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="RESTRICT"), nullable=False) | |
| # Relationships | |
| from_assignment = relationship("InventoryAssignment", foreign_keys=[from_assignment_id], | |
| backref="outgoing_transfers") | |
| to_assignment = relationship("InventoryAssignment", foreign_keys=[to_assignment_id], | |
| backref="incoming_transfers") | |
| from_user = relationship("User", foreign_keys=[from_user_id]) | |
| to_user = relationship("User", foreign_keys=[to_user_id]) | |
| approved_by = relationship("User", foreign_keys=[approved_by_user_id]) | |
| rejected_by = relationship("User", foreign_keys=[rejected_by_user_id]) | |
| initiated_by = relationship("User", foreign_keys=[initiated_by_user_id]) | |
| # Table constraints | |
| __table_args__ = ( | |
| CheckConstraint("quantity > 0", name="chk_positive_transfer_quantity"), | |
| CheckConstraint("from_user_id != to_user_id", name="chk_different_users"), | |
| Index("idx_inventory_transfers_from_assignment", "from_assignment_id", | |
| postgresql_where=(Column("deleted_at").is_(None))), | |
| Index("idx_inventory_transfers_to_assignment", "to_assignment_id", | |
| postgresql_where=(Column("to_assignment_id").isnot(None)) & (Column("deleted_at").is_(None))), | |
| Index("idx_inventory_transfers_from_user", "from_user_id", "status", "created_at", | |
| postgresql_where=(Column("deleted_at").is_(None))), | |
| Index("idx_inventory_transfers_to_user", "to_user_id", "status", "created_at", | |
| postgresql_where=(Column("deleted_at").is_(None))), | |
| Index("idx_inventory_transfers_status", "status", "created_at", | |
| postgresql_where=(Column("deleted_at").is_(None))), | |
| Index("idx_inventory_transfers_pending", "to_user_id", "status", | |
| postgresql_where=(Column("status") == "pending") & (Column("deleted_at").is_(None))), | |
| Index("idx_inventory_transfers_metadata_gin", "additional_metadata", postgresql_using="gin"), | |
| ) | |
| def is_pending(self) -> bool: | |
| """Check if transfer is pending approval or acceptance""" | |
| return self.status == "pending" | |
| def is_completed(self) -> bool: | |
| """Check if transfer is completed""" | |
| return self.status == "completed" | |
| def can_approve(self) -> bool: | |
| """Check if transfer can be approved""" | |
| return self.status == "pending" and self.requires_approval and not self.approved_at | |
| def can_accept(self) -> bool: | |
| """Check if transfer can be accepted by recipient""" | |
| if self.requires_approval: | |
| return self.status == "approved" and not self.accepted_by_to_user_at | |
| return self.status == "pending" and not self.accepted_by_to_user_at | |
| def __repr__(self): | |
| return f"<InventoryTransfer(id={self.id}, from={self.from_user_id}, to={self.to_user_id}, status={self.status})>" | |