Files
hr-portal/backend/test_tenant_sequence.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

89 lines
2.6 KiB
Python

"""
測試租戶內序號自動生成
展示每個租戶的資料都從 1 開始編號
"""
from app.db.session import get_db
from app.models.employee import Employee
from app.models.tenant import Tenant
from sqlalchemy import text
def test_tenant_sequences():
db = next(get_db())
print("=" * 80)
print("租戶內序號測試")
print("=" * 80)
# 1. 查看現有資料的序號分佈
print("\n【現有員工資料】")
employees = db.query(Employee).order_by(Employee.tenant_id, Employee.seq_no).all()
current_tenant = None
for emp in employees:
if emp.tenant_id != current_tenant:
current_tenant = emp.tenant_id
tenant = db.query(Tenant).filter(Tenant.id == emp.tenant_id).first()
print(f"\n租戶 {emp.tenant_id} ({tenant.tenant_code if tenant else 'N/A'}):")
print(f" seq_no={emp.seq_no:3d} | employee_id={emp.employee_id:8s} | {emp.legal_name}")
# 2. 測試新增員工時序號自動生成
print("\n" + "=" * 80)
print("【測試新增員工 - 序號自動生成】")
print("=" * 80)
# 查詢租戶 1 的最大序號
max_seq = db.query(text("MAX(seq_no)")).select_from(Employee).filter(
Employee.tenant_id == 1
).scalar() or 0
print(f"\n租戶 1 當前最大序號: {max_seq}")
print(f"預期下一個序號: {max_seq + 1}")
# 3. 展示跨租戶序號獨立性
print("\n" + "=" * 80)
print("【租戶序號獨立性】")
print("=" * 80)
result = db.execute(text("""
SELECT
tenant_id,
COUNT(*) as total,
MIN(seq_no) as min_seq,
MAX(seq_no) as max_seq
FROM org_employees
GROUP BY tenant_id
ORDER BY tenant_id
"""))
print("\n租戶ID | 員工數 | 最小序號 | 最大序號")
print("-" * 50)
for row in result:
print(f"{row.tenant_id:7d} | {row.total:6d} | {row.min_seq:8d} | {row.max_seq:8d}")
# 4. 展示觸發器運作
print("\n" + "=" * 80)
print("【觸發器狀態】")
print("=" * 80)
triggers = db.execute(text("""
SELECT
trigger_name,
event_manipulation,
event_object_table
FROM information_schema.triggers
WHERE trigger_schema = 'public'
AND trigger_name LIKE '%seq_no%'
ORDER BY event_object_table
"""))
print("\n觸發器名稱".ljust(40), "事件".ljust(10), "目標表")
print("-" * 80)
for row in triggers:
print(f"{row.trigger_name:40s} {row.event_manipulation:10s} {row.event_object_table}")
db.close()
if __name__ == "__main__":
test_tenant_sequences()