const fs = require('fs'); const path = require('path'); const DATA_DIR = path.join(__dirname, 'data'); const UPLOADS_DIR = path.join(__dirname, 'uploads', 'issues'); const ISSUES_FILE = path.join(DATA_DIR, 'issues.json'); function ensureDirs() { if (!fs.existsSync(DATA_DIR)) { fs.mkdirSync(DATA_DIR, { recursive: true }); } if (!fs.existsSync(UPLOADS_DIR)) { fs.mkdirSync(UPLOADS_DIR, { recursive: true }); } if (!fs.existsSync(ISSUES_FILE)) { fs.writeFileSync(ISSUES_FILE, JSON.stringify([], null, 2), 'utf-8'); } } function getAllIssues() { ensureDirs(); try { const raw = fs.readFileSync(ISSUES_FILE, 'utf-8'); const list = JSON.parse(raw); return Array.isArray(list) ? list : []; } catch (err) { console.error('Error reading issues.json:', err); return []; } } function saveIssues(issues) { ensureDirs(); const tmpFile = `${ISSUES_FILE}.tmp`; fs.writeFileSync(tmpFile, JSON.stringify(issues, null, 2), 'utf-8'); fs.renameSync(tmpFile, ISSUES_FILE); } function generateIssueId() { const now = new Date(); const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, '0'); const d = String(now.getDate()).padStart(2, '0'); const prefix = `ISS-${y}${m}${d}`; const all = getAllIssues(); const todayCount = all.filter(item => item.id && item.id.startsWith(prefix)).length + 1; const seq = String(todayCount).padStart(3, '0'); return `${prefix}-${seq}`; } function createIssue(data, file) { ensureDirs(); const all = getAllIssues(); const id = generateIssueId(); const now = new Date().toISOString(); let attachment = null; if (file) { attachment = { filename: file.filename, originalname: file.originalname, size: file.size, mimetype: file.mimetype, url: `/uploads/issues/${file.filename}`, }; } const newIssue = { id, title: (data.title || 'Không có tiêu đề').trim(), category: data.category || 'SAI_LECH_SO_LIEU', // SAI_LECH_SO_LIEU, LOI_GIAO_DIEN, LOI_KET_NOI, DE_XUAT, YEU_CAU_MOI, KHAC module: data.module || 'CHUNG', // NGOAI_TRU, NOI_TRU, CHUYEN_VIEN, THEO_DOI, CHUNG priority: data.priority || 'BINH_THUONG', // THAP, BINH_THUONG, CAO, KHAN_CAP database: data.database || '', databaseName: data.databaseName || data.database || '', period: data.period || '', reporter: (data.reporter || 'Cán bộ giao ban').trim(), department: (data.department || '').trim(), description: (data.description || '').trim(), status: 'CHO_XU_LY', // CHO_XU_LY, DANG_XU_LY, DA_XU_LY, DONG handlerNotes: '', resolvedBy: '', resolvedAt: null, createdAt: now, updatedAt: now, attachment, }; all.unshift(newIssue); saveIssues(all); return newIssue; } function updateIssue(id, updates) { const all = getAllIssues(); const idx = all.findIndex(item => item.id === id); if (idx === -1) { throw new Error(`Không tìm thấy Issue với mã: ${id}`); } const now = new Date().toISOString(); const cur = all[idx]; const updated = { ...cur, ...updates, updatedAt: now, }; if (updates.status === 'DA_XU_LY' && cur.status !== 'DA_XU_LY') { updated.resolvedAt = now; } all[idx] = updated; saveIssues(all); return updated; } function deleteIssue(id) { const all = getAllIssues(); const idx = all.findIndex(item => item.id === id); if (idx === -1) { throw new Error(`Không tìm thấy Issue với mã: ${id}`); } const item = all[idx]; if (item.attachment && item.attachment.filename) { const fPath = path.join(UPLOADS_DIR, item.attachment.filename); if (fs.existsSync(fPath)) { try { fs.unlinkSync(fPath); } catch (e) { /* ignore */ } } } all.splice(idx, 1); saveIssues(all); return true; } module.exports = { UPLOADS_DIR, ensureDirs, getAllIssues, createIssue, updateIssue, deleteIssue, };