Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
203 lines
10 KiB
Python
203 lines
10 KiB
Python
"""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)
|