Files
tk-factory-services/system1-factory/api/models/meetingModel.js
Hyungi Ahn 184cdd6aa8 fix(tkfb): project_code → job_no 컬럼명 수정 (500 에러 해결)
projects 테이블에 project_code 컬럼이 없고 job_no가 올바른 컬럼명.
백엔드 SQL에서는 pr.job_no AS project_code alias 사용,
프론트 드롭다운에서는 p.job_no로 직접 참조.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 08:18:49 +09:00

224 lines
7.7 KiB
JavaScript

// models/meetingModel.js
const { getDb } = require('../dbPool');
const MeetingModel = {
// === 회의록 ===
async getAll(filters = {}) {
const db = await getDb();
let sql = `
SELECT m.*,
su.name AS created_by_name,
(SELECT COUNT(*) FROM meeting_attendees WHERE meeting_id = m.meeting_id) AS attendee_count,
(SELECT COUNT(*) FROM meeting_agenda_items WHERE meeting_id = m.meeting_id) AS agenda_count,
(SELECT COUNT(*) FROM meeting_agenda_items
WHERE meeting_id = m.meeting_id AND status IN ('open','in_progress')) AS open_action_count
FROM meeting_minutes m
LEFT JOIN sso_users su ON m.created_by = su.user_id
WHERE 1=1
`;
const params = [];
if (filters.year && filters.month) {
sql += ' AND YEAR(m.meeting_date) = ? AND MONTH(m.meeting_date) = ?';
params.push(filters.year, filters.month);
} else if (filters.year) {
sql += ' AND YEAR(m.meeting_date) = ?';
params.push(filters.year);
}
if (filters.search) {
sql += ' AND (m.title LIKE ? OR m.summary LIKE ?)';
params.push(`%${filters.search}%`, `%${filters.search}%`);
}
sql += ' ORDER BY m.meeting_date DESC, m.created_at DESC';
const [rows] = await db.query(sql, params);
return rows;
},
async getById(meetingId) {
const db = await getDb();
// 회의 기본정보
const [meetings] = await db.query(`
SELECT m.*, su.name AS created_by_name
FROM meeting_minutes m
LEFT JOIN sso_users su ON m.created_by = su.user_id
WHERE m.meeting_id = ?
`, [meetingId]);
if (meetings.length === 0) return null;
const meeting = meetings[0];
// 참석자
const [attendees] = await db.query(`
SELECT ma.id, ma.user_id, su.name, su.username, su.department
FROM meeting_attendees ma
JOIN sso_users su ON ma.user_id = su.user_id
WHERE ma.meeting_id = ?
ORDER BY su.name
`, [meetingId]);
meeting.attendees = attendees;
// 안건
const [items] = await db.query(`
SELECT ai.*, pr.project_name, pr.job_no AS project_code,
ms.milestone_name, ms.milestone_date,
su.name AS responsible_name
FROM meeting_agenda_items ai
LEFT JOIN projects pr ON ai.project_id = pr.project_id
LEFT JOIN schedule_milestones ms ON ai.milestone_id = ms.milestone_id
LEFT JOIN sso_users su ON ai.responsible_user_id = su.user_id
WHERE ai.meeting_id = ?
ORDER BY ai.display_order, ai.item_id
`, [meetingId]);
meeting.items = items;
return meeting;
},
async create(data) {
const db = await getDb();
const [result] = await db.query(
`INSERT INTO meeting_minutes (meeting_date, meeting_time, title, location, summary, status, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[data.meeting_date, data.meeting_time || null, data.title,
data.location || null, data.summary || null, 'draft', data.created_by]
);
const meetingId = result.insertId;
// 참석자 추가
if (data.attendees && data.attendees.length > 0) {
const values = data.attendees.map(userId => [meetingId, userId]);
await db.query('INSERT INTO meeting_attendees (meeting_id, user_id) VALUES ?', [values]);
}
return meetingId;
},
async update(meetingId, data) {
const db = await getDb();
const fields = [];
const params = [];
const allowed = ['meeting_date', 'meeting_time', 'title', 'location', 'summary'];
for (const key of allowed) {
if (data[key] !== undefined) { fields.push(`${key} = ?`); params.push(data[key]); }
}
if (fields.length > 0) {
fields.push('updated_at = NOW()');
params.push(meetingId);
await db.query(`UPDATE meeting_minutes SET ${fields.join(', ')} WHERE meeting_id = ?`, params);
}
// 참석자 재설정
if (data.attendees !== undefined) {
await db.query('DELETE FROM meeting_attendees WHERE meeting_id = ?', [meetingId]);
if (data.attendees.length > 0) {
const values = data.attendees.map(userId => [meetingId, userId]);
await db.query('INSERT INTO meeting_attendees (meeting_id, user_id) VALUES ?', [values]);
}
}
},
async publish(meetingId) {
const db = await getDb();
await db.query(
"UPDATE meeting_minutes SET status = 'published', updated_at = NOW() WHERE meeting_id = ?",
[meetingId]
);
},
async unpublish(meetingId) {
const db = await getDb();
await db.query(
"UPDATE meeting_minutes SET status = 'draft', updated_at = NOW() WHERE meeting_id = ?",
[meetingId]
);
},
async delete(meetingId) {
const db = await getDb();
await db.query('DELETE FROM meeting_minutes WHERE meeting_id = ?', [meetingId]);
},
// === 안건 ===
async addItem(meetingId, data) {
const db = await getDb();
const [result] = await db.query(
`INSERT INTO meeting_agenda_items
(meeting_id, project_id, milestone_id, item_type, content, decision, action_required,
responsible_user_id, due_date, status, display_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[meetingId, data.project_id || null, data.milestone_id || null,
data.item_type || 'other', data.content, data.decision || null,
data.action_required || null, data.responsible_user_id || null,
data.due_date || null, data.status || 'open', data.display_order || 0]
);
return result.insertId;
},
async updateItem(itemId, data) {
const db = await getDb();
const fields = [];
const params = [];
const allowed = ['project_id', 'milestone_id', 'item_type', 'content', 'decision',
'action_required', 'responsible_user_id', 'due_date', 'status', 'display_order'];
for (const key of allowed) {
if (data[key] !== undefined) { fields.push(`${key} = ?`); params.push(data[key]); }
}
if (fields.length === 0) return;
fields.push('updated_at = NOW()');
params.push(itemId);
await db.query(`UPDATE meeting_agenda_items SET ${fields.join(', ')} WHERE item_id = ?`, params);
},
async deleteItem(itemId) {
const db = await getDb();
await db.query('DELETE FROM meeting_agenda_items WHERE item_id = ?', [itemId]);
},
async updateItemStatus(itemId, status) {
const db = await getDb();
await db.query(
'UPDATE meeting_agenda_items SET status = ?, updated_at = NOW() WHERE item_id = ?',
[status, itemId]
);
},
// === 미완료 조치사항 ===
async getActionItems(filters = {}) {
const db = await getDb();
let sql = `
SELECT ai.*, m.title AS meeting_title, m.meeting_date,
pr.project_name, pr.job_no AS project_code,
su.name AS responsible_name
FROM meeting_agenda_items ai
JOIN meeting_minutes m ON ai.meeting_id = m.meeting_id
LEFT JOIN projects pr ON ai.project_id = pr.project_id
LEFT JOIN sso_users su ON ai.responsible_user_id = su.user_id
WHERE ai.item_type IN ('action_item', 'issue', 'decision')
`;
const params = [];
if (filters.status) {
sql += ' AND ai.status = ?';
params.push(filters.status);
} else {
sql += " AND ai.status IN ('open', 'in_progress')";
}
if (filters.responsible_user_id) {
sql += ' AND ai.responsible_user_id = ?';
params.push(filters.responsible_user_id);
}
sql += ' ORDER BY ai.due_date ASC, m.meeting_date DESC';
const [rows] = await db.query(sql, params);
return rows;
},
// 회의록 상태 조회 (published 체크용)
async getStatus(meetingId) {
const db = await getDb();
const [rows] = await db.query(
'SELECT status FROM meeting_minutes WHERE meeting_id = ?',
[meetingId]
);
return rows.length > 0 ? rows[0].status : null;
}
};
module.exports = MeetingModel;