148 lines
3.8 KiB
JavaScript
148 lines
3.8 KiB
JavaScript
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,
|
||
|
|
};
|