feat(tkfb): 공정표 + 생산회의록 시스템 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
223
system1-factory/api/models/meetingModel.js
Normal file
223
system1-factory/api/models/meetingModel.js
Normal file
@@ -0,0 +1,223 @@
|
||||
// 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.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.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;
|
||||
Reference in New Issue
Block a user