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>
This commit is contained in:
317
database/test_schema.sql
Normal file
317
database/test_schema.sql
Normal file
@@ -0,0 +1,317 @@
|
||||
-- ============================================================================
|
||||
-- HR Portal Database Schema Test Script
|
||||
-- 用途: 驗證資料庫 schema 是否正確創建
|
||||
-- 使用方法: psql -U postgres -d hr_portal -f test_schema.sql
|
||||
-- ============================================================================
|
||||
|
||||
\echo ''
|
||||
\echo '========================================='
|
||||
\echo 'HR Portal Database Schema 測試'
|
||||
\echo '========================================='
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 1. 測試表格是否存在
|
||||
-- ============================================================================
|
||||
\echo '1. 檢查表格是否存在...'
|
||||
\echo ''
|
||||
|
||||
SELECT
|
||||
table_name,
|
||||
CASE
|
||||
WHEN table_name IN ('employees', 'business_units', 'departments',
|
||||
'employee_identities', 'network_drives', 'audit_logs')
|
||||
THEN '✓ 存在'
|
||||
ELSE '✗ 不存在'
|
||||
END AS status
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_type = 'BASE TABLE'
|
||||
ORDER BY table_name;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. 測試視圖是否存在
|
||||
-- ============================================================================
|
||||
\echo '2. 檢查視圖是否存在...'
|
||||
\echo ''
|
||||
|
||||
SELECT
|
||||
table_name AS view_name,
|
||||
'✓ 存在' AS status
|
||||
FROM information_schema.views
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. 測試初始資料是否存在
|
||||
-- ============================================================================
|
||||
\echo '3. 檢查初始資料...'
|
||||
\echo ''
|
||||
|
||||
\echo '事業部資料:'
|
||||
SELECT id, name, code, email_domain FROM business_units ORDER BY id;
|
||||
|
||||
\echo ''
|
||||
\echo '部門資料:'
|
||||
SELECT id, business_unit_id, name, code FROM departments ORDER BY id;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 4. 測試外鍵約束
|
||||
-- ============================================================================
|
||||
\echo '4. 檢查外鍵約束...'
|
||||
\echo ''
|
||||
|
||||
SELECT
|
||||
tc.table_name,
|
||||
kcu.column_name,
|
||||
ccu.table_name AS foreign_table_name,
|
||||
ccu.column_name AS foreign_column_name
|
||||
FROM information_schema.table_constraints AS tc
|
||||
JOIN information_schema.key_column_usage AS kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
JOIN information_schema.constraint_column_usage AS ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
AND ccu.table_schema = tc.table_schema
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
AND tc.table_schema = 'public'
|
||||
ORDER BY tc.table_name, kcu.column_name;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 5. 測試唯一約束
|
||||
-- ============================================================================
|
||||
\echo '5. 檢查唯一約束...'
|
||||
\echo ''
|
||||
|
||||
SELECT
|
||||
tc.table_name,
|
||||
tc.constraint_name,
|
||||
kcu.column_name
|
||||
FROM information_schema.table_constraints AS tc
|
||||
JOIN information_schema.key_column_usage AS kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.constraint_type = 'UNIQUE'
|
||||
AND tc.table_schema = 'public'
|
||||
ORDER BY tc.table_name, kcu.column_name;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 6. 測試索引
|
||||
-- ============================================================================
|
||||
\echo '6. 檢查索引...'
|
||||
\echo ''
|
||||
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
indexname
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY tablename, indexname;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 7. 測試插入員工資料 (模擬真實場景)
|
||||
-- ============================================================================
|
||||
\echo '7. 測試插入員工資料...'
|
||||
\echo ''
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 插入員工
|
||||
INSERT INTO employees (employee_id, username_base, legal_name, english_name, phone, hire_date, status)
|
||||
VALUES ('EMP001', 'porsche.chen', '陳保時', 'Porsche Chen', '02-1234-5678', '2020-01-01', 'active')
|
||||
RETURNING id, employee_id, username_base, legal_name;
|
||||
|
||||
-- 插入第一個身份 (智能發展部 - 資訊部)
|
||||
INSERT INTO employee_identities (
|
||||
employee_id,
|
||||
username,
|
||||
keycloak_id,
|
||||
business_unit_id,
|
||||
department_id,
|
||||
job_title,
|
||||
job_level,
|
||||
is_primary,
|
||||
email_quota_mb,
|
||||
started_at,
|
||||
is_active
|
||||
)
|
||||
VALUES (
|
||||
(SELECT id FROM employees WHERE username_base = 'porsche.chen'),
|
||||
'porsche.chen@lab.taipei',
|
||||
'test-keycloak-uuid-001',
|
||||
(SELECT id FROM business_units WHERE code = 'smart'),
|
||||
(SELECT id FROM departments WHERE code = 'it' AND business_unit_id = (SELECT id FROM business_units WHERE code = 'smart')),
|
||||
'技術總監',
|
||||
'Senior',
|
||||
true,
|
||||
5000,
|
||||
'2020-01-01',
|
||||
true
|
||||
)
|
||||
RETURNING id, username, job_title, is_primary;
|
||||
|
||||
-- 插入第二個身份 (業務發展部 - 顧問部)
|
||||
INSERT INTO employee_identities (
|
||||
employee_id,
|
||||
username,
|
||||
keycloak_id,
|
||||
business_unit_id,
|
||||
department_id,
|
||||
job_title,
|
||||
job_level,
|
||||
is_primary,
|
||||
email_quota_mb,
|
||||
started_at,
|
||||
is_active
|
||||
)
|
||||
VALUES (
|
||||
(SELECT id FROM employees WHERE username_base = 'porsche.chen'),
|
||||
'porsche.chen@ease.taipei',
|
||||
'test-keycloak-uuid-002',
|
||||
(SELECT id FROM business_units WHERE code = 'biz'),
|
||||
(SELECT id FROM departments WHERE code = 'consulting' AND business_unit_id = (SELECT id FROM business_units WHERE code = 'biz')),
|
||||
'資深顧問',
|
||||
'Senior',
|
||||
false,
|
||||
5000,
|
||||
'2021-06-01',
|
||||
true
|
||||
)
|
||||
RETURNING id, username, job_title, is_primary;
|
||||
|
||||
-- 插入 NAS 帳號
|
||||
INSERT INTO network_drives (
|
||||
employee_id,
|
||||
drive_name,
|
||||
quota_gb,
|
||||
webdav_url,
|
||||
smb_url,
|
||||
is_active
|
||||
)
|
||||
VALUES (
|
||||
(SELECT id FROM employees WHERE username_base = 'porsche.chen'),
|
||||
'porsche.chen',
|
||||
200,
|
||||
'https://nas.lab.taipei/webdav/porsche.chen',
|
||||
'\\10.1.0.30\porsche.chen',
|
||||
true
|
||||
)
|
||||
RETURNING id, drive_name, quota_gb;
|
||||
|
||||
\echo ''
|
||||
\echo '測試查詢 v_employee_full_info 視圖:'
|
||||
SELECT
|
||||
emp_no,
|
||||
username_base,
|
||||
legal_name,
|
||||
sso_username,
|
||||
job_title,
|
||||
job_level,
|
||||
business_unit_name,
|
||||
department_name,
|
||||
drive_name,
|
||||
nas_quota_gb
|
||||
FROM v_employee_full_info
|
||||
WHERE username_base = 'porsche.chen'
|
||||
ORDER BY is_primary DESC;
|
||||
|
||||
ROLLBACK;
|
||||
|
||||
\echo ''
|
||||
\echo '✓ 測試資料已回滾 (不影響資料庫)'
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 8. 測試唯一約束 (一個員工在同一事業部只能有一個身份)
|
||||
-- ============================================================================
|
||||
\echo '8. 測試唯一約束 (一個員工在同一事業部只能有一個身份)...'
|
||||
\echo ''
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
-- 嘗試插入重複身份 (應該失敗)
|
||||
INSERT INTO employees (employee_id, username_base, legal_name, hire_date)
|
||||
VALUES ('TEST001', 'test.user', '測試用戶', CURRENT_DATE);
|
||||
|
||||
INSERT INTO employee_identities (
|
||||
employee_id, username, keycloak_id, business_unit_id,
|
||||
job_title, job_level, email_quota_mb, started_at
|
||||
)
|
||||
VALUES (
|
||||
(SELECT id FROM employees WHERE employee_id = 'TEST001'),
|
||||
'test.user@lab.taipei',
|
||||
'test-uuid-1',
|
||||
(SELECT id FROM business_units WHERE code = 'smart'),
|
||||
'測試職位1',
|
||||
'Junior',
|
||||
1000,
|
||||
CURRENT_DATE
|
||||
);
|
||||
|
||||
INSERT INTO employee_identities (
|
||||
employee_id, username, keycloak_id, business_unit_id,
|
||||
job_title, job_level, email_quota_mb, started_at
|
||||
)
|
||||
VALUES (
|
||||
(SELECT id FROM employees WHERE employee_id = 'TEST001'),
|
||||
'test.user@lab.taipei',
|
||||
'test-uuid-2',
|
||||
(SELECT id FROM business_units WHERE code = 'smart'),
|
||||
'測試職位2',
|
||||
'Junior',
|
||||
1000,
|
||||
CURRENT_DATE
|
||||
);
|
||||
|
||||
RAISE NOTICE '✗ 唯一約束測試失敗: 允許插入重複身份';
|
||||
ROLLBACK;
|
||||
EXCEPTION
|
||||
WHEN unique_violation THEN
|
||||
RAISE NOTICE '✓ 唯一約束測試成功: 正確阻止重複身份';
|
||||
ROLLBACK;
|
||||
END;
|
||||
END $$;
|
||||
|
||||
\echo ''
|
||||
|
||||
-- ============================================================================
|
||||
-- 9. 統計資訊
|
||||
-- ============================================================================
|
||||
\echo '9. 資料庫統計資訊...'
|
||||
\echo ''
|
||||
|
||||
SELECT
|
||||
'employees' AS table_name,
|
||||
COUNT(*) AS row_count
|
||||
FROM employees
|
||||
UNION ALL
|
||||
SELECT 'business_units', COUNT(*) FROM business_units
|
||||
UNION ALL
|
||||
SELECT 'departments', COUNT(*) FROM departments
|
||||
UNION ALL
|
||||
SELECT 'employee_identities', COUNT(*) FROM employee_identities
|
||||
UNION ALL
|
||||
SELECT 'network_drives', COUNT(*) FROM network_drives
|
||||
UNION ALL
|
||||
SELECT 'audit_logs', COUNT(*) FROM audit_logs
|
||||
ORDER BY table_name;
|
||||
|
||||
\echo ''
|
||||
\echo '========================================='
|
||||
\echo '測試完成!'
|
||||
\echo '========================================='
|
||||
\echo ''
|
||||
Reference in New Issue
Block a user