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>
379 lines
13 KiB
PL/PgSQL
379 lines
13 KiB
PL/PgSQL
-- ====================================
|
|
-- HR Portal Database Schema
|
|
-- ====================================
|
|
-- 創建時間: 2026-02-08
|
|
-- 資料庫: PostgreSQL 16
|
|
-- ====================================
|
|
|
|
-- 設定時區
|
|
SET timezone = 'Asia/Taipei';
|
|
|
|
-- ====================================
|
|
-- 1. 事業部表 (Business Units)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS business_units (
|
|
id SERIAL PRIMARY KEY,
|
|
code VARCHAR(50) UNIQUE NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
name_en VARCHAR(100),
|
|
description TEXT,
|
|
manager_id INTEGER, -- 將在建立 employees 表後設定外鍵
|
|
email_domain VARCHAR(50),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- 初始資料
|
|
INSERT INTO business_units (code, name, name_en, email_domain, description) VALUES
|
|
('wind-energy', '玄鐵風能授權服務事業部', 'Wind Energy Licensing', 'ease.taipei', '風力發電技術授權與風場評估服務'),
|
|
('carbon-credit', '國際碳權申請服務事業部', 'Carbon Credit Services', 'ease.taipei', '碳權申請、碳盤查與碳交易媒合服務'),
|
|
('smart-rd', '智能研發服務事業部', 'Smart R&D Services', 'lab.taipei', 'AI/ML、IoT 與能源管理系統研發'),
|
|
('management', '管理部門', 'Management', 'porscheworld.tw', '人資、財務、行政、資訊等管理部門')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- ====================================
|
|
-- 2. 部門表 (Divisions)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS divisions (
|
|
id SERIAL PRIMARY KEY,
|
|
business_unit_id INTEGER REFERENCES business_units(id) ON DELETE CASCADE,
|
|
code VARCHAR(50) UNIQUE NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
name_en VARCHAR(100),
|
|
manager_id INTEGER, -- 將在建立 employees 表後設定外鍵
|
|
email VARCHAR(100),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- 初始資料 - 玄鐵風能授權服務
|
|
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
|
(1, 'wind-licensing', '技術授權部', 'Technical Licensing', 'wind-licensing@ease.taipei'),
|
|
(1, 'wind-assessment', '風場評估部', 'Wind Assessment', 'wind-assessment@ease.taipei'),
|
|
(1, 'wind-service', '客戶服務部', 'Customer Service', 'wind-service@ease.taipei')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- 國際碳權申請服務
|
|
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
|
(2, 'carbon-apply', '碳權申請部', 'Carbon Application', 'carbon-apply@ease.taipei'),
|
|
(2, 'carbon-audit', '碳盤查部', 'Carbon Audit', 'carbon-audit@ease.taipei'),
|
|
(2, 'carbon-trade', '碳交易部', 'Carbon Trading', 'carbon-trade@ease.taipei')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- 智能研發服務
|
|
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
|
(3, 'software-dev', '軟體研發部', 'Software Development', 'software@lab.taipei'),
|
|
(3, 'hardware-dev', '硬體研發部', 'Hardware Development', 'hardware@lab.taipei'),
|
|
(3, 'product-mgmt', '產品管理部', 'Product Management', 'product@lab.taipei')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- 管理部門
|
|
INSERT INTO divisions (business_unit_id, code, name, name_en, email) VALUES
|
|
(4, 'hr', '人力資源部', 'Human Resources', 'hr@porscheworld.tw'),
|
|
(4, 'finance', '財務部', 'Finance', 'finance@porscheworld.tw'),
|
|
(4, 'admin', '行政部', 'Administration', 'admin@porscheworld.tw'),
|
|
(4, 'it', '資訊部', 'Information Technology', 'it@porscheworld.tw')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- ====================================
|
|
-- 3. 員工表 (Employees)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS employees (
|
|
id SERIAL PRIMARY KEY,
|
|
keycloak_user_id UUID UNIQUE,
|
|
employee_id VARCHAR(20) UNIQUE NOT NULL,
|
|
username VARCHAR(100) UNIQUE NOT NULL,
|
|
|
|
-- 基本資料
|
|
first_name VARCHAR(50) NOT NULL,
|
|
last_name VARCHAR(50) NOT NULL,
|
|
chinese_name VARCHAR(50),
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
phone VARCHAR(20),
|
|
mobile VARCHAR(20),
|
|
|
|
-- 任職資訊
|
|
business_unit_id INTEGER REFERENCES business_units(id),
|
|
division_id INTEGER REFERENCES divisions(id),
|
|
team VARCHAR(100),
|
|
position VARCHAR(100),
|
|
job_title VARCHAR(100),
|
|
job_level VARCHAR(20), -- C-Level, VP, Director, Manager, Senior, Staff, etc.
|
|
employment_type VARCHAR(20) DEFAULT 'full-time', -- full-time, part-time, contractor, intern
|
|
|
|
-- 日期
|
|
hire_date DATE,
|
|
termination_date DATE,
|
|
|
|
-- 狀態
|
|
status VARCHAR(20) DEFAULT 'active', -- active, inactive, suspended, terminated
|
|
|
|
-- 審計
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_by VARCHAR(100),
|
|
updated_by VARCHAR(100)
|
|
);
|
|
|
|
-- 索引
|
|
CREATE INDEX idx_employees_keycloak_id ON employees(keycloak_user_id);
|
|
CREATE INDEX idx_employees_status ON employees(status);
|
|
CREATE INDEX idx_employees_business_unit ON employees(business_unit_id);
|
|
CREATE INDEX idx_employees_division ON employees(division_id);
|
|
CREATE INDEX idx_employees_email ON employees(email);
|
|
|
|
-- 新增外鍵到 business_units 和 divisions
|
|
ALTER TABLE business_units ADD CONSTRAINT fk_business_unit_manager
|
|
FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
|
|
ALTER TABLE divisions ADD CONSTRAINT fk_division_manager
|
|
FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
|
|
-- ====================================
|
|
-- 4. 郵件帳號表 (Email Accounts)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS email_accounts (
|
|
id SERIAL PRIMARY KEY,
|
|
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
|
email_address VARCHAR(255) UNIQUE NOT NULL,
|
|
mailbox_quota_mb INTEGER DEFAULT 1024,
|
|
mailbox_used_mb INTEGER DEFAULT 0,
|
|
|
|
-- 郵件設定
|
|
forward_to VARCHAR(255),
|
|
auto_reply BOOLEAN DEFAULT FALSE,
|
|
auto_reply_message TEXT,
|
|
|
|
-- 狀態
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
-- 審計
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_by VARCHAR(100)
|
|
);
|
|
|
|
CREATE INDEX idx_email_accounts_employee ON email_accounts(employee_id);
|
|
CREATE INDEX idx_email_accounts_email ON email_accounts(email_address);
|
|
|
|
-- ====================================
|
|
-- 5. 網路硬碟表 (Network Drives)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS network_drives (
|
|
id SERIAL PRIMARY KEY,
|
|
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
|
|
|
-- 硬碟資訊
|
|
drive_name VARCHAR(100) NOT NULL,
|
|
drive_path VARCHAR(500) NOT NULL,
|
|
quota_gb INTEGER DEFAULT 10,
|
|
used_gb DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- 存取設定
|
|
webdav_url VARCHAR(500),
|
|
smb_path VARCHAR(500),
|
|
|
|
-- 權限
|
|
can_share BOOLEAN DEFAULT FALSE,
|
|
|
|
-- 狀態
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
-- 審計
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_network_drives_employee ON network_drives(employee_id);
|
|
|
|
-- ====================================
|
|
-- 6. 系統權限表 (System Permissions)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS system_permissions (
|
|
id SERIAL PRIMARY KEY,
|
|
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
|
system_name VARCHAR(100) NOT NULL,
|
|
system_url VARCHAR(500),
|
|
access_level VARCHAR(50), -- admin, user, readonly
|
|
|
|
-- 狀態
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
granted_by VARCHAR(100),
|
|
revoked_at TIMESTAMP,
|
|
revoked_by VARCHAR(100),
|
|
|
|
UNIQUE(employee_id, system_name)
|
|
);
|
|
|
|
CREATE INDEX idx_system_permissions_employee ON system_permissions(employee_id);
|
|
CREATE INDEX idx_system_permissions_system ON system_permissions(system_name);
|
|
|
|
-- ====================================
|
|
-- 7. 專案表 (Projects)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS projects (
|
|
id SERIAL PRIMARY KEY,
|
|
business_unit_id INTEGER REFERENCES business_units(id),
|
|
|
|
project_code VARCHAR(50) UNIQUE NOT NULL,
|
|
project_name VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
client_name VARCHAR(200),
|
|
|
|
-- 專案狀態
|
|
status VARCHAR(20) DEFAULT 'planning', -- planning, active, on-hold, completed, cancelled
|
|
|
|
-- 專案經理
|
|
project_manager_id INTEGER REFERENCES employees(id),
|
|
|
|
-- 時間
|
|
start_date DATE,
|
|
end_date DATE,
|
|
actual_end_date DATE,
|
|
|
|
-- 預算
|
|
budget_amount DECIMAL(15,2),
|
|
budget_currency VARCHAR(10) DEFAULT 'TWD',
|
|
|
|
-- 專案空間
|
|
nas_path VARCHAR(500),
|
|
git_repo VARCHAR(200),
|
|
|
|
-- 審計
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_by VARCHAR(100)
|
|
);
|
|
|
|
CREATE INDEX idx_projects_business_unit ON projects(business_unit_id);
|
|
CREATE INDEX idx_projects_status ON projects(status);
|
|
CREATE INDEX idx_projects_manager ON projects(project_manager_id);
|
|
|
|
-- ====================================
|
|
-- 8. 專案成員表 (Project Members)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS project_members (
|
|
id SERIAL PRIMARY KEY,
|
|
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
|
employee_id INTEGER REFERENCES employees(id) ON DELETE CASCADE,
|
|
|
|
role VARCHAR(50), -- project-manager, developer, consultant, support
|
|
allocation_percentage INTEGER DEFAULT 100, -- 0-100
|
|
|
|
joined_date DATE DEFAULT CURRENT_DATE,
|
|
left_date DATE,
|
|
|
|
UNIQUE(project_id, employee_id)
|
|
);
|
|
|
|
CREATE INDEX idx_project_members_project ON project_members(project_id);
|
|
CREATE INDEX idx_project_members_employee ON project_members(employee_id);
|
|
|
|
-- ====================================
|
|
-- 9. 審計日誌表 (Audit Logs)
|
|
-- ====================================
|
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id SERIAL PRIMARY KEY,
|
|
employee_id INTEGER REFERENCES employees(id),
|
|
|
|
action VARCHAR(50) NOT NULL, -- create, update, delete, login, logout
|
|
resource_type VARCHAR(50), -- employee, email, drive, project
|
|
resource_id INTEGER,
|
|
|
|
old_value JSONB,
|
|
new_value JSONB,
|
|
|
|
ip_address VARCHAR(50),
|
|
user_agent TEXT,
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_audit_logs_employee ON audit_logs(employee_id);
|
|
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
|
CREATE INDEX idx_audit_logs_resource ON audit_logs(resource_type, resource_id);
|
|
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at DESC);
|
|
|
|
-- ====================================
|
|
-- 10. 更新時間觸發器函數
|
|
-- ====================================
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
-- 為需要的表新增觸發器
|
|
CREATE TRIGGER update_business_units_updated_at BEFORE UPDATE ON business_units
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_divisions_updated_at BEFORE UPDATE ON divisions
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_email_accounts_updated_at BEFORE UPDATE ON email_accounts
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_network_drives_updated_at BEFORE UPDATE ON network_drives
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- ====================================
|
|
-- 11. 建立視圖 (Views)
|
|
-- ====================================
|
|
|
|
-- 員工完整資訊視圖
|
|
CREATE OR REPLACE VIEW v_employees_full AS
|
|
SELECT
|
|
e.*,
|
|
bu.name as business_unit_name,
|
|
bu.name_en as business_unit_name_en,
|
|
d.name as division_name,
|
|
d.name_en as division_name_en
|
|
FROM employees e
|
|
LEFT JOIN business_units bu ON e.business_unit_id = bu.id
|
|
LEFT JOIN divisions d ON e.division_id = d.id;
|
|
|
|
-- 專案人力統計視圖
|
|
CREATE OR REPLACE VIEW v_project_stats AS
|
|
SELECT
|
|
p.id,
|
|
p.project_code,
|
|
p.project_name,
|
|
p.status,
|
|
COUNT(pm.id) as member_count,
|
|
SUM(pm.allocation_percentage) as total_allocation
|
|
FROM projects p
|
|
LEFT JOIN project_members pm ON p.id = pm.project_id AND pm.left_date IS NULL
|
|
GROUP BY p.id, p.project_code, p.project_name, p.status;
|
|
|
|
-- 部門人力統計視圖
|
|
CREATE OR REPLACE VIEW v_division_headcount AS
|
|
SELECT
|
|
bu.name as business_unit_name,
|
|
d.name as division_name,
|
|
COUNT(e.id) as employee_count,
|
|
COUNT(CASE WHEN e.status = 'active' THEN 1 END) as active_count
|
|
FROM divisions d
|
|
LEFT JOIN business_units bu ON d.business_unit_id = bu.id
|
|
LEFT JOIN employees e ON e.division_id = d.id
|
|
GROUP BY bu.name, d.name;
|
|
|
|
-- ====================================
|
|
-- 完成
|
|
-- ====================================
|
|
-- 授予權限 (依實際用戶名調整)
|
|
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO hr_user;
|
|
-- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO hr_user;
|
|
|
|
SELECT 'HR Portal Database Schema 初始化完成!' as message;
|