Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -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])
|
||||
Reference in New Issue
Block a user