TorbatYar/backend/services/accounting/app/models/payroll.py
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
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>
2026-07-24 15:26:43 +03:30

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)