Files
hr-portal/backend/app/models/emp_setting.py
Porsche Chen 360533393f feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage
Major Features:
-  Multi-tenant architecture (tenant isolation)
-  Employee CRUD with lifecycle management (onboarding/offboarding)
-  Department tree structure with email domain management
-  Company info management (single-record editing)
-  System functions CRUD (permission management)
-  Email account management (multi-account per employee)
-  Keycloak SSO integration (auth.lab.taipei)
-  Redis session storage (10.1.0.254:6379)
  - Solves Cookie 4KB limitation
  - Cross-system session sharing
  - Sliding expiration (8 hours)
  - Automatic token refresh

Technical Stack:
Backend:
- FastAPI + SQLAlchemy
- PostgreSQL 16 (10.1.0.20:5433)
- Keycloak Admin API integration
- Docker Mailserver integration (SSH)
- Alembic migrations

Frontend:
- Next.js 14 (App Router)
- NextAuth 4 with Keycloak Provider
- Redis session storage (ioredis)
- Tailwind CSS

Infrastructure:
- Redis 7 (10.1.0.254:6379) - Session + Cache
- Keycloak 26.1.0 (auth.lab.taipei)
- Docker Mailserver (10.1.0.254)

Architecture Highlights:
- Session管理由 Keycloak + Redis 統一控制
- 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session
- Token 自動刷新,異質服務整合
- 未來可無縫遷移到雲端

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-23 20:12:43 +08:00

87 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
員工任用設定 Model (員工任用資料檔)
記錄員工的任用資訊、職務、薪資等(與組織任用相關的資料)
使用複合主鍵 (tenant_id, seq_no)
"""
from datetime import datetime, date
from sqlalchemy import Column, Integer, String, Boolean, Date, DateTime, Text, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import relationship
from app.db.base import Base
class EmpSetting(Base):
"""員工任用設定表(複合主鍵)"""
__tablename__ = "tenant_emp_settings"
__table_args__ = (
UniqueConstraint("tenant_id", "tenant_resume_id", name="uq_tenant_resume_setting"),
UniqueConstraint("tenant_id", "tenant_emp_code", name="uq_tenant_emp_code"),
Index("idx_emp_setting_tenant", "tenant_id"),
)
# 複合主鍵
tenant_id = Column(Integer, ForeignKey("tenants.id", ondelete="CASCADE"), primary_key=True,
comment="租戶 ID")
seq_no = Column(Integer, primary_key=True, comment="租戶內序號 (觸發器自動生成)")
# 關聯人員基本檔
tenant_resume_id = Column(Integer, ForeignKey("tenant_emp_resumes.id", ondelete="RESTRICT"), nullable=False,
comment="人員基本檔 ID一個人只有一筆任用設定")
# 員工編號(自動生成)
tenant_emp_code = Column(String(20), nullable=False, index=True,
comment="員工編號(自動生成,格式: prefix + seq_no例如 PWD0001")
# SSO 整合
tenant_keycloak_user_id = Column(String(36), unique=True, nullable=True, index=True,
comment="Keycloak User UUID (唯一 SSO 識別碼,永久不變)")
tenant_keycloak_username = Column(String(100), unique=True, nullable=True,
comment="Keycloak 登入帳號")
# 任用資訊
hire_at = Column(Date, nullable=False, comment="到職日期")
resign_date = Column(Date, nullable=True, comment="離職日期")
job_title = Column(String(100), nullable=True, comment="職稱")
employment_type = Column(String(50), nullable=False, default="full_time",
comment="任用類型: full_time/part_time/contractor/intern")
# 薪資資訊(加密儲存)
salary_amount = Column(Integer, nullable=True, comment="月薪(加密)")
salary_currency = Column(String(10), default="TWD", comment="薪資幣別")
# 主要部門(員工可屬於多個部門,但有一個主要部門)
primary_dept_id = Column(Integer, ForeignKey("tenant_departments.id", ondelete="SET NULL"), nullable=True,
comment="主要部門 ID")
# 個人化服務配額設定
storage_quota_gb = Column(Integer, default=20, nullable=False, comment="儲存配額 (GB) - Drive 使用")
email_quota_mb = Column(Integer, default=5120, nullable=False, comment="郵件配額 (MB) - Email 使用")
# 狀態
employment_status = Column(String(20), default="active", nullable=False,
comment="任用狀態: active/on_leave/resigned/terminated")
# 通用欄位
is_active = Column(Boolean, default=True, nullable=False, comment="是否啟用")
edit_by = Column(String(36), nullable=True, comment="最後編輯者 keycloak_user_id")
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, comment="建立時間")
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False, comment="更新時間")
# 關聯
tenant = relationship("Tenant")
resume = relationship("EmpResume", back_populates="employment_setting")
primary_department = relationship("Department", foreign_keys=[primary_dept_id])
# 關聯:部門歸屬(多對多)- 透過 resume 的 employee 關聯
# department_memberships 在 Employee Model 中定義
# 關聯:角色分配(多對多)- 透過 keycloak_user_id 查詢
# user_role_assignments 在 UserRoleAssignment Model 中定義
# 關聯:個人化服務設定(多對多)- 透過 keycloak_user_id 查詢
# personal_service_settings 在 EmpPersonalServiceSetting Model 中定義
def __repr__(self):
return f"<EmpSetting {self.tenant_emp_code} (tenant_id={self.tenant_id}, seq_no={self.seq_no})>"