from datetime import date, datetime from decimal import Decimal from sqlalchemy import JSON, Date, DateTime, Integer, Numeric, String, Text from sqlalchemy.orm import Mapped, mapped_column from app.core.constants import ActorValue from app.core.database import Base from app.core.utils.time import utc_now from app.modules.business.constants import ( AccountType, PriorityValue, RiskLevel, SourceSystem, StatusValue, VersionValue, ) class TimestampMixin: created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now) updated_at: Mapped[datetime] = mapped_column( DateTime, default=utc_now, onupdate=utc_now ) class Project(Base, TimestampMixin): __tablename__ = "projects" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) name: Mapped[str] = mapped_column(String(255), index=True) owner: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) status: Mapped[str] = mapped_column(String(64), default=StatusValue.INITIATED, index=True) priority: Mapped[str] = mapped_column(String(32), default=PriorityValue.P2) progress_percent: Mapped[int] = mapped_column(Integer, default=0) risk_level: Mapped[str] = mapped_column(String(32), default=RiskLevel.LOW, index=True) budget_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=0) actual_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=0) start_date: Mapped[date | None] = mapped_column(Date, nullable=True) due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) source_system: Mapped[str] = mapped_column(String(64), default=SourceSystem.INTERNAL) external_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) class WorkTask(Base, TimestampMixin): __tablename__ = "work_tasks" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) title: Mapped[str] = mapped_column(String(255), index=True) project_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) owner: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) status: Mapped[str] = mapped_column(String(64), default=StatusValue.TODO, index=True) priority: Mapped[str] = mapped_column(String(32), default=PriorityValue.P2) due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) blocker: Mapped[str | None] = mapped_column(Text, nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) source_system: Mapped[str] = mapped_column(String(64), default=SourceSystem.INTERNAL) external_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) class Procurement(Base, TimestampMixin): __tablename__ = "procurements" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) name: Mapped[str] = mapped_column(String(255), index=True) applicant: Mapped[str | None] = mapped_column(String(128), nullable=True) project_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) supplier_name: Mapped[str | None] = mapped_column(String(255), nullable=True) budget_subject: Mapped[str | None] = mapped_column(String(128), nullable=True) expected_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=0) actual_amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=0) approval_status: Mapped[str] = mapped_column(String(64), default=StatusValue.DRAFT, index=True) delivery_status: Mapped[str] = mapped_column( String(64), default=StatusValue.UNDELIVERED, index=True, ) payment_status: Mapped[str] = mapped_column(String(64), default=StatusValue.UNPAID, index=True) comparison_summary: Mapped[str | None] = mapped_column(Text, nullable=True) class Expense(Base, TimestampMixin): __tablename__ = "expenses" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) expense_type: Mapped[str] = mapped_column(String(128), index=True) amount: Mapped[Decimal] = mapped_column(Numeric(14, 2), default=0) applicant: Mapped[str | None] = mapped_column(String(128), nullable=True) department: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) project_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) budget_subject: Mapped[str | None] = mapped_column(String(128), nullable=True) payment_account: Mapped[str | None] = mapped_column(String(128), nullable=True) invoice_status: Mapped[str] = mapped_column( String(64), default=StatusValue.INVOICE_NOT_RECEIVED, ) approval_status: Mapped[str] = mapped_column(String(64), default=StatusValue.DRAFT, index=True) payment_status: Mapped[str] = mapped_column(String(64), default=StatusValue.UNPAID, index=True) class FundAccount(Base, TimestampMixin): __tablename__ = "fund_accounts" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) name: Mapped[str] = mapped_column(String(255), index=True) account_type: Mapped[str] = mapped_column(String(64), default=AccountType.BANK) current_balance: Mapped[Decimal] = mapped_column(Numeric(16, 2), default=0) expected_receivable: Mapped[Decimal] = mapped_column(Numeric(16, 2), default=0) expected_payable: Mapped[Decimal] = mapped_column(Numeric(16, 2), default=0) safety_line: Mapped[Decimal] = mapped_column(Numeric(16, 2), default=0) risk_level: Mapped[str] = mapped_column(String(32), default=RiskLevel.LOW, index=True) note: Mapped[str | None] = mapped_column(Text, nullable=True) class Policy(Base, TimestampMixin): __tablename__ = "policies" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) title: Mapped[str] = mapped_column(String(255), index=True) policy_type: Mapped[str | None] = mapped_column(String(128), nullable=True) owner_department: Mapped[str | None] = mapped_column(String(128), nullable=True) version: Mapped[str] = mapped_column(String(32), default=VersionValue.V1_0) status: Mapped[str] = mapped_column(String(64), default=StatusValue.POLICY_DRAFT, index=True) effective_date: Mapped[date | None] = mapped_column(Date, nullable=True) feishu_doc_url: Mapped[str | None] = mapped_column(String(1024), nullable=True) summary: Mapped[str | None] = mapped_column(Text, nullable=True) class Standard(Base, TimestampMixin): __tablename__ = "standards" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) title: Mapped[str] = mapped_column(String(255), index=True) standard_type: Mapped[str | None] = mapped_column(String(128), nullable=True) applies_to: Mapped[str | None] = mapped_column(String(255), nullable=True) status: Mapped[str] = mapped_column(String(64), default=StatusValue.VALID, index=True) check_items: Mapped[str | None] = mapped_column(Text, nullable=True) remediation: Mapped[str | None] = mapped_column(Text, nullable=True) policy_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) class PerformanceMetric(Base, TimestampMixin): __tablename__ = "performance_metrics" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) name: Mapped[str] = mapped_column(String(255), index=True) applies_to_role: Mapped[str | None] = mapped_column(String(128), nullable=True) formula: Mapped[str | None] = mapped_column(Text, nullable=True) weight: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=0) data_source: Mapped[str | None] = mapped_column(String(255), nullable=True) auto_score: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=0) confirmed_score: Mapped[Decimal | None] = mapped_column(Numeric(8, 2), nullable=True) status: Mapped[str] = mapped_column(String(64), default=StatusValue.DRAFT, index=True) class Supplier(Base, TimestampMixin): __tablename__ = "suppliers" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) name: Mapped[str] = mapped_column(String(255), index=True) category: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) contact: Mapped[str | None] = mapped_column(String(128), nullable=True) quality_score: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=0) delivery_score: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=0) price_score: Mapped[Decimal] = mapped_column(Numeric(5, 2), default=0) risk_level: Mapped[str] = mapped_column(String(32), default=RiskLevel.LOW, index=True) blacklist_status: Mapped[str] = mapped_column( String(32), default=StatusValue.NORMAL, index=True, ) class AttendanceRecord(Base, TimestampMixin): __tablename__ = "attendance_records" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) employee_name: Mapped[str] = mapped_column(String(128), index=True) employee_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) department: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) project_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) work_date: Mapped[date] = mapped_column(Date, index=True) check_in_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) check_out_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) status: Mapped[str] = mapped_column(String(64), default=StatusValue.NORMAL_CN, index=True) location: Mapped[str | None] = mapped_column(String(255), nullable=True) source_system: Mapped[str] = mapped_column(String(64), default=SourceSystem.INTERNAL) external_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) note: Mapped[str | None] = mapped_column(Text, nullable=True) class WorkReport(Base, TimestampMixin): __tablename__ = "work_reports" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) report_type: Mapped[str] = mapped_column(String(32), index=True) title: Mapped[str] = mapped_column(String(255), index=True) reporter: Mapped[str] = mapped_column(String(128), default=ActorValue.SYSTEM, index=True) department: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) project_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) period_start: Mapped[date] = mapped_column(Date, index=True) period_end: Mapped[date] = mapped_column(Date, index=True) content: Mapped[str] = mapped_column(Text) metrics: Mapped[dict | None] = mapped_column(JSON, nullable=True) risk_summary: Mapped[dict | None] = mapped_column(JSON, nullable=True) status: Mapped[str] = mapped_column(String(64), default=StatusValue.GENERATED, index=True) source_system: Mapped[str] = mapped_column(String(64), default=SourceSystem.INTERNAL) class RiskEvent(Base, TimestampMixin): __tablename__ = "risk_events" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) title: Mapped[str] = mapped_column(String(255), index=True) risk_type: Mapped[str] = mapped_column(String(64), index=True) risk_level: Mapped[str] = mapped_column(String(32), default=RiskLevel.MEDIUM, index=True) status: Mapped[str] = mapped_column(String(32), default=StatusValue.OPEN, index=True) source_domain: Mapped[str] = mapped_column(String(128), index=True) source_record_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) project_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) owner: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) detected_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) assigned_to: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) resolved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) closed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) closed_reason: Mapped[str | None] = mapped_column(Text, nullable=True) review_summary: Mapped[str | None] = mapped_column(Text, nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) mitigation: Mapped[str | None] = mapped_column(Text, nullable=True) evidence: Mapped[dict | None] = mapped_column(JSON, nullable=True) class RiskEventAction(Base): __tablename__ = "risk_event_actions" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) risk_event_id: Mapped[int] = mapped_column(Integer, index=True) action: Mapped[str] = mapped_column(String(64), index=True) actor: Mapped[str] = mapped_column(String(128), default=ActorValue.SYSTEM, index=True) from_status: Mapped[str | None] = mapped_column(String(32), nullable=True) to_status: Mapped[str | None] = mapped_column(String(32), nullable=True) assigned_to: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) comment: Mapped[str | None] = mapped_column(Text, nullable=True) payload: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) class LegacySyncRun(Base, TimestampMixin): __tablename__ = "legacy_sync_runs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) domain: Mapped[str] = mapped_column(String(128), index=True) source_table: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) status: Mapped[str] = mapped_column(String(32), default=StatusValue.RUNNING, index=True) started_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_count: Mapped[int] = mapped_column(Integer, default=0) updated_count: Mapped[int] = mapped_column(Integer, default=0) skipped_count: Mapped[int] = mapped_column(Integer, default=0) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) note: Mapped[str | None] = mapped_column(Text, nullable=True) class ReportPushRun(Base, TimestampMixin): __tablename__ = "report_push_runs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(String(64), unique=True, index=True) report_type: Mapped[str] = mapped_column(String(64), index=True) title: Mapped[str | None] = mapped_column(String(255), nullable=True) receive_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) receive_id_type: Mapped[str] = mapped_column(String(64), index=True) status: Mapped[str] = mapped_column(String(32), index=True) task_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True) actor: Mapped[str] = mapped_column(String(128), default=ActorValue.SYSTEM, index=True) provider_response: Mapped[dict | None] = mapped_column(JSON, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) queued_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now, index=True) sent_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)