Добавлена ​​функция сохранения истории обработки территорий

This commit is contained in:
2025-09-13 01:24:33 +03:00
parent f3f13c073a
commit ff393417a1
15 changed files with 1393 additions and 379 deletions

View File

@@ -0,0 +1,32 @@
const getTable = require("../middleware/genReportTerritories");
class generatorReportTerritoriesController {
async getTable(req, res) {
if (req.possibilities.can_add_territory) {
let result = await getTable();
if (result) {
res.setHeader(
'Content-Disposition',
`attachment; filename*=UTF-8''${encodeURIComponent('Опрацювання_територій.xlsx')}`
);
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
return res.status(200).send(result);
} else {
return res
.status(404)
.send({ message: 'Table creation error.' });
}
} else {
return res
.status(403)
.send({ message: 'The user does not have enough rights.' });
}
}
}
module.exports = new generatorReportTerritoriesController();

View File

@@ -0,0 +1,241 @@
const db = require("../config/db");
const ExcelJS = require('exceljs');
async function getTable() {
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Опрацювання територій');
// Обычная тонкая рамка
function setThinBorder(cell) {
cell.border = {
top: { style: 'thin', color: { argb: 'FF000000' } },
left: { style: 'thin', color: { argb: 'FF000000' } },
bottom: { style: 'thin', color: { argb: 'FF000000' } },
right: { style: 'thin', color: { argb: 'FF000000' } },
};
}
// Жирные рамки для Вісників
function setTopCellBorder(cell) {
cell.border = {
top: { style: 'thick', color: { argb: 'FF000000' } },
left: { style: 'thick', color: { argb: 'FF000000' } },
right: { style: 'thick', color: { argb: 'FF000000' } },
bottom: { style: 'thin', color: { argb: 'FF000000' } },
};
}
function setStartColBorder(cell) {
cell.border = {
top: { style: 'thin', color: { argb: 'FF000000' } },
left: { style: 'thick', color: { argb: 'FF000000' } },
bottom: { style: 'thick', color: { argb: 'FF000000' } },
right: { style: 'thin', color: { argb: 'FF000000' } },
};
}
function setEndColBorder(cell) {
cell.border = {
top: { style: 'thin', color: { argb: 'FF000000' } },
left: { style: 'thin', color: { argb: 'FF000000' } },
bottom: { style: 'thick', color: { argb: 'FF000000' } },
right: { style: 'thick', color: { argb: 'FF000000' } },
};
}
sheet.addRow([]);
sheet.addRow([]);
// № объекта
sheet.getCell('A1').value = '№';
sheet.mergeCells('A1:A2');
sheet.getCell('A1').alignment = { vertical: 'middle', horizontal: 'center' };
sheet.getCell('A1').font = { bold: true };
setThinBorder(sheet.getCell('A1'));
sheet.getColumn(1).width = 5;
// "Об’єкт"
sheet.getCell('B1').value = 'Територія';
sheet.mergeCells('B1:B2');
sheet.getCell('B1').alignment = { vertical: 'middle', horizontal: 'center' };
sheet.getCell('B1').font = { bold: true };
setThinBorder(sheet.getCell('B1'));
sheet.getColumn(2).width = 40;
// "Остання дата опрацювання"
sheet.getCell('C1').value = 'Остання дата опрацювання';
sheet.mergeCells('C1:C2');
sheet.getCell('C1').alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
sheet.getCell('C1').font = { bold: true };
setThinBorder(sheet.getCell('C1'));
sheet.getColumn(3).width = 20;
// Вісники
for (let i = 0; i < 4; i++) {
const startCol = 4 + i * 2;
const endCol = startCol + 1;
sheet.mergeCells(1, startCol, 1, endCol);
const topCell = sheet.getCell(1, startCol);
topCell.value = `Вісник (група) ${i + 1}`;
topCell.alignment = { horizontal: 'center', vertical: 'middle' };
topCell.font = { bold: true };
setThinBorder(topCell);
const recvCell = sheet.getCell(2, startCol);
recvCell.value = 'Дата отримання';
recvCell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
recvCell.font = { bold: true };
setThinBorder(recvCell);
const procCell = sheet.getCell(2, endCol);
procCell.value = 'Дата опрацювання';
procCell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
procCell.font = { bold: true };
setThinBorder(procCell);
sheet.getColumn(startCol).width = 16;
sheet.getColumn(endCol).width = 16;
}
const objects = await new Promise((resolve, reject) => {
db.all(`
SELECT
e.id as object_id,
e.entrance_number as number,
h.number as house_number,
h.title as house_title,
NULL as settlement,
'entrance' as type
FROM entrance e
JOIN house h ON e.house_id = h.id
UNION ALL
SELECT
hm.id as object_id,
hm.number as number,
NULL as house_number,
hm.title as house_title,
hm.settlement,
'homestead' as type
FROM homestead hm
`, (err, rows) => {
if (err) return reject(err);
resolve(rows);
});
});
let currentRow = 3;
let objIndex = 1;
for (const obj of objects) {
const table = obj.type === 'entrance' ? 'entrance_history' : 'homestead_history';
const idField = obj.type === 'entrance' ? 'entrance_id' : 'homestead_id';
const history = await new Promise((resolve, reject) => {
db.all(`
SELECT date_start, date_end, name, group_id
FROM ${table}
WHERE ${idField} = ?
ORDER BY date_start DESC
LIMIT 4
`, [obj.object_id], (err, rows) => {
if (err) return reject(err);
resolve(rows);
});
});
// № объекта
const numCell = sheet.getCell(currentRow, 1);
numCell.value = objIndex;
numCell.alignment = { vertical: 'middle', horizontal: 'center' };
setThinBorder(numCell);
sheet.mergeCells(currentRow, 1, currentRow + 1, 1);
// Название объекта
let objectName;
if (obj.type === 'homestead' && obj.settlement) {
objectName = `(${obj.settlement}) ${obj.house_title} ${obj.number}`;
} else if (obj.type === 'homestead') {
objectName = `${obj.house_title} ${obj.number}`;
} else {
objectName = `${obj.house_title} ${obj.house_number} (П. ${obj.number + 1})`;
}
const objCell = sheet.getCell(currentRow, 2);
objCell.value = objectName;
sheet.mergeCells(currentRow, 2, currentRow + 1, 2);
objCell.alignment = { vertical: 'middle', horizontal: 'left' };
setThinBorder(objCell);
// Последняя дата обработки
let lastDate = null;
for (const h of history) {
if (h.date_end && h.date_end !== 0) {
lastDate = new Date(h.date_end);
break;
}
}
const lastDateCell = sheet.getCell(currentRow, 3);
lastDateCell.value = lastDate;
lastDateCell.numFmt = 'dd.mm.yyyy';
sheet.mergeCells(currentRow, 3, currentRow + 1, 3);
lastDateCell.alignment = { vertical: 'middle', horizontal: 'center' };
setThinBorder(lastDateCell);
// История Вісників
history.forEach((h, idx) => {
const startCol = 4 + idx * 2;
const endCol = startCol + 1;
let name = h.name;
if (h.name === 'Групова') name = `${h.name} (${h.group_id})`;
// Верхняя ячейка имени
const nameCell = sheet.getCell(currentRow, startCol);
sheet.mergeCells(currentRow, startCol, currentRow, endCol);
nameCell.value = name;
nameCell.alignment = { horizontal: 'center' };
setTopCellBorder(nameCell);
// Дата получения
const startDate = h.date_start ? new Date(h.date_start) : null;
const recvCell = sheet.getCell(currentRow + 1, startCol);
recvCell.value = startDate;
recvCell.numFmt = 'dd.mm.yyyy';
recvCell.alignment = { horizontal: 'center' };
setStartColBorder(recvCell);
// Дата опрацювання
const endDate = h.date_end ? new Date(h.date_end) : null;
const procCell = sheet.getCell(currentRow + 1, endCol);
procCell.value = endDate;
procCell.numFmt = 'dd.mm.yyyy';
procCell.alignment = { horizontal: 'center' };
setEndColBorder(procCell);
});
// Чётное затемнение только для блока данных
if (objIndex % 2 === 0) {
for (let col = 1; col <= 11; col++) {
sheet.getCell(currentRow, col).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFEFEFEF' },
};
sheet.getCell(currentRow + 1, col).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFEFEFEF' },
};
}
}
currentRow += 2;
objIndex++;
}
const buffer = await workbook.xlsx.writeBuffer();
return buffer;
}
module.exports = getTable;

1025
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
"puppeteer": "^24.4.0",
"sharp": "^0.33.5",
"sqlite3": "^5.1.7",
"web-push": "^3.6.7"
"web-push": "^3.6.7",
"exceljs": "^4.4.0"
}
}

View File

@@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();
const generatorReportTerritoriesController = require('../controllers/generator.report.territories.controller');
const authenticate = require("../middleware/auth");
router
.route('/')
.get(authenticate, generatorReportTerritoriesController.getTable);
module.exports = router;

View File

@@ -15,6 +15,7 @@ const standRoutes = require('./stand.routes');
const pushRoutes = require('./push.routes');
const generatorCardsRoutes = require('./generator.cards.routes');
const generatorReportTerritoriesRoutes = require('./generator.report.territories.routes');
router.use('/auth', authRoutes);
router.use('/sheeps?', sheepsRoutes);
@@ -30,5 +31,6 @@ router.use('/stand', standRoutes);
router.use('/push', pushRoutes);
router.use('/generator/cards', generatorCardsRoutes);
router.use('/generator/report/territories', generatorReportTerritoriesRoutes);
module.exports = router;