Prepare ERP source for Gitea deployment

This commit is contained in:
A R R R Associates
2026-06-20 15:01:44 +05:30
commit 5c75eb6bd9
450 changed files with 67698 additions and 0 deletions
+660
View File
@@ -0,0 +1,660 @@
from __future__ import annotations
from datetime import date, datetime, time, timezone
from sqlalchemy import Boolean, Date, DateTime, Float, ForeignKey, Integer, String, Text, Time, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.db.common import CommonBase
class Employee(CommonBase):
"""Tenant and branch aware employee master.
This is the v2 Employee Core foundation migrated from the older HRMS module.
It intentionally keeps attendance, leave, payroll, documents and ESS out of
this table so those features can be added safely in later phases.
"""
__tablename__ = "employees"
__table_args__ = (
UniqueConstraint("tenant_id", "employee_code", name="uq_employees_tenant_code"),
UniqueConstraint("tenant_id", "user_id", name="uq_employees_tenant_user"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
employee_code: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
full_name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
email: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True)
mobile: Mapped[str | None] = mapped_column(String(20), nullable=True)
alternate_mobile: Mapped[str | None] = mapped_column(String(20), nullable=True)
date_of_joining: Mapped[date | None] = mapped_column(Date, nullable=True)
date_of_leaving: Mapped[date | None] = mapped_column(Date, nullable=True)
employment_type: Mapped[str] = mapped_column(String(50), nullable=False, default="full_time", index=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="active", index=True)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, index=True)
department: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
designation: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
reporting_manager_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
pan: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
uan: Mapped[str | None] = mapped_column(String(30), nullable=True)
esi_no: Mapped[str | None] = mapped_column(String(30), nullable=True)
pf_no: Mapped[str | None] = mapped_column(String(30), nullable=True)
aadhaar_last4: Mapped[str | None] = mapped_column(String(4), nullable=True)
bank_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
bank_account_no: Mapped[str | None] = mapped_column(String(40), nullable=True)
bank_ifsc: Mapped[str | None] = mapped_column(String(20), nullable=True)
address: Mapped[str | None] = mapped_column(Text, nullable=True)
emergency_contact_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
emergency_contact_mobile: Mapped[str | None] = mapped_column(String(20), nullable=True)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
user = relationship("User", foreign_keys=[user_id])
reporting_manager = relationship("User", foreign_keys=[reporting_manager_user_id])
class EmployeeRegistrationRequest(CommonBase):
"""Employee self-registration / linkage request for ESS onboarding.
This table is deliberately separate from employees so a logged-in user can
request an employee profile without immediately creating an employee master.
Firm Admin / Partner / Branch Manager can review and approve it.
"""
__tablename__ = "employee_registration_requests"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
requested_employee_code: Mapped[str | None] = mapped_column(String(50), nullable=True, index=True)
full_name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
email: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True)
mobile: Mapped[str | None] = mapped_column(String(20), nullable=True)
department: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
designation: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
date_of_joining: Mapped[date | None] = mapped_column(Date, nullable=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
reviewed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
reviewed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_employee_id: Mapped[int | None] = mapped_column(ForeignKey("employees.id", ondelete="SET NULL"), nullable=True, index=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
user = relationship("User", foreign_keys=[user_id])
reviewed_by = relationship("User", foreign_keys=[reviewed_by_user_id])
created_employee = relationship("Employee", foreign_keys=[created_employee_id])
class EmployeeAttendance(CommonBase):
"""Daily attendance records for employee self-service and admin review.
One row is maintained per employee per attendance date. Phase 6C keeps the
model deliberately simple and tenant/branch-safe. Geo/IP validation can be
added later without changing the employee master.
"""
__tablename__ = "employee_attendance"
__table_args__ = (
UniqueConstraint("tenant_id", "employee_id", "attendance_date", name="uq_employee_attendance_employee_date"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
attendance_date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
punch_in_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
punch_out_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
punch_in_local_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=False), nullable=True)
punch_out_local_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=False), nullable=True)
branch_timezone: Mapped[str] = mapped_column(String(64), nullable=False, default="Asia/Kolkata")
scheduled_start_local: Mapped[time | None] = mapped_column(Time, nullable=True)
scheduled_end_local: Mapped[time | None] = mapped_column(Time, nullable=True)
late_by_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
attendance_rule_status: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True)
is_weekly_off: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
work_duration_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="present", index=True)
approval_status: Mapped[str] = mapped_column(String(30), nullable=False, default="approved", index=True)
source: Mapped[str] = mapped_column(String(30), nullable=False, default="self_punch", index=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
punch_in_latitude: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_in_longitude: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_in_accuracy_meters: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_in_distance_meters: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_in_ip: Mapped[str | None] = mapped_column(String(80), nullable=True)
punch_in_geo_status: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True)
punch_in_ip_status: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True)
punch_out_latitude: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_out_longitude: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_out_accuracy_meters: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_out_distance_meters: Mapped[float | None] = mapped_column(Float, nullable=True)
punch_out_ip: Mapped[str | None] = mapped_column(String(80), nullable=True)
punch_out_geo_status: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True)
punch_out_ip_status: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True)
reviewed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
reviewed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
employee = relationship("Employee", foreign_keys=[employee_id])
user = relationship("User", foreign_keys=[user_id])
reviewed_by = relationship("User", foreign_keys=[reviewed_by_user_id])
class EmployeeLeaveType(CommonBase):
"""Tenant/branch aware leave type master for Phase 6D."""
__tablename__ = "employee_leave_types"
__table_args__ = (
UniqueConstraint("tenant_id", "branch_id", "code", name="uq_employee_leave_types_tenant_branch_code"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
code: Mapped[str] = mapped_column(String(30), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
annual_quota_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
carry_forward_allowed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
allow_negative_balance: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
requires_approval: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
is_paid: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, index=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
class EmployeeLeaveBalance(CommonBase):
"""Leave balance per employee and leave type."""
__tablename__ = "employee_leave_balances"
__table_args__ = (
UniqueConstraint("tenant_id", "employee_id", "leave_type_id", name="uq_employee_leave_balances_employee_type"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
leave_type_id: Mapped[int] = mapped_column(ForeignKey("employee_leave_types.id", ondelete="CASCADE"), nullable=False, index=True)
opening_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
credited_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
availed_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
adjusted_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
balance_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
employee = relationship("Employee", foreign_keys=[employee_id])
leave_type = relationship("EmployeeLeaveType", foreign_keys=[leave_type_id])
class EmployeeLeaveRequest(CommonBase):
"""Employee leave request, review and approval workflow."""
__tablename__ = "employee_leave_requests"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
leave_type_id: Mapped[int] = mapped_column(ForeignKey("employee_leave_types.id", ondelete="RESTRICT"), nullable=False, index=True)
from_date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
to_date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
days: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
request_source: Mapped[str] = mapped_column(String(30), nullable=False, default="employee_portal", index=True)
reviewed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
reviewed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
employee = relationship("Employee", foreign_keys=[employee_id])
user = relationship("User", foreign_keys=[user_id])
leave_type = relationship("EmployeeLeaveType", foreign_keys=[leave_type_id])
reviewed_by = relationship("User", foreign_keys=[reviewed_by_user_id])
class EmployeeDocumentType(CommonBase):
"""Tenant/branch aware employee document type master for Phase 6E."""
__tablename__ = "employee_document_types"
__table_args__ = (
UniqueConstraint("tenant_id", "branch_id", "code", name="uq_employee_document_types_tenant_branch_code"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
code: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(150), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
is_mandatory: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
allow_employee_upload: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
requires_verification: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, index=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
class EmployeeDocument(CommonBase):
"""Employee document metadata.
Files are stored on disk under data/uploads/employee_documents. The DB keeps
only controlled metadata and the relative file path so a later storage
backend such as Nextcloud/local office storage can be introduced safely.
"""
__tablename__ = "employee_documents"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
document_type_id: Mapped[int | None] = mapped_column(ForeignKey("employee_document_types.id", ondelete="SET NULL"), nullable=True, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
document_no: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
issue_date: Mapped[date | None] = mapped_column(Date, nullable=True)
expiry_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
original_filename: Mapped[str] = mapped_column(String(255), nullable=False)
stored_filename: Mapped[str] = mapped_column(String(255), nullable=False)
storage_path: Mapped[str] = mapped_column(String(500), nullable=False)
content_type: Mapped[str | None] = mapped_column(String(150), nullable=True)
file_size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="uploaded", index=True)
visibility: Mapped[str] = mapped_column(String(30), nullable=False, default="employee_and_hr", index=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
verified_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
verified_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
verification_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
uploaded_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
employee = relationship("Employee", foreign_keys=[employee_id])
document_type = relationship("EmployeeDocumentType", foreign_keys=[document_type_id])
verified_by = relationship("User", foreign_keys=[verified_by_user_id])
uploaded_by = relationship("User", foreign_keys=[uploaded_by_user_id])
class EmployeeOnboardingChecklistItem(CommonBase):
"""Reusable tenant/branch onboarding checklist master."""
__tablename__ = "employee_onboarding_checklist_items"
__table_args__ = (
UniqueConstraint("tenant_id", "branch_id", "code", name="uq_employee_onboarding_items_tenant_branch_code"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
code: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
stage: Mapped[str] = mapped_column(String(50), nullable=False, default="joining", index=True)
default_due_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
is_mandatory: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, index=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
class EmployeeOnboardingTask(CommonBase):
"""Employee-specific onboarding checklist task."""
__tablename__ = "employee_onboarding_tasks"
__table_args__ = (
UniqueConstraint("tenant_id", "employee_id", "checklist_item_id", name="uq_employee_onboarding_task_employee_item"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
checklist_item_id: Mapped[int | None] = mapped_column(ForeignKey("employee_onboarding_checklist_items.id", ondelete="SET NULL"), nullable=True, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
stage: Mapped[str] = mapped_column(String(50), nullable=False, default="joining", index=True)
due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
assigned_to_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
completed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
completed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
employee = relationship("Employee", foreign_keys=[employee_id])
checklist_item = relationship("EmployeeOnboardingChecklistItem", foreign_keys=[checklist_item_id])
assigned_to = relationship("User", foreign_keys=[assigned_to_user_id])
completed_by = relationship("User", foreign_keys=[completed_by_user_id])
class EmployeeOffboardingRequest(CommonBase):
"""Employee resignation/relieving/offboarding workflow request."""
__tablename__ = "employee_offboarding_requests"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
request_type: Mapped[str] = mapped_column(String(50), nullable=False, default="resignation", index=True)
requested_relieving_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
approved_relieving_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
handover_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
requested_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
reviewed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
reviewed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
completed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
completed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
employee = relationship("Employee", foreign_keys=[employee_id])
requested_by = relationship("User", foreign_keys=[requested_by_user_id])
reviewed_by = relationship("User", foreign_keys=[reviewed_by_user_id])
completed_by = relationship("User", foreign_keys=[completed_by_user_id])
class EmployeeOffboardingTask(CommonBase):
"""Offboarding checklist task linked to an offboarding request."""
__tablename__ = "employee_offboarding_tasks"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
request_id: Mapped[int] = mapped_column(ForeignKey("employee_offboarding_requests.id", ondelete="CASCADE"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
due_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
assigned_to_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
completed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
completed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False
)
updated_at_utc: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
nullable=False,
)
request = relationship("EmployeeOffboardingRequest", foreign_keys=[request_id])
employee = relationship("Employee", foreign_keys=[employee_id])
assigned_to = relationship("User", foreign_keys=[assigned_to_user_id])
completed_by = relationship("User", foreign_keys=[completed_by_user_id])
class EmployeeSalaryStructure(CommonBase):
"""Employee salary structure header for Phase 6G payroll foundation."""
__tablename__ = "employee_salary_structures"
__table_args__ = (
UniqueConstraint("tenant_id", "employee_id", "effective_from", name="uq_employee_salary_structure_effective"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
effective_from: Mapped[date] = mapped_column(Date, nullable=False, index=True)
effective_to: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
pay_cycle: Mapped[str] = mapped_column(String(30), nullable=False, default="monthly", index=True)
monthly_ctc_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
basic_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
hra_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
allowance_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
employee_pf_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
employee_esi_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
professional_tax_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
tds_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
other_deduction_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, index=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
updated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
employee = relationship("Employee", foreign_keys=[employee_id])
class EmployeePayrollRun(CommonBase):
"""Monthly payroll run header."""
__tablename__ = "employee_payroll_runs"
__table_args__ = (
UniqueConstraint("tenant_id", "branch_id", "pay_year", "pay_month", name="uq_employee_payroll_run_period"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
pay_year: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
pay_month: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
run_name: Mapped[str] = mapped_column(String(150), nullable=False)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="draft", index=True)
total_employees: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
gross_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
deduction_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
net_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
processed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
approved_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
approved_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
paid_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
paid_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
processed_by = relationship("User", foreign_keys=[processed_by_user_id])
approved_by = relationship("User", foreign_keys=[approved_by_user_id])
paid_by = relationship("User", foreign_keys=[paid_by_user_id])
class EmployeePayslip(CommonBase):
"""Employee payslip generated from an approved salary structure."""
__tablename__ = "employee_payslips"
__table_args__ = (
UniqueConstraint("tenant_id", "payroll_run_id", "employee_id", name="uq_employee_payslip_run_employee"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
branch_id: Mapped[int] = mapped_column(ForeignKey("branches.id"), nullable=False, index=True)
payroll_run_id: Mapped[int] = mapped_column(ForeignKey("employee_payroll_runs.id", ondelete="CASCADE"), nullable=False, index=True)
employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id", ondelete="CASCADE"), nullable=False, index=True)
salary_structure_id: Mapped[int | None] = mapped_column(ForeignKey("employee_salary_structures.id", ondelete="SET NULL"), nullable=True, index=True)
pay_year: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
pay_month: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
basic_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
hra_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
allowance_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
gross_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
employee_pf_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
employee_esi_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
professional_tax_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
tds_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
other_deduction_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
deduction_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
net_amount: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="generated", index=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
generated_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
created_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at_utc: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
payroll_run = relationship("EmployeePayrollRun", foreign_keys=[payroll_run_id])
employee = relationship("Employee", foreign_keys=[employee_id])
salary_structure = relationship("EmployeeSalaryStructure", foreign_keys=[salary_structure_id])
generated_by = relationship("User", foreign_keys=[generated_by_user_id])