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>
215 lines
6.2 KiB
Python
215 lines
6.2 KiB
Python
"""
|
|
建立所有資料庫觸發器
|
|
根據 HR Portal 系統規格表 v3.0
|
|
"""
|
|
import psycopg2
|
|
|
|
conn = psycopg2.connect(
|
|
host="10.1.0.20",
|
|
port=5433,
|
|
database="hr_portal",
|
|
user="admin",
|
|
password="DC1qaz2wsx"
|
|
)
|
|
conn.autocommit = False
|
|
cur = conn.cursor()
|
|
|
|
try:
|
|
print("=" * 60)
|
|
print("建立所有資料庫觸發器")
|
|
print("=" * 60)
|
|
|
|
# ========================================
|
|
# 1. trigger_tenant_departments_seq_no
|
|
# ========================================
|
|
print("\n[1/6] 建立 trigger_tenant_departments_seq_no...")
|
|
|
|
# 函數:自動生成部門 seq_no
|
|
cur.execute("""
|
|
CREATE OR REPLACE FUNCTION fn_auto_seq_no_tenant_departments()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
next_seq INTEGER;
|
|
BEGIN
|
|
-- 如果 seq_no 未提供,自動生成
|
|
IF NEW.seq_no IS NULL THEN
|
|
SELECT COALESCE(MAX(seq_no), 0) + 1
|
|
INTO next_seq
|
|
FROM tenant_departments
|
|
WHERE tenant_id = NEW.tenant_id;
|
|
|
|
NEW.seq_no := next_seq;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
""")
|
|
|
|
# 觸發器
|
|
cur.execute("""
|
|
DROP TRIGGER IF EXISTS trigger_tenant_departments_seq_no ON tenant_departments;
|
|
""")
|
|
|
|
cur.execute("""
|
|
CREATE TRIGGER trigger_tenant_departments_seq_no
|
|
BEFORE INSERT ON tenant_departments
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION fn_auto_seq_no_tenant_departments();
|
|
""")
|
|
|
|
print(" [OK] trigger_tenant_departments_seq_no created")
|
|
|
|
# ========================================
|
|
# 2. trigger_tenant_employees_seq_no (SKIPPED - table not exists)
|
|
# ========================================
|
|
print("\n[2/6] SKIP trigger_tenant_employees_seq_no (table not exists)")
|
|
|
|
# ========================================
|
|
# 3. trigger_tenant_user_roles_seq_no
|
|
# ========================================
|
|
print("\n[3/6] 建立 trigger_tenant_user_roles_seq_no...")
|
|
|
|
cur.execute("""
|
|
CREATE OR REPLACE FUNCTION fn_auto_seq_no_tenant_user_roles()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
next_seq INTEGER;
|
|
BEGIN
|
|
IF NEW.seq_no IS NULL THEN
|
|
SELECT COALESCE(MAX(seq_no), 0) + 1
|
|
INTO next_seq
|
|
FROM tenant_user_roles
|
|
WHERE tenant_id = NEW.tenant_id;
|
|
|
|
NEW.seq_no := next_seq;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
""")
|
|
|
|
cur.execute("""
|
|
DROP TRIGGER IF EXISTS trigger_tenant_user_roles_seq_no ON tenant_user_roles;
|
|
""")
|
|
|
|
cur.execute("""
|
|
CREATE TRIGGER trigger_tenant_user_roles_seq_no
|
|
BEFORE INSERT ON tenant_user_roles
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION fn_auto_seq_no_tenant_user_roles();
|
|
""")
|
|
|
|
print(" [OK] trigger_tenant_user_roles_seq_no created")
|
|
|
|
# ========================================
|
|
# 4. trigger_tenant_emp_resumes_seq_no
|
|
# ========================================
|
|
print("\n[4/6] 建立 trigger_tenant_emp_resumes_seq_no...")
|
|
|
|
cur.execute("""
|
|
CREATE OR REPLACE FUNCTION fn_auto_seq_no_tenant_emp_resumes()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
next_seq INTEGER;
|
|
BEGIN
|
|
IF NEW.seq_no IS NULL THEN
|
|
SELECT COALESCE(MAX(seq_no), 0) + 1
|
|
INTO next_seq
|
|
FROM tenant_emp_resumes
|
|
WHERE tenant_id = NEW.tenant_id;
|
|
|
|
NEW.seq_no := next_seq;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
""")
|
|
|
|
cur.execute("""
|
|
DROP TRIGGER IF EXISTS trigger_tenant_emp_resumes_seq_no ON tenant_emp_resumes;
|
|
""")
|
|
|
|
cur.execute("""
|
|
CREATE TRIGGER trigger_tenant_emp_resumes_seq_no
|
|
BEFORE INSERT ON tenant_emp_resumes
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION fn_auto_seq_no_tenant_emp_resumes();
|
|
""")
|
|
|
|
print(" [OK] trigger_tenant_emp_resumes_seq_no created")
|
|
|
|
# ========================================
|
|
# 5&6. trigger_tenant_emp_settings_auto_fields (合併 seq_no + emp_code)
|
|
# ========================================
|
|
print("\n[5&6] 建立 trigger_tenant_emp_settings_auto_fields...")
|
|
|
|
# 合併函數:同時處理 seq_no 和 emp_code
|
|
cur.execute("""
|
|
CREATE OR REPLACE FUNCTION fn_auto_fields_tenant_emp_settings()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
next_seq INTEGER;
|
|
tenant_prefix VARCHAR(10);
|
|
BEGIN
|
|
-- 步驟 1: 自動生成 seq_no
|
|
IF NEW.seq_no IS NULL THEN
|
|
SELECT COALESCE(MAX(seq_no), 0) + 1
|
|
INTO next_seq
|
|
FROM tenant_emp_settings
|
|
WHERE tenant_id = NEW.tenant_id;
|
|
|
|
NEW.seq_no := next_seq;
|
|
END IF;
|
|
|
|
-- 步驟 2: 自動生成 tenant_emp_code
|
|
IF NEW.tenant_emp_code IS NULL OR NEW.tenant_emp_code = '' THEN
|
|
-- 從 tenants 表取得 prefix
|
|
SELECT prefix INTO tenant_prefix
|
|
FROM tenants
|
|
WHERE id = NEW.tenant_id;
|
|
|
|
-- 生成員工工號: prefix + LPAD(seq_no, 4, '0')
|
|
-- 例如: PWD + 0001 = PWD0001
|
|
NEW.tenant_emp_code := tenant_prefix || LPAD(NEW.seq_no::TEXT, 4, '0');
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
""")
|
|
|
|
# 刪除舊觸發器
|
|
cur.execute("""
|
|
DROP TRIGGER IF EXISTS trigger_tenant_emp_settings_seq_no ON tenant_emp_settings;
|
|
DROP TRIGGER IF EXISTS trigger_generate_emp_code ON tenant_emp_settings;
|
|
""")
|
|
|
|
# 建立新觸發器
|
|
cur.execute("""
|
|
CREATE TRIGGER trigger_tenant_emp_settings_auto_fields
|
|
BEFORE INSERT ON tenant_emp_settings
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION fn_auto_fields_tenant_emp_settings();
|
|
""")
|
|
|
|
print(" [OK] trigger_tenant_emp_settings_auto_fields created")
|
|
|
|
# 提交所有變更
|
|
conn.commit()
|
|
|
|
print("\n" + "=" * 60)
|
|
print("所有觸發器建立完成!")
|
|
print("=" * 60)
|
|
|
|
except Exception as e:
|
|
conn.rollback()
|
|
print(f"\nERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
finally:
|
|
cur.close()
|
|
conn.close()
|