feat: add employee management, backend health check, and UI improvements

This commit is contained in:
2026-05-07 11:50:10 +08:00
parent a5db09f41e
commit c00db75c13
59 changed files with 3926 additions and 5796 deletions

View File

@@ -1,5 +1,15 @@
from app.models.approval import ApprovalRecord
from app.models.employee_change_log import EmployeeChangeLog
from app.models.employee import Employee
from app.models.organization import OrganizationUnit
from app.models.reimbursement import ReimbursementRequest
from app.models.role import Role
__all__ = ["ApprovalRecord", "Employee", "ReimbursementRequest"]
__all__ = [
"ApprovalRecord",
"Employee",
"EmployeeChangeLog",
"OrganizationUnit",
"ReimbursementRequest",
"Role",
]

View File

@@ -1,13 +1,20 @@
from __future__ import annotations
import uuid
from datetime import datetime
from datetime import date, datetime
from sqlalchemy import DateTime, String, func
from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, String, Table, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base_class import Base
employee_role_links = Table(
"employee_role_links",
Base.metadata,
Column("employee_id", String(36), ForeignKey("employees.id"), primary_key=True),
Column("role_id", String(36), ForeignKey("roles.id"), primary_key=True),
)
class Employee(Base):
__tablename__ = "employees"
@@ -15,11 +22,37 @@ class Employee(Base):
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
employee_no: Mapped[str] = mapped_column(String(50), unique=True, index=True)
name: Mapped[str] = mapped_column(String(100), index=True)
department: Mapped[str] = mapped_column(String(100), index=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
gender: Mapped[str | None] = mapped_column(String(20), nullable=True)
birth_date: Mapped[date | None] = mapped_column(Date(), nullable=True)
phone: Mapped[str | None] = mapped_column(String(30), nullable=True)
join_date: Mapped[date | None] = mapped_column(Date(), nullable=True)
location: Mapped[str | None] = mapped_column(String(100), nullable=True)
position: Mapped[str] = mapped_column(String(100), default="员工")
grade: Mapped[str] = mapped_column(String(20), default="P3", index=True)
cost_center: Mapped[str | None] = mapped_column(String(50), nullable=True)
finance_owner_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
employment_status: Mapped[str] = mapped_column(String(30), default="在职", index=True)
sync_state: Mapped[str] = mapped_column(String(30), default="已同步")
spotlight: Mapped[bool] = mapped_column(Boolean, default=False)
last_sync_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
organization_unit_id: Mapped[str | None] = mapped_column(
ForeignKey("organization_units.id"), nullable=True, index=True
)
manager_id: Mapped[str | None] = mapped_column(ForeignKey("employees.id"), nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
organization_unit = relationship("OrganizationUnit", back_populates="employees")
manager = relationship("Employee", remote_side=[id], back_populates="reports")
reports = relationship("Employee", back_populates="manager")
roles = relationship("Role", secondary=employee_role_links, back_populates="employees")
change_logs = relationship(
"EmployeeChangeLog",
back_populates="employee",
cascade="all, delete-orphan",
order_by="desc(EmployeeChangeLog.occurred_at)",
)
reimbursement_requests = relationship("ReimbursementRequest", back_populates="employee")

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base_class import Base
class EmployeeChangeLog(Base):
__tablename__ = "employee_change_logs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
employee_id: Mapped[str] = mapped_column(ForeignKey("employees.id"), index=True)
action: Mapped[str] = mapped_column(String(255))
owner: Mapped[str] = mapped_column(String(100))
occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
employee = relationship("Employee", back_populates="change_logs")

View File

@@ -0,0 +1,32 @@
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base_class import Base
class OrganizationUnit(Base):
__tablename__ = "organization_units"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
unit_code: Mapped[str] = mapped_column(String(50), unique=True, index=True)
name: Mapped[str] = mapped_column(String(100), index=True)
unit_type: Mapped[str] = mapped_column(String(30), default="department", index=True)
parent_id: Mapped[str | None] = mapped_column(
ForeignKey("organization_units.id"), nullable=True, index=True
)
cost_center: Mapped[str | None] = mapped_column(String(50), nullable=True)
location: Mapped[str | None] = mapped_column(String(100), nullable=True)
manager_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
parent = relationship("OrganizationUnit", remote_side=[id], back_populates="children")
children = relationship("OrganizationUnit", back_populates="parent")
employees = relationship("Employee", back_populates="organization_unit")

View File

@@ -0,0 +1,24 @@
from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import DateTime, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base_class import Base
class Role(Base):
__tablename__ = "roles"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
role_code: Mapped[str] = mapped_column(String(50), unique=True, index=True)
name: Mapped[str] = mapped_column(String(100), unique=True, index=True)
description: Mapped[str] = mapped_column(String(500), default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
employees = relationship("Employee", secondary="employee_role_links", back_populates="roles")