完成評審評分機制

This commit is contained in:
2025-09-18 18:34:31 +08:00
parent 2101767690
commit ffa1e45f63
54 changed files with 5730 additions and 709 deletions

View File

@@ -748,7 +748,7 @@ export class UserService {
}
// 靜態方法保持向後兼容
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id?: string }): Promise<User> {
const service = new UserService();
return await service.create(userData);
}
@@ -932,6 +932,44 @@ export class JudgeService {
return false;
}
}
// 獲取評審的評分任務
static async getJudgeScoringTasks(judgeId: string, competitionId?: string): Promise<any[]> {
let sql = `
SELECT DISTINCT
a.id,
a.name,
'app' as type,
'individual' as participant_type,
COALESCE(js.total_score, 0) as score,
CASE
WHEN js.total_score > 0 THEN 'completed'
ELSE 'pending'
END as status,
js.submitted_at,
t.name as team_name,
CONCAT(COALESCE(t.name, '未知團隊'), ' - ', a.name) as display_name
FROM apps a
LEFT JOIN teams t ON a.team_id = t.id
LEFT JOIN competition_apps ca ON a.id = ca.app_id
LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ?
WHERE ca.competition_id = ?
`;
const params = [judgeId];
if (competitionId) {
params.push(competitionId);
} else {
// 如果沒有指定競賽,獲取所有競賽的任務
sql = sql.replace('WHERE ca.competition_id = ?', 'WHERE ca.competition_id IS NOT NULL');
}
sql += ' ORDER BY a.name';
const results = await db.query(sql, params);
return results;
}
}
// =====================================================
@@ -1722,16 +1760,6 @@ export class CompetitionService {
return result.affectedRows > 0;
}
// 獲取競賽的評分規則列表
static async getCompetitionRules(competitionId: string): Promise<any[]> {
const sql = `
SELECT cr.*
FROM competition_rules cr
WHERE cr.competition_id = ?
ORDER BY cr.order_index ASC, cr.created_at ASC
`;
return await db.query(sql, [competitionId]);
}
// 為競賽添加評分規則
static async addCompetitionRules(competitionId: string, rules: any[]): Promise<boolean> {
@@ -1797,7 +1825,7 @@ export class CompetitionService {
this.getCompetitionTeams(competitionId),
this.getCompetitionApps(competitionId),
this.getCompetitionAwardTypes(competitionId),
this.getCompetitionRules(competitionId)
ScoringService.getCompetitionRules(competitionId)
]);
// 根據日期動態計算競賽狀態
@@ -3186,7 +3214,7 @@ export class AppService {
apps.push({
id: appId,
name: appDetails.appName || '未知應用',
name: (appDetails as any).appName || '未知應用',
description: '應用描述不可用',
category: '未分類',
type: '未分類',
@@ -3322,7 +3350,7 @@ export class AppService {
details = activity.details;
}
}
category = details.category || '未分類';
category = (details as any).category || '未分類';
}
if (!categoryCount[category]) {
@@ -3332,12 +3360,12 @@ export class AppService {
categoryCount[category].uniqueApps.add(activity.resource_id);
} catch (error) {
// 出錯時使用默認類別
const category = '未分類';
if (!categoryCount[category]) {
categoryCount[category] = { count: 0, uniqueApps: new Set() };
const defaultCategory = '未分類';
if (!categoryCount[defaultCategory]) {
categoryCount[defaultCategory] = { count: 0, uniqueApps: new Set() };
}
categoryCount[category].count++;
categoryCount[category].uniqueApps.add(activity.resource_id);
categoryCount[defaultCategory].count++;
categoryCount[defaultCategory].uniqueApps.add(activity.resource_id);
}
}
@@ -3419,35 +3447,443 @@ export class AppService {
// 評分服務
// =====================================================
export class ScoringService {
// 提交應用評分
static async submitAppScore(scoreData: Omit<AppJudgeScore, 'id' | 'submitted_at'>): Promise<AppJudgeScore> {
const sql = `
INSERT INTO app_judge_scores (id, judge_id, app_id, innovation_score, technical_score, usability_score, presentation_score, impact_score, total_score, comments)
VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
innovation_score = VALUES(innovation_score),
technical_score = VALUES(technical_score),
usability_score = VALUES(usability_score),
presentation_score = VALUES(presentation_score),
impact_score = VALUES(impact_score),
total_score = VALUES(total_score),
comments = VALUES(comments),
submitted_at = CURRENT_TIMESTAMP
`;
const params = [
scoreData.judge_id,
scoreData.app_id,
scoreData.innovation_score,
scoreData.technical_score,
scoreData.usability_score,
scoreData.presentation_score,
scoreData.impact_score,
scoreData.total_score,
scoreData.comments || null
];
// 獲取競賽規則
static async getCompetitionRules(competitionId: string): Promise<any[]> {
console.log('🔍 獲取競賽規則competitionId:', competitionId);
await db.insert(sql, params);
return await this.getAppScore(scoreData.judge_id, scoreData.app_id) as AppJudgeScore;
const sql = `
SELECT id, name, description, weight, order_index
FROM competition_rules
WHERE competition_id = ?
ORDER BY order_index ASC
`;
try {
const result = await db.query(sql, [competitionId]);
console.log('🔍 競賽規則查詢結果:', result);
return result;
} catch (error) {
console.error('❌ 獲取競賽規則失敗:', error);
return [];
}
}
// 根據APP ID獲取競賽ID
static async getCompetitionIdByAppId(appId: string): Promise<string | null> {
const sql = `
SELECT ca.competition_id
FROM competition_apps ca
WHERE ca.app_id = ?
`;
const result = await db.query(sql, [appId]);
return result.length > 0 ? result[0].competition_id : null;
}
// 提交應用評分(基於競賽規則的動態評分)
static async submitAppScore(scoreData: any): Promise<any> {
console.log('🔍 開始提交評分,數據:', scoreData);
const { judge_id, app_id, competition_id, scores, total_score, comments, isEdit, recordId } = scoreData;
// 驗證必要參數
console.log('🔍 參數驗證:');
console.log('judge_id:', judge_id, typeof judge_id);
console.log('app_id:', app_id, typeof app_id);
console.log('competition_id:', competition_id, typeof competition_id);
console.log('scores:', scores, typeof scores);
console.log('total_score:', total_score, typeof total_score);
if (!judge_id || !app_id || !competition_id || !scores || total_score === undefined) {
throw new Error('缺少必要的評分參數');
}
try {
// 1. 生成唯一的評分記錄ID
const judgeScoreId = `js_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
console.log('🔍 生成的評分記錄ID:', judgeScoreId);
let finalJudgeScoreId;
if (isEdit && recordId) {
// 編輯模式使用傳入的記錄ID
finalJudgeScoreId = recordId;
console.log('🔍 編輯模式使用記錄ID:', finalJudgeScoreId);
} else {
// 新增模式:檢查是否已存在評分記錄
const existingScore = await db.query(
'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ?',
[judge_id, app_id]
);
if (existingScore.length > 0) {
// 使用現有的評分記錄ID
finalJudgeScoreId = existingScore[0].id;
console.log('🔍 使用現有評分記錄ID:', finalJudgeScoreId);
} else {
// 創建新記錄
finalJudgeScoreId = `js_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
console.log('🔍 創建新評分記錄ID:', finalJudgeScoreId);
}
}
// 檢查記錄是否存在,決定是更新還是插入
const existingRecord = await db.query(
'SELECT id FROM judge_scores WHERE id = ?',
[finalJudgeScoreId]
);
if (existingRecord.length > 0) {
// 更新現有記錄
await db.update(`
UPDATE judge_scores
SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP
WHERE id = ?
`, [total_score, comments || null, finalJudgeScoreId]);
console.log('🔍 更新現有評分記錄');
} else {
// 檢查是否已有相同 judge_id + app_id + competition_id 的記錄
const duplicateRecord = await db.query(
'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ? AND competition_id = ?',
[judge_id, app_id, competition_id]
);
if (duplicateRecord.length > 0) {
// 更新現有記錄
finalJudgeScoreId = duplicateRecord[0].id;
await db.update(`
UPDATE judge_scores
SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP
WHERE id = ?
`, [total_score, comments || null, finalJudgeScoreId]);
console.log('🔍 更新重複的評分記錄');
} else {
// 創建新記錄
await db.insert(`
INSERT INTO judge_scores (id, judge_id, app_id, competition_id, total_score, comments)
VALUES (?, ?, ?, ?, ?, ?)
`, [
finalJudgeScoreId,
judge_id,
app_id,
competition_id,
total_score,
comments || null
]);
console.log('🔍 創建新評分記錄');
}
}
// 2. 獲取競賽規則
const rules = await this.getCompetitionRules(competition_id);
console.log('🔍 競賽規則:', rules);
// 3. 刪除現有的評分詳情
await db.delete(
'DELETE FROM judge_score_details WHERE judge_score_id = ?',
[finalJudgeScoreId]
);
// 4. 插入新的評分詳情
for (const [ruleName, score] of Object.entries(scores)) {
if (typeof score === 'number' && score > 0) {
// 找到對應的規則
const rule = rules.find((r: any) => r.name === ruleName);
console.log(`🔍 尋找規則 ${ruleName}:`, rule);
if (rule) {
const detailId = `jsd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
console.log('🔍 插入評分詳情:', {
detailId,
finalJudgeScoreId,
ruleId: rule.id,
ruleName: rule.name,
score,
weight: rule.weight
});
await db.insert(`
INSERT INTO judge_score_details (id, judge_score_id, rule_id, rule_name, score, weight)
VALUES (?, ?, ?, ?, ?, ?)
`, [
detailId,
finalJudgeScoreId,
rule.id,
rule.name,
score,
rule.weight
]);
} else {
console.log(`⚠️ 找不到規則: ${ruleName}`);
}
}
}
// 返回完整的評分記錄
return await this.getJudgeScoreById(finalJudgeScoreId);
} catch (error) {
console.error('❌ 提交評分失敗:', error);
throw error;
}
}
// 根據ID獲取評分記錄
static async getJudgeScoreById(judgeScoreId: string): Promise<any> {
const sql = `
SELECT
js.*,
GROUP_CONCAT(
CONCAT(jsd.rule_name, ':', jsd.score, ':', jsd.weight)
SEPARATOR '|'
) as score_details
FROM judge_scores js
LEFT JOIN judge_score_details jsd ON js.id = jsd.judge_score_id
WHERE js.id = ?
GROUP BY js.id
`;
const result = await db.query(sql, [judgeScoreId]);
return result.length > 0 ? result[0] : null;
}
// 獲取競賽評分進度
static async getCompetitionScoringProgress(competitionId: string): Promise<{
completed: number;
total: number;
percentage: number;
}> {
try {
console.log('🔍 獲取競賽評分進度competitionId:', competitionId);
// 獲取競賽的評審數量
const judgesResult = await db.query(`
SELECT COUNT(DISTINCT cj.judge_id) as judge_count
FROM competition_judges cj
WHERE cj.competition_id = ?
`, [competitionId]);
const judgeCount = judgesResult[0]?.judge_count || 0;
console.log('🔍 評審數量:', judgeCount);
// 獲取競賽的參賽APP數量
const appsResult = await db.query(`
SELECT COUNT(DISTINCT ca.app_id) as app_count
FROM competition_apps ca
WHERE ca.competition_id = ?
`, [competitionId]);
const appCount = appsResult[0]?.app_count || 0;
console.log('🔍 參賽APP數量:', appCount);
// 如果沒有評審或APP關聯嘗試從其他方式獲取
let finalJudgeCount = judgeCount;
let finalAppCount = appCount;
if (judgeCount === 0) {
// 嘗試從 judges 表獲取所有評審
const allJudgesResult = await db.query('SELECT COUNT(*) as judge_count FROM judges');
finalJudgeCount = allJudgesResult[0]?.judge_count || 0;
console.log('🔍 使用所有評審數量:', finalJudgeCount);
}
if (appCount === 0) {
// 嘗試從 apps 表獲取所有APP
const allAppsResult = await db.query('SELECT COUNT(*) as app_count FROM apps');
finalAppCount = allAppsResult[0]?.app_count || 0;
console.log('🔍 使用所有APP數量:', finalAppCount);
}
// 獲取已完成的評分數量
const completedResult = await db.query(`
SELECT COUNT(*) as completed_count
FROM judge_scores js
WHERE js.competition_id = ?
`, [competitionId]);
const completed = completedResult[0]?.completed_count || 0;
const total = finalJudgeCount * finalAppCount;
const percentage = total > 0 ? Math.min(Math.round((completed / total) * 100), 100) : 0;
console.log('🔍 評分進度結果:', { completed, total, percentage });
return {
completed,
total,
percentage
};
} catch (error) {
console.error('獲取評分進度失敗:', error);
return { completed: 0, total: 0, percentage: 0 };
}
}
// 獲取評分完成度匯總
static async getScoringSummary(competitionId: string): Promise<{
judges: Array<{
id: string;
name: string;
email: string;
completedCount: number;
totalCount: number;
completionRate: number;
status: 'completed' | 'partial' | 'not_started';
lastScoredAt?: string;
}>;
apps: Array<{
id: string;
name: string;
teamName?: string;
scoredCount: number;
totalJudges: number;
completionRate: number;
status: 'completed' | 'partial' | 'not_scored';
averageScore?: number;
}>;
overallStats: {
totalJudges: number;
totalApps: number;
totalPossibleScores: number;
completedScores: number;
overallCompletionRate: number;
};
}> {
try {
console.log('🔍 獲取評分完成度匯總competitionId:', competitionId);
// 獲取競賽的評審列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有評審
let judgesResult = await db.query(`
SELECT j.id, j.name
FROM judges j
LEFT JOIN competition_judges cj ON j.id = cj.judge_id
WHERE cj.competition_id = ?
ORDER BY j.name
`, [competitionId]);
// 如果沒有關聯的評審,獲取所有評審
if (judgesResult.length === 0) {
judgesResult = await db.query(`
SELECT id, name FROM judges ORDER BY name
`);
}
// 獲取競賽的APP列表 - 先嘗試從關聯表獲取如果沒有則獲取所有APP
let appsResult = await db.query(`
SELECT a.id, a.name, t.name as team_name
FROM apps a
LEFT JOIN competition_apps ca ON a.id = ca.app_id
LEFT JOIN teams t ON a.team_id = t.id
WHERE ca.competition_id = ?
ORDER BY a.name
`, [competitionId]);
// 如果沒有關聯的APP獲取所有APP
if (appsResult.length === 0) {
appsResult = await db.query(`
SELECT a.id, a.name, t.name as team_name
FROM apps a
LEFT JOIN teams t ON a.team_id = t.id
ORDER BY a.name
`);
}
// 獲取已完成的評分記錄
const scoresResult = await db.query(`
SELECT js.judge_id, js.app_id, js.total_score, js.submitted_at,
j.name as judge_name, a.name as app_name
FROM judge_scores js
LEFT JOIN judges j ON js.judge_id = j.id
LEFT JOIN apps a ON js.app_id = a.id
WHERE js.competition_id = ?
ORDER BY js.submitted_at DESC
`, [competitionId]);
const judges = judgesResult.map((judge: any) => {
const judgeScores = scoresResult.filter((score: any) => score.judge_id === judge.id);
const completedCount = judgeScores.length;
const totalCount = appsResult.length;
const completionRate = totalCount > 0 ? Math.min(Math.round((completedCount / totalCount) * 100), 100) : 0;
let status: 'completed' | 'partial' | 'not_started' = 'not_started';
if (completedCount === totalCount && totalCount > 0) status = 'completed';
else if (completedCount > 0) status = 'partial';
const lastScoredAt = judgeScores.length > 0 ?
judgeScores[0].submitted_at : undefined;
return {
id: judge.id,
name: judge.name,
email: '', // judges 表沒有 email 字段
completedCount,
totalCount,
completionRate,
status,
lastScoredAt
};
});
const apps = appsResult.map((app: any) => {
const appScores = scoresResult.filter((score: any) => score.app_id === app.id);
const scoredCount = appScores.length;
const totalJudges = judges.length;
const completionRate = totalJudges > 0 ? Math.min(Math.round((scoredCount / totalJudges) * 100), 100) : 0;
let status: 'completed' | 'partial' | 'not_scored' = 'not_scored';
if (scoredCount >= totalJudges && totalJudges > 0) status = 'completed';
else if (scoredCount > 0) status = 'partial';
const averageScore = appScores.length > 0 ?
Math.round(appScores.reduce((sum: number, score: any) => sum + (score.total_score || 0), 0) / appScores.length) : undefined;
return {
id: app.id,
name: app.name,
teamName: app.team_name,
scoredCount,
totalJudges,
completionRate,
status,
averageScore
};
});
const totalJudges = judges.length;
const totalApps = apps.length;
const totalPossibleScores = totalJudges * totalApps;
const completedScores = scoresResult.length;
const overallCompletionRate = totalPossibleScores > 0 ?
Math.min(Math.round((completedScores / totalPossibleScores) * 100), 100) : 0;
const summary = {
judges,
apps,
overallStats: {
totalJudges,
totalApps,
totalPossibleScores,
completedScores,
overallCompletionRate
}
};
console.log('🔍 評分完成度匯總結果:', summary);
return summary;
} catch (error) {
console.error('獲取評分完成度匯總失敗:', error);
return {
judges: [],
apps: [],
overallStats: {
totalJudges: 0,
totalApps: 0,
totalPossibleScores: 0,
completedScores: 0,
overallCompletionRate: 0
}
};
}
}
// 獲取應用評分
@@ -3504,6 +3940,291 @@ export class ScoringService {
const sql = 'SELECT * FROM proposal_judge_scores WHERE proposal_id = ? ORDER BY submitted_at DESC';
return await db.query<ProposalJudgeScore>(sql, [proposalId]);
}
// 獲取競賽的所有評分記錄(包含評審和參賽者信息)
static async getCompetitionScores(competitionId: string): Promise<any[]> {
const sql = `
SELECT
js.id,
js.judge_id,
js.app_id,
js.total_score,
js.comments,
js.submitted_at,
j.name as judge_name,
j.title as judge_title,
j.department as judge_department,
a.name as app_name,
a.creator_id,
u.name as creator_name,
u.department as creator_department,
'app' as participant_type,
-- 從 judge_score_details 表獲取詳細評分
(SELECT GROUP_CONCAT(CONCAT(jsd.rule_name, ':', jsd.score) SEPARATOR ',')
FROM judge_score_details jsd
WHERE jsd.judge_score_id = js.id) as score_details
FROM judge_scores js
JOIN judges j ON js.judge_id = j.id
JOIN apps a ON js.app_id = a.id
JOIN users u ON a.creator_id = u.id
WHERE js.competition_id = ?
UNION ALL
SELECT
js.id,
js.judge_id,
js.app_id,
js.total_score,
js.comments,
js.submitted_at,
j.name as judge_name,
j.title as judge_title,
j.department as judge_department,
t.name as app_name,
t.leader_id as creator_id,
u.name as creator_name,
u.department as creator_department,
'team' as participant_type,
-- 從 judge_score_details 表獲取詳細評分
(SELECT GROUP_CONCAT(CONCAT(jsd.rule_name, ':', jsd.score) SEPARATOR ',')
FROM judge_score_details jsd
WHERE jsd.judge_score_id = js.id) as score_details
FROM judge_scores js
JOIN judges j ON js.judge_id = j.id
JOIN teams t ON js.app_id = t.id
JOIN users u ON t.leader_id = u.id
WHERE js.competition_id = ?
UNION ALL
SELECT
js.id,
js.judge_id,
js.app_id,
js.total_score,
js.comments,
js.submitted_at,
j.name as judge_name,
j.title as judge_title,
j.department as judge_department,
p.title as app_name,
p.team_id as creator_id,
t.name as creator_name,
t.department as creator_department,
'proposal' as participant_type,
-- 從 judge_score_details 表獲取詳細評分
(SELECT GROUP_CONCAT(CONCAT(jsd.rule_name, ':', jsd.score) SEPARATOR ',')
FROM judge_score_details jsd
WHERE jsd.judge_score_id = js.id) as score_details
FROM judge_scores js
JOIN judges j ON js.judge_id = j.id
JOIN proposals p ON js.app_id = p.id
JOIN teams t ON p.team_id = t.id
WHERE js.competition_id = ?
ORDER BY submitted_at DESC
`;
return await db.query(sql, [competitionId, competitionId, competitionId]);
}
// 提交團隊評分(使用應用評分表,但標記為團隊類型)
static async submitTeamScore(scoreData: Omit<AppJudgeScore, 'id' | 'submitted_at'> & { teamId: string }): Promise<AppJudgeScore> {
// 創建一個虛擬的應用ID來存儲團隊評分
// 格式team_{teamId} 以便識別這是團隊評分
const virtualAppId = `team_${scoreData.teamId}`;
const sql = `
INSERT INTO app_judge_scores (id, judge_id, app_id, innovation_score, technical_score, usability_score, presentation_score, impact_score, total_score, comments)
VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
innovation_score = VALUES(innovation_score),
technical_score = VALUES(technical_score),
usability_score = VALUES(usability_score),
presentation_score = VALUES(presentation_score),
impact_score = VALUES(impact_score),
total_score = VALUES(total_score),
comments = VALUES(comments),
submitted_at = CURRENT_TIMESTAMP
`;
const params = [
scoreData.judge_id,
virtualAppId, // 使用虛擬應用ID
scoreData.innovation_score,
scoreData.technical_score,
scoreData.usability_score,
scoreData.presentation_score,
scoreData.impact_score,
scoreData.total_score,
scoreData.comments || null
];
await db.insert(sql, params);
return await this.getAppScore(scoreData.judge_id, virtualAppId) as AppJudgeScore;
}
// 獲取競賽評分統計
static async getCompetitionScoreStats(competitionId: string): Promise<{
totalScores: number;
completedScores: number;
pendingScores: number;
completionRate: number;
totalParticipants: number;
}> {
// 獲取競賽參與者數量
const participantsSql = `
SELECT
(SELECT COUNT(*) FROM competition_apps WHERE competition_id = ?) +
(SELECT COUNT(*) FROM competition_teams WHERE competition_id = ?) +
(SELECT COUNT(*) FROM competition_proposals WHERE competition_id = ?) as total_participants
`;
const participantsResult = await db.queryOne<{ total_participants: number }>(participantsSql, [competitionId, competitionId, competitionId]);
const totalParticipants = participantsResult?.total_participants || 0;
// 獲取評審數量
const judgesSql = 'SELECT COUNT(*) as judge_count FROM competition_judges WHERE competition_id = ?';
const judgesResult = await db.queryOne<{ judge_count: number }>(judgesSql, [competitionId]);
const judgeCount = judgesResult?.judge_count || 0;
// 計算總評分項目數
const totalScores = totalParticipants * judgeCount;
// 獲取已完成評分數量
const completedScoresSql = `
SELECT COUNT(*) as completed_count FROM judge_scores
WHERE competition_id = ?
`;
const completedResult = await db.queryOne<{ completed_count: number }>(completedScoresSql, [competitionId]);
const completedScores = completedResult?.completed_count || 0;
const pendingScores = Math.max(0, totalScores - completedScores);
const completionRate = totalScores > 0 ? Math.round((completedScores / totalScores) * 100) : 0;
return {
totalScores,
completedScores,
pendingScores,
completionRate,
totalParticipants
};
}
// 獲取評審的評分記錄
static async getJudgeScores(judgeId: string, competitionId?: string): Promise<any[]> {
let sql = `
SELECT
ajs.id,
ajs.judge_id,
ajs.app_id,
ajs.innovation_score,
ajs.technical_score,
ajs.usability_score,
ajs.presentation_score,
ajs.impact_score,
ajs.total_score,
ajs.comments,
ajs.submitted_at,
a.name as app_name,
a.creator_id,
u.name as creator_name,
u.department as creator_department,
'app' as participant_type
FROM app_judge_scores ajs
JOIN apps a ON ajs.app_id = a.id
JOIN users u ON a.creator_id = u.id
WHERE ajs.judge_id = ?
`;
const params = [judgeId];
if (competitionId) {
sql += ' AND EXISTS (SELECT 1 FROM competition_apps ca WHERE ca.app_id = a.id AND ca.competition_id = ?)';
params.push(competitionId);
}
sql += `
UNION ALL
SELECT
pjs.id,
pjs.judge_id,
pjs.proposal_id as app_id,
pjs.problem_identification_score as innovation_score,
pjs.solution_feasibility_score as technical_score,
pjs.innovation_score as usability_score,
pjs.impact_score as presentation_score,
pjs.presentation_score as impact_score,
pjs.total_score,
pjs.comments,
pjs.submitted_at,
p.title as app_name,
p.team_id as creator_id,
t.name as creator_name,
t.department as creator_department,
'proposal' as participant_type
FROM proposal_judge_scores pjs
JOIN proposals p ON pjs.proposal_id = p.id
JOIN teams t ON p.team_id = t.id
WHERE pjs.judge_id = ?
`;
params.push(judgeId);
if (competitionId) {
sql += ' AND EXISTS (SELECT 1 FROM competition_proposals cp WHERE cp.proposal_id = p.id AND cp.competition_id = ?)';
params.push(competitionId);
}
sql += ' ORDER BY submitted_at DESC';
return await db.query(sql, params);
}
// 刪除評分記錄
static async deleteScore(scoreId: string, scoreType: 'app' | 'proposal'): Promise<boolean> {
const tableName = scoreType === 'app' ? 'app_judge_scores' : 'proposal_judge_scores';
const sql = `DELETE FROM ${tableName} WHERE id = ?`;
const result = await db.delete(sql, [scoreId]);
return result.affectedRows > 0;
}
// 更新評分記錄
static async updateAppScore(scoreId: string, updates: Partial<AppJudgeScore>): Promise<boolean> {
const fields = Object.keys(updates).filter(key =>
key !== 'id' &&
key !== 'judge_id' &&
key !== 'app_id' &&
key !== 'submitted_at'
);
if (fields.length === 0) return true;
const setClause = fields.map(field => `${field} = ?`).join(', ');
const values = fields.map(field => (updates as any)[field]);
const sql = `UPDATE app_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`;
const result = await db.update(sql, [...values, scoreId]);
return result.affectedRows > 0;
}
// 更新提案評分記錄
static async updateProposalScore(scoreId: string, updates: Partial<ProposalJudgeScore>): Promise<boolean> {
const fields = Object.keys(updates).filter(key =>
key !== 'id' &&
key !== 'judge_id' &&
key !== 'proposal_id' &&
key !== 'submitted_at'
);
if (fields.length === 0) return true;
const setClause = fields.map(field => `${field} = ?`).join(', ');
const values = fields.map(field => (updates as any)[field]);
const sql = `UPDATE proposal_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`;
const result = await db.update(sql, [...values, scoreId]);
return result.affectedRows > 0;
}
}
// =====================================================