Files
hr-portal/backend/app/schemas/employee.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

121 lines
4.0 KiB
Python

"""
員工 Schemas
"""
from datetime import date, datetime
from typing import Optional, List
from pydantic import BaseModel, Field, EmailStr, ConfigDict
from app.schemas.base import BaseSchema, TimestampSchema
from app.models.employee import EmployeeStatus
class EmployeeBase(BaseSchema):
"""員工基礎 Schema"""
username_base: str = Field(..., min_length=3, max_length=50, description="基礎帳號名稱 (全公司唯一)")
legal_name: str = Field(..., min_length=2, max_length=100, description="法定姓名")
english_name: Optional[str] = Field(None, max_length=100, description="英文名稱")
phone: Optional[str] = Field(None, max_length=20, description="電話")
mobile: Optional[str] = Field(None, max_length=20, description="手機")
class EmployeeCreate(EmployeeBase):
"""創建員工 Schema (多層部門架構: department_id 指向任何層部門)"""
hire_date: date = Field(..., description="到職日期")
# 組織資訊 (新多層部門架構)
department_id: Optional[int] = Field(None, description="部門 ID (任何層級,選填)")
job_title: str = Field(..., min_length=2, max_length=100, description="職稱")
email_quota_mb: int = Field(5120, gt=0, description="郵件配額 (MB),預設 5120")
model_config = ConfigDict(
json_schema_extra={
"example": {
"username_base": "porsche.chen",
"legal_name": "陳保時",
"english_name": "Porsche Chen",
"phone": "02-1234-5678",
"mobile": "0912-345-678",
"hire_date": "2020-01-01",
"department_id": 2,
"job_title": "軟體工程師",
"email_quota_mb": 5120
}
}
)
class EmployeeUpdate(BaseSchema):
"""更新員工 Schema"""
legal_name: Optional[str] = Field(None, min_length=2, max_length=100)
english_name: Optional[str] = Field(None, max_length=100)
phone: Optional[str] = Field(None, max_length=20)
mobile: Optional[str] = Field(None, max_length=20)
status: Optional[EmployeeStatus] = None
class EmployeeInDB(EmployeeBase, TimestampSchema):
"""資料庫中的員工 Schema"""
id: int
employee_id: str = Field(..., description="員工編號 (EMP001)")
hire_date: date
status: EmployeeStatus
model_config = ConfigDict(from_attributes=True)
class EmployeeResponse(EmployeeInDB):
"""員工響應 Schema (多部門成員架構)"""
has_network_drive: Optional[bool] = Field(None, description="是否有 NAS 帳號")
department_count: Optional[int] = Field(None, description="所屬部門數量")
model_config = ConfigDict(
from_attributes=True,
json_schema_extra={
"example": {
"id": 1,
"employee_id": "EMP001",
"username_base": "porsche.chen",
"legal_name": "陳保時",
"english_name": "Porsche Chen",
"phone": "02-1234-5678",
"mobile": "0912-345-678",
"hire_date": "2020-01-01",
"status": "active",
"has_network_drive": True,
"department_count": 2,
"created_at": "2020-01-01T00:00:00",
"updated_at": "2020-01-01T00:00:00"
}
}
)
class EmployeeListItem(BaseSchema):
"""員工列表項 Schema (簡化版,多部門成員架構)"""
id: int
employee_id: str
username_base: str
legal_name: str
english_name: Optional[str] = None
status: EmployeeStatus
hire_date: date
# 主要部門資訊 (從 department_memberships 取得)
primary_department: Optional[str] = Field(None, description="主要部門名稱")
primary_job_title: Optional[str] = Field(None, description="職稱")
model_config = ConfigDict(from_attributes=True)
class EmployeeDetail(EmployeeInDB):
"""員工詳情 Schema (包含完整關聯資料)"""
# 將在後續添加 identities 和 network_drive
pass