"""Phase 5.9 — HCM & Payroll domain models.""" from __future__ import annotations import uuid from datetime import date from decimal import Decimal from sqlalchemy import Boolean, Date, Index, Integer, Numeric, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.models.base import TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin from app.models.types import EmploymentStatus, GUID, PayrollPeriodStatus, PayrollStatus class Department(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "departments" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) parent_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) cost_center_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_department_tenant_code"),) class Position(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "positions" code: Mapped[str] = mapped_column(String(50), nullable=False) title: Mapped[str] = mapped_column(String(255), nullable=False) department_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_position_tenant_code"),) class Employee(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "employees" employee_code: Mapped[str] = mapped_column(String(50), nullable=False) first_name: Mapped[str] = mapped_column(String(100), nullable=False) last_name: Mapped[str] = mapped_column(String(100), nullable=False) department_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) position_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) cost_center_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) hire_date: Mapped[date | None] = mapped_column(Date, nullable=True) employment_status: Mapped[EmploymentStatus] = mapped_column( default=EmploymentStatus.ACTIVE, nullable=False ) base_salary: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False) __table_args__ = ( UniqueConstraint("tenant_id", "employee_code", name="uq_employee_tenant_code"), Index("ix_employees_tenant_dept", "tenant_id", "department_id"), ) class EmploymentContract(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "employment_contracts" employee_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) contract_type: Mapped[str] = mapped_column(String(50), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date | None] = mapped_column(Date, nullable=True) base_salary: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) class SalaryComponent(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "salary_components" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) component_type: Mapped[str] = mapped_column(String(20), nullable=False) is_taxable: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_salary_component_tenant_code"),) class PayrollPeriod(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payroll_periods" name: Mapped[str] = mapped_column(String(100), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) status: Mapped[PayrollPeriodStatus] = mapped_column( default=PayrollPeriodStatus.OPEN, nullable=False ) is_current: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "name", name="uq_payroll_period_tenant_name"),) class Payroll(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payrolls" payroll_period_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) employee_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) gross_salary: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) total_deductions: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False) total_benefits: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False) net_salary: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) employer_cost: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False) status: Mapped[PayrollStatus] = mapped_column(default=PayrollStatus.DRAFT, nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) __table_args__ = ( Index("ix_payroll_period_employee", "tenant_id", "payroll_period_id", "employee_id"), ) class PayrollItem(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payroll_items" payroll_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) component_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) item_type: Mapped[str] = mapped_column(String(20), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) description: Mapped[str | None] = mapped_column(String(255), nullable=True) class Benefit(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "benefits" code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) __table_args__ = (UniqueConstraint("tenant_id", "code", name="uq_benefit_tenant_code"),) class Allowance(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "allowances" employee_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) component_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) effective_date: Mapped[date] = mapped_column(Date, nullable=False) class Deduction(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "deductions" employee_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) component_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) effective_date: Mapped[date] = mapped_column(Date, nullable=False) class EmployeeLoan(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "employee_loans" employee_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) loan_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) remaining_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) monthly_deduction: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) start_date: Mapped[date] = mapped_column(Date, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) class EmployeeAdvance(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "employee_advances" employee_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) remaining_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) advance_date: Mapped[date] = mapped_column(Date, nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) class PayrollAccountingProfile(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payroll_accounting_profiles" name: Mapped[str] = mapped_column(String(255), nullable=False) department_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) salary_expense_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) employer_contribution_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) payable_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) deduction_account_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) class PayrollHistory(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payroll_history" payroll_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) action: Mapped[str] = mapped_column(String(50), nullable=False) actor_user_id: Mapped[str] = mapped_column(String(100), nullable=False) notes: Mapped[str | None] = mapped_column(Text, nullable=True) class PayrollSettlement(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin): __tablename__ = "payroll_settlements" payroll_period_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) settlement_date: Mapped[date] = mapped_column(Date, nullable=False) total_amount: Mapped[Decimal] = mapped_column(Numeric(18, 4), nullable=False) voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)