ifixkart-backend/app/models/MigrationModel.py

259 lines
11 KiB
Python

import enum
import uuid
from datetime import datetime
from sqlalchemy import Column, String, Integer, BigInteger, Boolean, Enum as SQLEnum, Text, JSON, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from app.core.database.db_session import Base
def generate_uuid():
return str(uuid.uuid4())
class BatchTypeEnum(str, enum.Enum):
PRODUCTS = "PRODUCTS"
VARIANTS = "VARIANTS"
MEDIA = "MEDIA"
INVENTORY = "INVENTORY"
CRM_LEADS = "CRM_LEADS"
REPAIR_CATALOG = "REPAIR_CATALOG"
CUSTOMERS = "CUSTOMERS"
WAREHOUSES = "WAREHOUSES"
class ImportModeEnum(str, enum.Enum):
CREATE_ONLY = "CREATE_ONLY"
UPDATE_EXISTING = "UPDATE_EXISTING"
UPSERT = "UPSERT"
SKIP_EXISTING = "SKIP_EXISTING"
class BatchStatusEnum(str, enum.Enum):
PENDING = "PENDING"
VALIDATING = "VALIDATING"
READY = "READY"
PROCESSING = "PROCESSING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
ROLLED_BACK = "ROLLED_BACK"
class JobStatusEnum(str, enum.Enum):
QUEUED = "QUEUED"
VALIDATING = "VALIDATING"
RUNNING = "RUNNING"
CANCELLING = "CANCELLING"
CANCELLED = "CANCELLED"
FAILED = "FAILED"
COMPLETED = "COMPLETED"
class PhaseEnum(str, enum.Enum):
UPLOAD = "UPLOAD"
VALIDATE = "VALIDATE"
DRY_RUN = "DRY_RUN"
MASTER_DATA = "MASTER_DATA"
PRODUCTS = "PRODUCTS"
VARIANTS = "VARIANTS"
MEDIA_PROCESS = "MEDIA_PROCESS"
MEDIA_LINK = "MEDIA_LINK"
VERIFY = "VERIFY"
COMPLETED = "COMPLETED"
class MediaItemStatusEnum(str, enum.Enum):
PENDING = "PENDING"
PROCESSING = "PROCESSING"
STORED = "STORED"
REGISTERED = "REGISTERED"
FAILED = "FAILED"
class RetryStatusEnum(str, enum.Enum):
UNRESOLVED = "UNRESOLVED"
RETRYING = "RETRYING"
RETRIED = "RETRIED"
RESOLVED = "RESOLVED"
SKIPPED = "SKIPPED"
PERMANENT_FAILURE = "PERMANENT_FAILURE"
class ErrorSeverityEnum(str, enum.Enum):
ERROR = "ERROR"
WARNING = "WARNING"
class MediaSourceTypeEnum(str, enum.Enum):
MEDIA_JSON = "MEDIA_JSON"
FOLDER_PATH = "FOLDER_PATH"
class MigrationBatch(Base):
__tablename__ = "migration_batches"
id = Column(String(36), primary_key=True, default=generate_uuid)
batch_type = Column(SQLEnum(BatchTypeEnum), nullable=False, default=BatchTypeEnum.PRODUCTS)
user_id = Column(String(36), nullable=False)
import_mode = Column(SQLEnum(ImportModeEnum), nullable=False, default=ImportModeEnum.UPSERT)
status = Column(SQLEnum(BatchStatusEnum), nullable=False, default=BatchStatusEnum.PENDING)
created_at = Column(DateTime, default=datetime.utcnow)
completed_at = Column(DateTime, nullable=True)
jobs = relationship("MigrationJob", back_populates="batch", cascade="all, delete-orphan")
snapshots = relationship("MigrationSnapshot", back_populates="batch", cascade="all, delete-orphan")
class MappingConfig(Base):
__tablename__ = "mapping_configs"
id = Column(String(36), primary_key=True, default=generate_uuid)
name = Column(String(100), nullable=False)
entity_type = Column(String(50), nullable=False)
column_maps = Column(JSON, nullable=False)
created_by = Column(String(36), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
class MediaGroup(Base):
__tablename__ = "media_groups"
id = Column(String(36), primary_key=True, default=generate_uuid)
media_key = Column(String(255), unique=True, nullable=False, index=True)
source_type = Column(SQLEnum(MediaSourceTypeEnum), nullable=False, default=MediaSourceTypeEnum.FOLDER_PATH)
brand_name = Column(String(100), nullable=True)
model_name = Column(String(100), nullable=True)
variant_tag = Column(String(100), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
media_assets = relationship("MediaAsset", back_populates="group", cascade="all, delete-orphan")
class MediaAsset(Base):
__tablename__ = "media_library"
id = Column(String(36), primary_key=True, default=generate_uuid)
media_group_id = Column(String(36), ForeignKey("media_groups.id", ondelete="CASCADE"), nullable=False)
original_filename = Column(String(255), nullable=False)
stored_filename = Column(String(255), nullable=False)
mime_type = Column(String(50), nullable=False)
file_size_bytes = Column(BigInteger, nullable=False)
width = Column(Integer, nullable=False, default=0)
height = Column(Integer, nullable=False, default=0)
sha256_checksum = Column(String(64), unique=True, nullable=False, index=True) # Global unique SHA-256 asset
cdn_url = Column(String(512), nullable=False)
thumbnail_url = Column(String(512), nullable=False)
storage_path = Column(String(512), nullable=True)
exif_metadata = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
group = relationship("MediaGroup", back_populates="media_assets")
class MigrationJob(Base):
__tablename__ = "migration_jobs"
id = Column(String(36), primary_key=True, default=generate_uuid)
batch_id = Column(String(36), ForeignKey("migration_batches.id", ondelete="CASCADE"), nullable=False)
job_type = Column(String(50), nullable=False, default="PRODUCTS")
is_dry_run = Column(Boolean, nullable=False, default=False)
file_name = Column(String(255), nullable=False, default="")
file_format = Column(String(10), nullable=False, default="CSV")
status = Column(SQLEnum(JobStatusEnum), nullable=False, default=JobStatusEnum.QUEUED, index=True)
current_phase = Column(SQLEnum(PhaseEnum), nullable=False, default=PhaseEnum.UPLOAD, index=True)
# Worker Lease & Lock
worker_id = Column(String(64), nullable=True, index=True)
locked_at = Column(DateTime, nullable=True)
heartbeat_at = Column(DateTime, nullable=True)
lease_version = Column(BigInteger, nullable=False, default=1)
# Cancellation
cancel_requested_at = Column(DateTime, nullable=True)
# Phase Progress
current_batch = Column(Integer, nullable=False, default=0)
total_batches = Column(Integer, nullable=False, default=0)
last_successful_batch = Column(Integer, nullable=False, default=0)
# Overall Progress
processed_records = Column(Integer, nullable=False, default=0)
total_records = Column(Integer, nullable=False, default=0)
successful_records = Column(Integer, nullable=False, default=0)
failed_records = Column(Integer, nullable=False, default=0)
warning_records = Column(Integer, nullable=False, default=0)
retry_count = Column(Integer, nullable=False, default=0)
# Baseline Expectations (recorded during validation phase)
expected_products = Column(Integer, nullable=False, default=0)
expected_variants = Column(Integer, nullable=False, default=0)
expected_media_items = Column(Integer, nullable=False, default=0)
expected_media_links = Column(Integer, nullable=False, default=0)
# Timestamps & Errors
started_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True)
failed_at = Column(DateTime, nullable=True)
finished_at = Column(DateTime, nullable=True)
error_message = Column(Text, nullable=True)
batch = relationship("MigrationBatch", back_populates="jobs")
checkpoints = relationship("MigrationJobCheckpoint", back_populates="job", cascade="all, delete-orphan")
media_items = relationship("MigrationMediaItem", back_populates="job", cascade="all, delete-orphan")
errors = relationship("MigrationError", back_populates="job", cascade="all, delete-orphan")
class MigrationJobCheckpoint(Base):
__tablename__ = "migration_job_checkpoints"
id = Column(String(36), primary_key=True, default=generate_uuid)
job_id = Column(String(36), ForeignKey("migration_jobs.id", ondelete="CASCADE"), nullable=False, index=True)
phase = Column(SQLEnum(PhaseEnum), nullable=False, index=True)
last_successful_batch = Column(Integer, nullable=False, default=0)
total_batches = Column(Integer, nullable=False, default=0)
processed_records = Column(Integer, nullable=False, default=0)
failed_records = Column(Integer, nullable=False, default=0)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
job = relationship("MigrationJob", back_populates="checkpoints")
class MigrationMediaItem(Base):
__tablename__ = "migration_media_items"
id = Column(String(36), primary_key=True, default=generate_uuid)
job_id = Column(String(36), ForeignKey("migration_jobs.id", ondelete="CASCADE"), nullable=False, index=True)
batch_number = Column(Integer, nullable=False, default=0)
file_name = Column(String(255), nullable=False)
archive_name = Column(String(255), nullable=True)
zip_entry_path = Column(String(512), nullable=True)
media_key = Column(String(255), nullable=False, index=True)
sha256 = Column(String(64), nullable=True, index=True) # NOT unique (allows multiple source references to map to 1 MediaAsset)
storage_path = Column(String(512), nullable=True)
status = Column(SQLEnum(MediaItemStatusEnum), nullable=False, default=MediaItemStatusEnum.PENDING, index=True)
error = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
job = relationship("MigrationJob", back_populates="media_items")
class MigrationError(Base):
__tablename__ = "migration_errors"
id = Column(String(36), primary_key=True, default=generate_uuid)
job_id = Column(String(36), ForeignKey("migration_jobs.id", ondelete="CASCADE"), nullable=False, index=True)
batch_number = Column(Integer, nullable=False, default=0)
row_number = Column(Integer, nullable=False)
sku = Column(String(100), nullable=True)
product_name = Column(String(255), nullable=True)
file_name = Column(String(255), nullable=True)
entity_type = Column(String(50), nullable=True)
entity_id = Column(String(36), nullable=True)
phase = Column(String(50), nullable=False, default="VALIDATE")
error_type = Column(String(100), nullable=False, default="DATA_ERROR")
severity = Column(SQLEnum(ErrorSeverityEnum), nullable=False, default=ErrorSeverityEnum.ERROR)
field_name = Column(String(100), nullable=True)
error_message = Column(Text, nullable=False)
suggested_fix = Column(Text, nullable=True)
retry_status = Column(SQLEnum(RetryStatusEnum), nullable=False, default=RetryStatusEnum.UNRESOLVED)
attempt_count = Column(Integer, nullable=False, default=1)
raw_row_data = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
resolved_at = Column(DateTime, nullable=True)
job = relationship("MigrationJob", back_populates="errors")
class MigrationSnapshot(Base):
__tablename__ = "migration_snapshots"
id = Column(String(36), primary_key=True, default=generate_uuid)
batch_id = Column(String(36), ForeignKey("migration_batches.id", ondelete="CASCADE"), nullable=False)
entity_table = Column(String(50), nullable=False)
entity_id = Column(String(36), nullable=False)
action_taken = Column(String(20), nullable=False)
previous_state = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
batch = relationship("MigrationBatch", back_populates="snapshots")