init
This commit is contained in:
97
scripts/check-db-structure.js
Normal file
97
scripts/check-db-structure.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// 檢查資料庫結構
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const mysql = require('mysql2/promise')
|
||||
|
||||
async function checkDatabaseStructure() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
database: process.env.DB_DATABASE,
|
||||
user: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config)
|
||||
console.log('🔗 連接到 MySQL 資料庫...')
|
||||
|
||||
// 檢查所有表
|
||||
console.log('\n📋 檢查資料庫表結構...')
|
||||
const [tables] = await connection.execute(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = ?
|
||||
`, [config.database])
|
||||
|
||||
console.log(`\n📊 找到 ${tables.length} 個表:`)
|
||||
|
||||
for (const table of tables) {
|
||||
const tableName = table.table_name
|
||||
console.log(`\n🔍 表: ${tableName}`)
|
||||
|
||||
if (!tableName) {
|
||||
console.log(' ⚠️ 表名為空,跳過')
|
||||
continue
|
||||
}
|
||||
|
||||
// 檢查表結構
|
||||
const [columns] = await connection.execute(`
|
||||
SELECT column_name, data_type, is_nullable, column_default, column_key
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = ? AND table_name = ?
|
||||
ORDER BY ordinal_position
|
||||
`, [config.database, tableName])
|
||||
|
||||
console.log(' 欄位:')
|
||||
columns.forEach(col => {
|
||||
const key = col.column_key ? ` (${col.column_key})` : ''
|
||||
const nullable = col.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'
|
||||
console.log(` - ${col.column_name}: ${col.data_type} ${nullable}${key}`)
|
||||
})
|
||||
|
||||
// 檢查外鍵
|
||||
const [foreignKeys] = await connection.execute(`
|
||||
SELECT
|
||||
constraint_name,
|
||||
column_name,
|
||||
referenced_table_name,
|
||||
referenced_column_name
|
||||
FROM information_schema.key_column_usage
|
||||
WHERE table_schema = ?
|
||||
AND table_name = ?
|
||||
AND referenced_table_name IS NOT NULL
|
||||
`, [config.database, tableName])
|
||||
|
||||
if (foreignKeys.length > 0) {
|
||||
console.log(' 外鍵:')
|
||||
foreignKeys.forEach(fk => {
|
||||
console.log(` - ${fk.constraint_name}: ${fk.column_name} -> ${fk.referenced_table_name}.${fk.referenced_column_name}`)
|
||||
})
|
||||
}
|
||||
|
||||
// 檢查索引
|
||||
const [indexes] = await connection.execute(`
|
||||
SELECT index_name, column_name, non_unique
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = ? AND table_name = ?
|
||||
ORDER BY index_name, seq_in_index
|
||||
`, [config.database, tableName])
|
||||
|
||||
if (indexes.length > 0) {
|
||||
console.log(' 索引:')
|
||||
indexes.forEach(idx => {
|
||||
const unique = idx.non_unique === 0 ? 'UNIQUE' : ''
|
||||
console.log(` - ${idx.index_name}: ${idx.column_name} ${unique}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await connection.end()
|
||||
console.log('\n✅ 資料庫結構檢查完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 檢查資料庫結構失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
checkDatabaseStructure()
|
21
scripts/check-env.js
Normal file
21
scripts/check-env.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// 檢查環境變數
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
|
||||
console.log('🔍 檢查環境變數...')
|
||||
console.log('DB_HOST:', process.env.DB_HOST)
|
||||
console.log('DB_PORT:', process.env.DB_PORT)
|
||||
console.log('DB_DATABASE:', process.env.DB_DATABASE)
|
||||
console.log('DB_USERNAME:', process.env.DB_USERNAME)
|
||||
console.log('DB_PASSWORD:', process.env.DB_PASSWORD ? '***已設定***' : '未設定')
|
||||
|
||||
if (!process.env.DB_HOST || !process.env.DB_PORT || !process.env.DB_DATABASE || !process.env.DB_USERNAME || !process.env.DB_PASSWORD) {
|
||||
console.log('❌ 環境變數未完整設定')
|
||||
console.log('💡 請創建 .env.local 檔案並設定以下變數:')
|
||||
console.log('DB_HOST=mysql.theaken.com')
|
||||
console.log('DB_PORT=33306')
|
||||
console.log('DB_DATABASE=db_A022')
|
||||
console.log('DB_USERNAME=A022')
|
||||
console.log('DB_PASSWORD=NNyTXRWWtP4b')
|
||||
} else {
|
||||
console.log('✅ 環境變數設定完整')
|
||||
}
|
171
scripts/create-database-schema.sql
Normal file
171
scripts/create-database-schema.sql
Normal file
@@ -0,0 +1,171 @@
|
||||
-- 創建 MySQL 資料庫 Schema
|
||||
-- 執行管理績效儀表板系統
|
||||
|
||||
-- 用戶表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
role ENUM('executive', 'manager', 'hr') NOT NULL,
|
||||
avatar_url VARCHAR(500),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_email (email),
|
||||
INDEX idx_role (role)
|
||||
);
|
||||
|
||||
-- KPI 表
|
||||
CREATE TABLE IF NOT EXISTS kpi (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
category ENUM('financial', 'operational', 'team', 'innovation', 'strategy') NOT NULL,
|
||||
weight INT NOT NULL DEFAULT 1,
|
||||
target_value DECIMAL(15,2) NOT NULL,
|
||||
current_value DECIMAL(15,2) DEFAULT 0,
|
||||
unit VARCHAR(50),
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
approved_by VARCHAR(255),
|
||||
status ENUM('pending', 'approved', 'rejected', 'active', 'completed', 'deleted') DEFAULT 'pending',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_date_range (start_date, end_date)
|
||||
);
|
||||
|
||||
-- KPI 進度追蹤表
|
||||
CREATE TABLE IF NOT EXISTS kpi_progress (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
kpi_id VARCHAR(255) NOT NULL,
|
||||
progress DECIMAL(15,2) NOT NULL,
|
||||
comment TEXT,
|
||||
attachment_url VARCHAR(500),
|
||||
blockers TEXT,
|
||||
created_by VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (kpi_id) REFERENCES kpi(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_kpi_id (kpi_id),
|
||||
INDEX idx_created_by (created_by),
|
||||
INDEX idx_created_at (created_at)
|
||||
);
|
||||
|
||||
-- 評審表
|
||||
CREATE TABLE IF NOT EXISTS reviews (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
reviewer_id VARCHAR(255) NOT NULL,
|
||||
reviewee_id VARCHAR(255) NOT NULL,
|
||||
review_type ENUM('quarterly', 'annual', 'one_on_one', 'goal_setting') NOT NULL,
|
||||
scheduled_date DATETIME NOT NULL,
|
||||
completed_date DATETIME,
|
||||
status ENUM('scheduled', 'in_progress', 'completed', 'cancelled') DEFAULT 'scheduled',
|
||||
summary TEXT,
|
||||
overall_rating DECIMAL(3,2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (reviewer_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (reviewee_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_reviewer_id (reviewer_id),
|
||||
INDEX idx_reviewee_id (reviewee_id),
|
||||
INDEX idx_scheduled_date (scheduled_date),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_review_type (review_type)
|
||||
);
|
||||
|
||||
-- 評審回應表
|
||||
CREATE TABLE IF NOT EXISTS review_responses (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
review_id VARCHAR(255) NOT NULL,
|
||||
question_id VARCHAR(255) NOT NULL,
|
||||
question_text TEXT NOT NULL,
|
||||
response_text TEXT,
|
||||
score INT CHECK(score >= 1 AND score <= 5),
|
||||
question_type ENUM('text', 'rating', 'multiple_choice') NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (review_id) REFERENCES reviews(id) ON DELETE CASCADE,
|
||||
INDEX idx_review_id (review_id)
|
||||
);
|
||||
|
||||
-- KPI 模板表(供 HR 管理)
|
||||
CREATE TABLE IF NOT EXISTS kpi_templates (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
category ENUM('financial', 'operational', 'team', 'innovation', 'strategy'),
|
||||
default_weight INT,
|
||||
smart_criteria JSON,
|
||||
created_by VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_is_active (is_active)
|
||||
);
|
||||
|
||||
-- 評審問題模板
|
||||
CREATE TABLE IF NOT EXISTS review_question_templates (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
question_text TEXT NOT NULL,
|
||||
question_type ENUM('text', 'rating', 'multiple_choice') NOT NULL,
|
||||
category VARCHAR(100),
|
||||
is_required BOOLEAN DEFAULT FALSE,
|
||||
options JSON,
|
||||
created_by VARCHAR(255),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_is_active (is_active)
|
||||
);
|
||||
|
||||
-- 初始化示例數據
|
||||
INSERT IGNORE INTO users (id, name, email, role) VALUES
|
||||
('user_admin', '系統管理員', 'admin@company.com', 'executive'),
|
||||
('user_hr_1', 'HR 經理', 'hr@company.com', 'hr'),
|
||||
('user_mgr_1', '部門經理', 'manager@company.com', 'manager'),
|
||||
('user_exec_1', '執行長', 'ceo@company.com', 'executive'),
|
||||
('user_exec_2', '營運長', 'coo@company.com', 'executive');
|
||||
|
||||
-- 插入示例 KPI 模板
|
||||
INSERT IGNORE INTO kpi_templates (id, name, description, category, default_weight) VALUES
|
||||
('template_1', '營收成長', '年對年營收成長百分比', 'financial', 30),
|
||||
('template_2', '團隊滿意度', '員工滿意度調查分數', 'team', 25),
|
||||
('template_3', '市場佔有率', '公司市場佔有率百分比', 'operational', 20),
|
||||
('template_4', '創新指數', '推出的新計畫數量', 'innovation', 25);
|
||||
|
||||
-- 插入示例評審問題模板
|
||||
INSERT IGNORE INTO review_question_templates (id, question_text, question_type, category, is_required) VALUES
|
||||
('q1', '您如何評價這個季度的整體表現?', 'rating', 'performance', TRUE),
|
||||
('q2', '這個期間您的主要成就是什麼?', 'text', 'performance', TRUE),
|
||||
('q3', '您面臨哪些挑戰,如何克服?', 'text', 'challenges', FALSE),
|
||||
('q4', '您領導團隊的效率如何?', 'rating', 'leadership', TRUE),
|
||||
('q5', '下個季度您的目標是什麼?', 'text', 'goals', TRUE);
|
||||
|
||||
-- 創建視圖以便更容易查詢
|
||||
CREATE OR REPLACE VIEW kpi_with_user AS
|
||||
SELECT
|
||||
k.*,
|
||||
u.name as user_name,
|
||||
u.email as user_email,
|
||||
u.role as user_role,
|
||||
approver.name as approver_name
|
||||
FROM kpi k
|
||||
LEFT JOIN users u ON k.user_id = u.id
|
||||
LEFT JOIN users approver ON k.approved_by = approver.id;
|
||||
|
||||
CREATE OR REPLACE VIEW reviews_with_users AS
|
||||
SELECT
|
||||
r.*,
|
||||
reviewer.name as reviewer_name,
|
||||
reviewer.email as reviewer_email,
|
||||
reviewee.name as reviewee_name,
|
||||
reviewee.email as reviewee_email
|
||||
FROM reviews r
|
||||
LEFT JOIN users reviewer ON r.reviewer_id = reviewer.id
|
||||
LEFT JOIN users reviewee ON r.reviewee_id = reviewee.id;
|
275
scripts/init-database.js
Normal file
275
scripts/init-database.js
Normal file
@@ -0,0 +1,275 @@
|
||||
// 資料庫初始化腳本
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const mysql = require('mysql2/promise')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
async function initDatabase() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
database: process.env.DB_DATABASE,
|
||||
user: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}
|
||||
|
||||
console.log('🔗 連接到 MySQL 資料庫...')
|
||||
console.log(`主機: ${config.host}:${config.port}`)
|
||||
console.log(`資料庫: ${config.database}`)
|
||||
console.log(`用戶: ${config.user}`)
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config)
|
||||
console.log('✅ 資料庫連接成功!')
|
||||
|
||||
// 讀取 SQL schema 檔案
|
||||
const schemaPath = path.join(__dirname, 'create-database-schema.sql')
|
||||
const schemaSQL = fs.readFileSync(schemaPath, 'utf8')
|
||||
|
||||
// 執行 schema
|
||||
console.log('📋 執行資料庫 schema...')
|
||||
const statements = schemaSQL.split(';').filter(stmt => stmt.trim())
|
||||
|
||||
for (const statement of statements) {
|
||||
if (statement.trim()) {
|
||||
await connection.execute(statement)
|
||||
}
|
||||
}
|
||||
console.log('✅ Schema 執行完成')
|
||||
|
||||
// 插入測試數據
|
||||
console.log('📊 插入測試數據...')
|
||||
|
||||
// 插入用戶數據
|
||||
const users = [
|
||||
{
|
||||
id: 'user_admin',
|
||||
name: '系統管理員',
|
||||
email: 'admin@company.com',
|
||||
role: 'executive',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_001',
|
||||
name: '陳雅雯',
|
||||
email: 'chen@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_002',
|
||||
name: '王志明',
|
||||
email: 'wang@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_003',
|
||||
name: '李美玲',
|
||||
email: 'li@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_004',
|
||||
name: '張建國',
|
||||
email: 'zhang@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
}
|
||||
]
|
||||
|
||||
for (const user of users) {
|
||||
await connection.execute(
|
||||
'INSERT IGNORE INTO users (id, name, email, role, avatar_url) VALUES (?, ?, ?, ?, ?)',
|
||||
[user.id, user.name, user.email, user.role, user.avatar_url]
|
||||
)
|
||||
}
|
||||
console.log('✅ 用戶數據插入完成')
|
||||
|
||||
// 插入 KPI 數據
|
||||
const kpis = [
|
||||
{
|
||||
id: 'kpi_001',
|
||||
user_id: 'user_001',
|
||||
title: '營收成長率',
|
||||
description: '年度營收成長百分比目標',
|
||||
category: 'financial',
|
||||
weight: 30,
|
||||
target_value: 100,
|
||||
current_value: 85,
|
||||
unit: '%',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 'kpi_002',
|
||||
user_id: 'user_002',
|
||||
title: '團隊滿意度',
|
||||
description: '員工滿意度調查分數',
|
||||
category: 'team',
|
||||
weight: 25,
|
||||
target_value: 90,
|
||||
current_value: 92,
|
||||
unit: '分',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 'kpi_003',
|
||||
user_id: 'user_003',
|
||||
title: '市場佔有率',
|
||||
description: '公司在目標市場的佔有率',
|
||||
category: 'operational',
|
||||
weight: 20,
|
||||
target_value: 75,
|
||||
current_value: 68,
|
||||
unit: '%',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 'kpi_004',
|
||||
user_id: 'user_004',
|
||||
title: '創新指數',
|
||||
description: '新產品開發和創新項目數量',
|
||||
category: 'innovation',
|
||||
weight: 25,
|
||||
target_value: 80,
|
||||
current_value: 45,
|
||||
unit: '項',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'at_risk'
|
||||
}
|
||||
]
|
||||
|
||||
for (const kpi of kpis) {
|
||||
await connection.execute(
|
||||
`INSERT IGNORE INTO kpi (
|
||||
id, user_id, title, description, category, weight,
|
||||
target_value, current_value, unit, start_date, end_date,
|
||||
approved_by, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
kpi.id, kpi.user_id, kpi.title, kpi.description, kpi.category,
|
||||
kpi.weight, kpi.target_value, kpi.current_value, kpi.unit,
|
||||
kpi.start_date, kpi.end_date, kpi.approved_by, kpi.status
|
||||
]
|
||||
)
|
||||
}
|
||||
console.log('✅ KPI 數據插入完成')
|
||||
|
||||
// 插入評審數據
|
||||
const reviews = [
|
||||
{
|
||||
id: 'review_001',
|
||||
reviewer_id: 'user_admin',
|
||||
reviewee_id: 'user_001',
|
||||
review_type: 'quarterly',
|
||||
scheduled_date: '2024-03-15 14:00:00',
|
||||
status: 'scheduled',
|
||||
notes: 'Q1 績效評審'
|
||||
},
|
||||
{
|
||||
id: 'review_002',
|
||||
reviewer_id: 'user_admin',
|
||||
reviewee_id: 'user_002',
|
||||
review_type: 'quarterly',
|
||||
scheduled_date: '2024-03-20 10:00:00',
|
||||
status: 'scheduled',
|
||||
notes: 'Q1 績效評審'
|
||||
}
|
||||
]
|
||||
|
||||
for (const review of reviews) {
|
||||
await connection.execute(
|
||||
`INSERT IGNORE INTO reviews (
|
||||
id, reviewer_id, reviewee_id, review_type,
|
||||
scheduled_date, status, notes
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
review.id, review.reviewer_id, review.reviewee_id,
|
||||
review.review_type, review.scheduled_date, review.status, review.notes
|
||||
]
|
||||
)
|
||||
}
|
||||
console.log('✅ 評審數據插入完成')
|
||||
|
||||
// 插入 KPI 進度數據
|
||||
const progressData = [
|
||||
{
|
||||
kpi_id: 'kpi_001',
|
||||
progress: 85,
|
||||
comment: 'Q1 表現良好,預計 Q2 可達標',
|
||||
blockers: null,
|
||||
created_by: 'user_001'
|
||||
},
|
||||
{
|
||||
kpi_id: 'kpi_002',
|
||||
progress: 92,
|
||||
comment: '團隊滿意度超出預期',
|
||||
blockers: null,
|
||||
created_by: 'user_002'
|
||||
},
|
||||
{
|
||||
kpi_id: 'kpi_003',
|
||||
progress: 68,
|
||||
comment: '市場競爭激烈,需要調整策略',
|
||||
blockers: '競爭對手價格戰',
|
||||
created_by: 'user_003'
|
||||
},
|
||||
{
|
||||
kpi_id: 'kpi_004',
|
||||
progress: 45,
|
||||
comment: '創新項目進度落後,需要加速',
|
||||
blockers: '研發資源不足',
|
||||
created_by: 'user_004'
|
||||
}
|
||||
]
|
||||
|
||||
for (const progress of progressData) {
|
||||
await connection.execute(
|
||||
`INSERT IGNORE INTO kpi_progress (
|
||||
kpi_id, progress, comment, blockers, created_by
|
||||
) VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
progress.kpi_id, progress.progress, progress.comment,
|
||||
progress.blockers, progress.created_by
|
||||
]
|
||||
)
|
||||
}
|
||||
console.log('✅ KPI 進度數據插入完成')
|
||||
|
||||
// 驗證數據
|
||||
console.log('🔍 驗證數據...')
|
||||
const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users')
|
||||
const [kpiCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi')
|
||||
const [reviewCount] = await connection.execute('SELECT COUNT(*) as count FROM reviews')
|
||||
const [progressCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi_progress')
|
||||
|
||||
console.log(`📊 數據統計:`)
|
||||
console.log(` - 用戶: ${userCount[0].count} 筆`)
|
||||
console.log(` - KPI: ${kpiCount[0].count} 筆`)
|
||||
console.log(` - 評審: ${reviewCount[0].count} 筆`)
|
||||
console.log(` - 進度記錄: ${progressCount[0].count} 筆`)
|
||||
|
||||
await connection.end()
|
||||
console.log('🎉 資料庫初始化完成!')
|
||||
console.log('💡 您現在可以啟動應用程式並測試功能')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 資料庫初始化失敗:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 執行初始化
|
||||
initDatabase()
|
296
scripts/reset-database.js
Normal file
296
scripts/reset-database.js
Normal file
@@ -0,0 +1,296 @@
|
||||
// 重置資料庫腳本
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const mysql = require('mysql2/promise')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
async function resetDatabase() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
database: process.env.DB_DATABASE,
|
||||
user: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}
|
||||
|
||||
console.log('🔗 連接到 MySQL 資料庫...')
|
||||
console.log(`主機: ${config.host}:${config.port}`)
|
||||
console.log(`資料庫: ${config.database}`)
|
||||
console.log(`用戶: ${config.user}`)
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config)
|
||||
console.log('✅ 資料庫連接成功!')
|
||||
|
||||
// 刪除現有的表(如果存在)
|
||||
console.log('\n🗑️ 清理現有表...')
|
||||
const dropTables = [
|
||||
'DROP TABLE IF EXISTS review_responses',
|
||||
'DROP TABLE IF EXISTS kpi_progress',
|
||||
'DROP TABLE IF EXISTS kpi',
|
||||
'DROP TABLE IF EXISTS reviews',
|
||||
'DROP TABLE IF EXISTS users'
|
||||
]
|
||||
|
||||
for (const dropSQL of dropTables) {
|
||||
try {
|
||||
await connection.execute(dropSQL)
|
||||
console.log(` ✅ 刪除表: ${dropSQL.split(' ')[4]}`)
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 刪除表失敗: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 讀取並執行 schema
|
||||
console.log('\n📋 創建新的資料庫結構...')
|
||||
const schemaPath = path.join(__dirname, 'create-database-schema.sql')
|
||||
const schemaSQL = fs.readFileSync(schemaPath, 'utf8')
|
||||
|
||||
const statements = schemaSQL.split(';').filter(stmt => stmt.trim())
|
||||
|
||||
for (const statement of statements) {
|
||||
if (statement.trim()) {
|
||||
try {
|
||||
await connection.execute(statement)
|
||||
console.log(' ✅ 執行 SQL 語句成功')
|
||||
} catch (error) {
|
||||
console.log(` ❌ SQL 執行失敗: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 插入測試數據
|
||||
console.log('\n📊 插入測試數據...')
|
||||
|
||||
// 插入用戶數據
|
||||
const users = [
|
||||
{
|
||||
id: 'user_admin',
|
||||
name: '系統管理員',
|
||||
email: 'admin@company.com',
|
||||
role: 'executive',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_001',
|
||||
name: '陳雅雯',
|
||||
email: 'chen@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_002',
|
||||
name: '王志明',
|
||||
email: 'wang@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_003',
|
||||
name: '李美玲',
|
||||
email: 'li@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
},
|
||||
{
|
||||
id: 'user_004',
|
||||
name: '張建國',
|
||||
email: 'zhang@company.com',
|
||||
role: 'manager',
|
||||
avatar_url: null
|
||||
}
|
||||
]
|
||||
|
||||
for (const user of users) {
|
||||
await connection.execute(
|
||||
'INSERT IGNORE INTO users (id, name, email, role, avatar_url) VALUES (?, ?, ?, ?, ?)',
|
||||
[user.id, user.name, user.email, user.role, user.avatar_url]
|
||||
)
|
||||
}
|
||||
console.log('✅ 用戶數據插入完成')
|
||||
|
||||
// 插入 KPI 數據
|
||||
const kpis = [
|
||||
{
|
||||
id: 'kpi_001',
|
||||
user_id: 'user_001',
|
||||
title: '營收成長率',
|
||||
description: '年度營收成長百分比目標',
|
||||
category: 'financial',
|
||||
weight: 30,
|
||||
target_value: 100,
|
||||
current_value: 85,
|
||||
unit: '%',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 'kpi_002',
|
||||
user_id: 'user_002',
|
||||
title: '團隊滿意度',
|
||||
description: '員工滿意度調查分數',
|
||||
category: 'team',
|
||||
weight: 25,
|
||||
target_value: 90,
|
||||
current_value: 92,
|
||||
unit: '分',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 'kpi_003',
|
||||
user_id: 'user_003',
|
||||
title: '市場佔有率',
|
||||
description: '公司在目標市場的佔有率',
|
||||
category: 'operational',
|
||||
weight: 20,
|
||||
target_value: 75,
|
||||
current_value: 68,
|
||||
unit: '%',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 'kpi_004',
|
||||
user_id: 'user_004',
|
||||
title: '創新指數',
|
||||
description: '新產品開發和創新項目數量',
|
||||
category: 'innovation',
|
||||
weight: 25,
|
||||
target_value: 80,
|
||||
current_value: 45,
|
||||
unit: '項',
|
||||
start_date: '2024-01-01',
|
||||
end_date: '2024-12-31',
|
||||
approved_by: 'user_admin',
|
||||
status: 'at_risk'
|
||||
}
|
||||
]
|
||||
|
||||
for (const kpi of kpis) {
|
||||
await connection.execute(
|
||||
`INSERT IGNORE INTO kpi (
|
||||
id, user_id, title, description, category, weight,
|
||||
target_value, current_value, unit, start_date, end_date,
|
||||
approved_by, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
kpi.id, kpi.user_id, kpi.title, kpi.description, kpi.category,
|
||||
kpi.weight, kpi.target_value, kpi.current_value, kpi.unit,
|
||||
kpi.start_date, kpi.end_date, kpi.approved_by, kpi.status
|
||||
]
|
||||
)
|
||||
}
|
||||
console.log('✅ KPI 數據插入完成')
|
||||
|
||||
// 插入評審數據
|
||||
const reviews = [
|
||||
{
|
||||
id: 'review_001',
|
||||
reviewer_id: 'user_admin',
|
||||
reviewee_id: 'user_001',
|
||||
review_type: 'quarterly',
|
||||
scheduled_date: '2024-03-15 14:00:00',
|
||||
status: 'scheduled',
|
||||
notes: 'Q1 績效評審'
|
||||
},
|
||||
{
|
||||
id: 'review_002',
|
||||
reviewer_id: 'user_admin',
|
||||
reviewee_id: 'user_002',
|
||||
review_type: 'quarterly',
|
||||
scheduled_date: '2024-03-20 10:00:00',
|
||||
status: 'scheduled',
|
||||
notes: 'Q1 績效評審'
|
||||
}
|
||||
]
|
||||
|
||||
for (const review of reviews) {
|
||||
await connection.execute(
|
||||
`INSERT IGNORE INTO reviews (
|
||||
id, reviewer_id, reviewee_id, review_type,
|
||||
scheduled_date, status, summary
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
review.id, review.reviewer_id, review.reviewee_id,
|
||||
review.review_type, review.scheduled_date, review.status, review.notes
|
||||
]
|
||||
)
|
||||
}
|
||||
console.log('✅ 評審數據插入完成')
|
||||
|
||||
// 插入 KPI 進度數據
|
||||
const progressData = [
|
||||
{
|
||||
kpi_id: 'kpi_001',
|
||||
progress: 85,
|
||||
comment: 'Q1 表現良好,預計 Q2 可達標',
|
||||
blockers: null,
|
||||
created_by: 'user_001'
|
||||
},
|
||||
{
|
||||
kpi_id: 'kpi_002',
|
||||
progress: 92,
|
||||
comment: '團隊滿意度超出預期',
|
||||
blockers: null,
|
||||
created_by: 'user_002'
|
||||
},
|
||||
{
|
||||
kpi_id: 'kpi_003',
|
||||
progress: 68,
|
||||
comment: '市場競爭激烈,需要調整策略',
|
||||
blockers: '競爭對手價格戰',
|
||||
created_by: 'user_003'
|
||||
},
|
||||
{
|
||||
kpi_id: 'kpi_004',
|
||||
progress: 45,
|
||||
comment: '創新項目進度落後,需要加速',
|
||||
blockers: '研發資源不足',
|
||||
created_by: 'user_004'
|
||||
}
|
||||
]
|
||||
|
||||
for (const progress of progressData) {
|
||||
await connection.execute(
|
||||
`INSERT IGNORE INTO kpi_progress (
|
||||
kpi_id, progress, comment, blockers, created_by
|
||||
) VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
progress.kpi_id, progress.progress, progress.comment,
|
||||
progress.blockers, progress.created_by
|
||||
]
|
||||
)
|
||||
}
|
||||
console.log('✅ KPI 進度數據插入完成')
|
||||
|
||||
// 驗證數據
|
||||
console.log('\n🔍 驗證數據...')
|
||||
const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users')
|
||||
const [kpiCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi')
|
||||
const [reviewCount] = await connection.execute('SELECT COUNT(*) as count FROM reviews')
|
||||
const [progressCount] = await connection.execute('SELECT COUNT(*) as count FROM kpi_progress')
|
||||
|
||||
console.log(`📊 數據統計:`)
|
||||
console.log(` - 用戶: ${userCount[0].count} 筆`)
|
||||
console.log(` - KPI: ${kpiCount[0].count} 筆`)
|
||||
console.log(` - 評審: ${reviewCount[0].count} 筆`)
|
||||
console.log(` - 進度記錄: ${progressCount[0].count} 筆`)
|
||||
|
||||
await connection.end()
|
||||
console.log('\n🎉 資料庫重置完成!')
|
||||
console.log('💡 您現在可以啟動應用程式並測試功能')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 資料庫重置失敗:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
resetDatabase()
|
27
scripts/setup-env.js
Normal file
27
scripts/setup-env.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// 環境變數設定腳本
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const envContent = `# MySQL 資料庫設定
|
||||
DB_HOST=mysql.theaken.com
|
||||
DB_PORT=33306
|
||||
DB_DATABASE=db_A022
|
||||
DB_USERNAME=A022
|
||||
DB_PASSWORD=NNyTXRWWtP4b
|
||||
|
||||
# Next.js 設定
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3006
|
||||
`
|
||||
|
||||
const envPath = path.join(__dirname, '..', '.env.local')
|
||||
|
||||
try {
|
||||
fs.writeFileSync(envPath, envContent)
|
||||
console.log('✅ .env.local 檔案已創建')
|
||||
console.log('📁 檔案位置:', envPath)
|
||||
console.log('🔐 請確認資料庫連接資訊正確')
|
||||
} catch (error) {
|
||||
console.error('❌ 創建 .env.local 檔案失敗:', error.message)
|
||||
console.log('💡 請手動創建 .env.local 檔案並添加以下內容:')
|
||||
console.log(envContent)
|
||||
}
|
52
scripts/simple-db-check.js
Normal file
52
scripts/simple-db-check.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// 簡單的資料庫檢查
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const mysql = require('mysql2/promise')
|
||||
|
||||
async function simpleCheck() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
database: process.env.DB_DATABASE,
|
||||
user: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config)
|
||||
console.log('🔗 連接到 MySQL 資料庫...')
|
||||
|
||||
// 直接查詢表
|
||||
console.log('\n📋 檢查資料庫表...')
|
||||
const [tables] = await connection.execute('SHOW TABLES')
|
||||
|
||||
console.log(`\n📊 找到 ${tables.length} 個表:`)
|
||||
tables.forEach((table, index) => {
|
||||
const tableName = Object.values(table)[0]
|
||||
console.log(` ${index + 1}. ${tableName}`)
|
||||
})
|
||||
|
||||
// 檢查每個表的結構
|
||||
for (const table of tables) {
|
||||
const tableName = Object.values(table)[0]
|
||||
console.log(`\n🔍 表結構: ${tableName}`)
|
||||
|
||||
try {
|
||||
const [columns] = await connection.execute(`DESCRIBE ${tableName}`)
|
||||
console.log(' 欄位:')
|
||||
columns.forEach(col => {
|
||||
console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'YES' ? 'NULL' : 'NOT NULL'} ${col.Key || ''}`)
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(` ❌ 無法讀取表結構: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
await connection.end()
|
||||
console.log('\n✅ 資料庫檢查完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 資料庫檢查失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
simpleCheck()
|
101
scripts/test-api.js
Normal file
101
scripts/test-api.js
Normal file
@@ -0,0 +1,101 @@
|
||||
// API 測試腳本
|
||||
const http = require('http')
|
||||
|
||||
async function testAPI() {
|
||||
console.log('🧪 開始測試 API 端點...\n')
|
||||
|
||||
// 測試資料庫連接
|
||||
console.log('1️⃣ 測試資料庫連接...')
|
||||
try {
|
||||
const dbResponse = await makeRequest('/api/database/test')
|
||||
console.log(' ✅ 資料庫連接正常')
|
||||
console.log(` 📊 回應: ${JSON.stringify(dbResponse, null, 2)}`)
|
||||
} catch (error) {
|
||||
console.log(' ❌ 資料庫連接失敗:', error.message)
|
||||
}
|
||||
|
||||
// 測試 KPI API
|
||||
console.log('\n2️⃣ 測試 KPI API...')
|
||||
try {
|
||||
const kpiResponse = await makeRequest('/api/kpi?userId=user_001')
|
||||
console.log(' ✅ KPI API 正常')
|
||||
console.log(` 📊 找到 ${kpiResponse.length} 個 KPI`)
|
||||
kpiResponse.forEach(kpi => {
|
||||
console.log(` - ${kpi.title}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit}`)
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(' ❌ KPI API 失敗:', error.message)
|
||||
}
|
||||
|
||||
// 測試用戶 API
|
||||
console.log('\n3️⃣ 測試用戶 API...')
|
||||
try {
|
||||
const usersResponse = await makeRequest('/api/users')
|
||||
console.log(' ✅ 用戶 API 正常')
|
||||
console.log(` 📊 找到 ${usersResponse.length} 個用戶`)
|
||||
usersResponse.slice(0, 3).forEach(user => {
|
||||
console.log(` - ${user.name} (${user.role})`)
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(' ❌ 用戶 API 失敗:', error.message)
|
||||
}
|
||||
|
||||
// 測試評審 API
|
||||
console.log('\n4️⃣ 測試評審 API...')
|
||||
try {
|
||||
const reviewsResponse = await makeRequest('/api/reviews')
|
||||
console.log(' ✅ 評審 API 正常')
|
||||
console.log(` 📊 找到 ${reviewsResponse.length} 個評審`)
|
||||
reviewsResponse.forEach(review => {
|
||||
console.log(` - ${review.review_type} 評審 (${review.status})`)
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(' ❌ 評審 API 失敗:', error.message)
|
||||
}
|
||||
|
||||
console.log('\n🎉 API 測試完成!')
|
||||
}
|
||||
|
||||
function makeRequest(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 3006,
|
||||
path: path,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = ''
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const jsonData = JSON.parse(data)
|
||||
resolve(jsonData)
|
||||
} catch (error) {
|
||||
reject(new Error(`解析回應失敗: ${error.message}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(new Error(`請求失敗: ${error.message}`))
|
||||
})
|
||||
|
||||
req.setTimeout(5000, () => {
|
||||
req.destroy()
|
||||
reject(new Error('請求超時'))
|
||||
})
|
||||
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
testAPI().catch(console.error)
|
51
scripts/test-db-connection.js
Normal file
51
scripts/test-db-connection.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// MySQL 資料庫連接測試腳本
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const mysql = require('mysql2/promise')
|
||||
|
||||
async function testConnection() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
database: process.env.DB_DATABASE,
|
||||
user: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}
|
||||
|
||||
console.log('🔗 嘗試連接到 MySQL 資料庫...')
|
||||
console.log(`主機: ${config.host}:${config.port}`)
|
||||
console.log(`資料庫: ${config.database}`)
|
||||
console.log(`用戶: ${config.user}`)
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config)
|
||||
console.log('✅ MySQL 資料庫連接成功!')
|
||||
|
||||
// 測試基本查詢
|
||||
const [rows] = await connection.execute('SELECT VERSION() as version')
|
||||
console.log(`📊 MySQL 版本: ${rows[0].version}`)
|
||||
|
||||
// 檢查資料庫表
|
||||
const [tables] = await connection.execute(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = ?
|
||||
`, [config.database])
|
||||
|
||||
console.log(`📋 資料庫表數量: ${tables.length}`)
|
||||
if (tables.length > 0) {
|
||||
console.log('現有表:')
|
||||
tables.forEach(table => {
|
||||
console.log(` - ${table.table_name}`)
|
||||
})
|
||||
} else {
|
||||
console.log('⚠️ 沒有找到表,您可能需要執行 schema 創建腳本')
|
||||
}
|
||||
|
||||
await connection.end()
|
||||
} catch (error) {
|
||||
console.error('❌ 資料庫連接失敗:', error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
testConnection()
|
64
scripts/test-kpi-data.js
Normal file
64
scripts/test-kpi-data.js
Normal file
@@ -0,0 +1,64 @@
|
||||
// 測試 KPI 數據
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const mysql = require('mysql2/promise')
|
||||
|
||||
async function testKPIData() {
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
database: process.env.DB_DATABASE,
|
||||
user: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(config)
|
||||
console.log('🔗 連接到 MySQL 資料庫...')
|
||||
|
||||
// 檢查用戶數據
|
||||
console.log('\n👥 檢查用戶數據...')
|
||||
const [users] = await connection.execute('SELECT * FROM users')
|
||||
console.log(`找到 ${users.length} 個用戶:`)
|
||||
users.forEach(user => {
|
||||
console.log(` - ${user.id}: ${user.name} (${user.role})`)
|
||||
})
|
||||
|
||||
// 檢查 KPI 數據
|
||||
console.log('\n📊 檢查 KPI 數據...')
|
||||
const [kpis] = await connection.execute('SELECT * FROM kpi')
|
||||
console.log(`找到 ${kpis.length} 個 KPI:`)
|
||||
kpis.forEach(kpi => {
|
||||
console.log(` - ${kpi.id}: ${kpi.title} (${kpi.status}) - 用戶: ${kpi.user_id}`)
|
||||
})
|
||||
|
||||
// 測試特定用戶的 KPI
|
||||
console.log('\n🔍 測試 user_001 的 KPI...')
|
||||
const [userKPIs] = await connection.execute(
|
||||
'SELECT * FROM kpi WHERE user_id = ?',
|
||||
['user_001']
|
||||
)
|
||||
console.log(`user_001 有 ${userKPIs.length} 個 KPI:`)
|
||||
userKPIs.forEach(kpi => {
|
||||
console.log(` - ${kpi.title}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit} (${kpi.status})`)
|
||||
})
|
||||
|
||||
// 測試活躍狀態的 KPI
|
||||
console.log('\n🔍 測試活躍狀態的 KPI...')
|
||||
const [activeKPIs] = await connection.execute(
|
||||
'SELECT * FROM kpi WHERE status = ?',
|
||||
['active']
|
||||
)
|
||||
console.log(`有 ${activeKPIs.length} 個活躍的 KPI:`)
|
||||
activeKPIs.forEach(kpi => {
|
||||
console.log(` - ${kpi.title} (${kpi.user_id}): ${kpi.status}`)
|
||||
})
|
||||
|
||||
await connection.end()
|
||||
console.log('\n✅ KPI 數據測試完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ KPI 數據測試失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
testKPIData()
|
Reference in New Issue
Block a user