// 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;