425 lines
9.8 KiB
JavaScript
425 lines
9.8 KiB
JavaScript
'use strict';
|
|
|
|
require('../connector');
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
|
|
const {
|
|
gerarOrdemServicoPdf,
|
|
gerarRelatorioOrdensServicoPdf
|
|
} = require('../services/orionPdfService');
|
|
|
|
function getPool() {
|
|
if (!global.ConexaoMySQL_Oriontard) {
|
|
throw new Error('Pool do banco Oriontard não foi inicializado.');
|
|
}
|
|
|
|
return global.ConexaoMySQL_Oriontard;
|
|
}
|
|
|
|
function dbQuery(sql, params = []) {
|
|
return new Promise((resolve, reject) => {
|
|
getPool().query(sql, params, (error, results, fields) => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
|
|
resolve({ results, fields });
|
|
});
|
|
});
|
|
}
|
|
|
|
function positiveInteger(value, fieldName) {
|
|
const number = Number(value);
|
|
|
|
if (!Number.isInteger(number) || number <= 0) {
|
|
const error = new Error(`${fieldName} inválido.`);
|
|
error.httpStatus = 400;
|
|
throw error;
|
|
}
|
|
|
|
return number;
|
|
}
|
|
|
|
function sendError(req, res, error) {
|
|
const status = error.httpStatus || 500;
|
|
const message = status >= 500
|
|
? 'Erro interno ao gerar o relatório.'
|
|
: error.message;
|
|
|
|
console.error(
|
|
`${new Date().toUTCString()} - ${req.originalUrl} ${status} error`,
|
|
error
|
|
);
|
|
|
|
if (res.headersSent) {
|
|
res.destroy(error);
|
|
return;
|
|
}
|
|
|
|
res.status(status).json({
|
|
status,
|
|
error: {
|
|
code: error.code || null,
|
|
message
|
|
},
|
|
response: null
|
|
});
|
|
}
|
|
|
|
function dateToSql(date) {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
function parseDateOrDefault(value, fallback) {
|
|
if (!value) {
|
|
return fallback;
|
|
}
|
|
|
|
const date = new Date(`${value}T00:00:00`);
|
|
|
|
if (Number.isNaN(date.getTime())) {
|
|
const error = new Error('Data de filtro inválida.');
|
|
error.httpStatus = 400;
|
|
throw error;
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
async function loadFullOrder(id) {
|
|
const [
|
|
{ results: orders },
|
|
{ results: activities },
|
|
{ results: technicians },
|
|
{ results: materials },
|
|
{ results: odometers },
|
|
{ results: payments }
|
|
] = await Promise.all([
|
|
dbQuery(
|
|
`
|
|
SELECT
|
|
os.*,
|
|
t.nome AS tecnico_responsavel_nome,
|
|
GREATEST(os.valor_total - os.valor_pago, 0) AS valor_pendente
|
|
FROM manut_ordens_servico os
|
|
LEFT JOIN manut_tecnicos t
|
|
ON t.id = os.tecnico_responsavel_id
|
|
WHERE os.id = ?
|
|
AND os.excluido_em IS NULL
|
|
LIMIT 1;
|
|
`,
|
|
[id]
|
|
),
|
|
dbQuery(
|
|
`
|
|
SELECT *
|
|
FROM manut_ordem_servico_atividades
|
|
WHERE ordem_servico_id = ?
|
|
ORDER BY data_atividade, ordem_exibicao, id;
|
|
`,
|
|
[id]
|
|
),
|
|
dbQuery(
|
|
`
|
|
SELECT
|
|
at.atividade_id,
|
|
at.tecnico_id,
|
|
at.papel,
|
|
at.horas_trabalhadas_expediente,
|
|
at.horas_trabalhadas_extras,
|
|
t.nome AS tecnico_nome
|
|
FROM manut_ordem_servico_atividade_tecnicos at
|
|
INNER JOIN manut_tecnicos t
|
|
ON t.id = at.tecnico_id
|
|
INNER JOIN manut_ordem_servico_atividades a
|
|
ON a.id = at.atividade_id
|
|
WHERE a.ordem_servico_id = ?
|
|
ORDER BY t.nome;
|
|
`,
|
|
[id]
|
|
),
|
|
dbQuery(
|
|
`
|
|
SELECT mat.*
|
|
FROM manut_ordem_servico_atividade_materiais mat
|
|
INNER JOIN manut_ordem_servico_atividades a
|
|
ON a.id = mat.atividade_id
|
|
WHERE a.ordem_servico_id = ?
|
|
ORDER BY mat.id;
|
|
`,
|
|
[id]
|
|
),
|
|
dbQuery(
|
|
`
|
|
SELECT *
|
|
FROM manut_equipamento_odometro_leituras
|
|
WHERE ordem_servico_id = ?
|
|
ORDER BY leitura_em, id;
|
|
`,
|
|
[id]
|
|
),
|
|
dbQuery(
|
|
`
|
|
SELECT *
|
|
FROM manut_ordem_servico_pagamentos
|
|
WHERE ordem_servico_id = ?
|
|
AND cancelado_em IS NULL
|
|
ORDER BY data_pagamento, id;
|
|
`,
|
|
[id]
|
|
)
|
|
]);
|
|
|
|
if (!orders.length) {
|
|
const error = new Error('Ordem de serviço não encontrada.');
|
|
error.httpStatus = 404;
|
|
throw error;
|
|
}
|
|
|
|
const techniciansByActivity = new Map();
|
|
technicians.forEach(item => {
|
|
if (!techniciansByActivity.has(item.atividade_id)) {
|
|
techniciansByActivity.set(item.atividade_id, []);
|
|
}
|
|
|
|
techniciansByActivity.get(item.atividade_id).push(item);
|
|
});
|
|
|
|
const materialsByActivity = new Map();
|
|
materials.forEach(item => {
|
|
if (!materialsByActivity.has(item.atividade_id)) {
|
|
materialsByActivity.set(item.atividade_id, []);
|
|
}
|
|
|
|
materialsByActivity.get(item.atividade_id).push(item);
|
|
});
|
|
|
|
const nestedActivities = activities.map(activity => ({
|
|
...activity,
|
|
tecnicos: techniciansByActivity.get(activity.id) || [],
|
|
materiais: materialsByActivity.get(activity.id) || []
|
|
}));
|
|
|
|
return {
|
|
...orders[0],
|
|
atividades: nestedActivities,
|
|
odometros_da_os: odometers,
|
|
pagamentos: payments
|
|
};
|
|
}
|
|
|
|
function buildReportFilters(query) {
|
|
const now = new Date();
|
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
|
|
|
const dataInicio = parseDateOrDefault(query.data_inicio, firstDay);
|
|
const dataFim = parseDateOrDefault(query.data_fim, lastDay);
|
|
|
|
if (dataFim < dataInicio) {
|
|
const error = new Error('A data final não pode ser anterior à data inicial.');
|
|
error.httpStatus = 400;
|
|
throw error;
|
|
}
|
|
|
|
const criterios = {
|
|
data_entrada: {
|
|
coluna: 'os.data_entrada',
|
|
label: 'data de entrada'
|
|
},
|
|
data_finalizacao: {
|
|
coluna: 'os.data_finalizacao',
|
|
label: 'data de finalização'
|
|
},
|
|
data_retirada: {
|
|
coluna: 'os.data_retirada',
|
|
label: 'data de retirada'
|
|
}
|
|
};
|
|
|
|
const criterio = criterios[query.criterio] || criterios.data_finalizacao;
|
|
|
|
return {
|
|
dataInicio,
|
|
dataFim,
|
|
dataInicioSql: dateToSql(dataInicio),
|
|
dataFimSql: dateToSql(dataFim),
|
|
criterio,
|
|
clienteId: query.cliente_id
|
|
? positiveInteger(query.cliente_id, 'Cliente')
|
|
: null,
|
|
equipamentoId: query.equipamento_id
|
|
? positiveInteger(query.equipamento_id, 'Equipamento')
|
|
: null,
|
|
status: query.status || null,
|
|
statusPagamento: query.status_pagamento || null,
|
|
tipo: query.tipo || null,
|
|
busca: query.busca ? String(query.busca).trim() : null,
|
|
cobrancaAberta:
|
|
query.cobranca_aberta === '1' ||
|
|
query.cobranca_aberta === 'true'
|
|
};
|
|
}
|
|
|
|
async function loadReportOrders(query) {
|
|
const filtros = buildReportFilters(query);
|
|
const where = [
|
|
'os.excluido_em IS NULL',
|
|
"os.status NOT IN ('RASCUNHO', 'CANCELADA')",
|
|
`${filtros.criterio.coluna} >= ?`,
|
|
`${filtros.criterio.coluna} < DATE_ADD(?, INTERVAL 1 DAY)`
|
|
];
|
|
|
|
const params = [
|
|
filtros.dataInicioSql,
|
|
filtros.dataFimSql
|
|
];
|
|
|
|
if (filtros.clienteId) {
|
|
where.push('os.cliente_id = ?');
|
|
params.push(filtros.clienteId);
|
|
}
|
|
|
|
if (filtros.equipamentoId) {
|
|
where.push('os.equipamento_id = ?');
|
|
params.push(filtros.equipamentoId);
|
|
}
|
|
|
|
if (filtros.status) {
|
|
where.push('os.status = ?');
|
|
params.push(filtros.status);
|
|
}
|
|
|
|
if (filtros.statusPagamento) {
|
|
where.push('os.status_pagamento = ?');
|
|
params.push(filtros.statusPagamento);
|
|
}
|
|
|
|
if (filtros.tipo) {
|
|
where.push('os.tipo = ?');
|
|
params.push(filtros.tipo);
|
|
}
|
|
|
|
if (filtros.cobrancaAberta) {
|
|
where.push("os.status_pagamento IN ('PENDENTE', 'PARCIAL')");
|
|
}
|
|
|
|
if (filtros.busca) {
|
|
const search = `%${filtros.busca}%`;
|
|
where.push(`(
|
|
CAST(os.id AS CHAR) LIKE ? OR
|
|
os.equipamento_nome_snapshot LIKE ? OR
|
|
os.numero_serie_snapshot LIKE ? OR
|
|
os.problema_relatado LIKE ? OR
|
|
os.cliente_nome_snapshot LIKE ?
|
|
)`);
|
|
|
|
params.push(search, search, search, search, search);
|
|
}
|
|
|
|
const { results } = await dbQuery(
|
|
`
|
|
SELECT
|
|
os.id,
|
|
os.cliente_id,
|
|
os.cliente_nome_snapshot,
|
|
os.equipamento_nome_snapshot,
|
|
os.numero_serie_snapshot,
|
|
os.tipo,
|
|
os.status,
|
|
os.status_pagamento,
|
|
os.data_entrada,
|
|
os.data_finalizacao,
|
|
os.data_retirada,
|
|
os.total_horas_expediente,
|
|
os.total_horas_extras,
|
|
os.valor_mao_obra,
|
|
os.valor_insumos,
|
|
os.valor_desconto,
|
|
os.valor_acrescimo,
|
|
os.valor_total,
|
|
os.valor_pago,
|
|
GREATEST(os.valor_total - os.valor_pago, 0) AS valor_pendente
|
|
FROM manut_ordens_servico os
|
|
WHERE ${where.join(' AND ')}
|
|
ORDER BY
|
|
os.cliente_nome_snapshot,
|
|
${filtros.criterio.coluna},
|
|
os.id;
|
|
`,
|
|
params
|
|
);
|
|
|
|
let clienteNome = null;
|
|
|
|
if (filtros.clienteId) {
|
|
const { results: clientes } = await dbQuery(
|
|
`
|
|
SELECT nome_fantasia
|
|
FROM manut_clientes
|
|
WHERE id = ?
|
|
LIMIT 1;
|
|
`,
|
|
[filtros.clienteId]
|
|
);
|
|
|
|
clienteNome = clientes.length
|
|
? clientes[0].nome_fantasia
|
|
: null;
|
|
}
|
|
|
|
return {
|
|
ordens: results,
|
|
filtros: {
|
|
dataInicio: filtros.dataInicio,
|
|
dataFim: filtros.dataFim,
|
|
criterioLabel: filtros.criterio.label,
|
|
clienteNome
|
|
}
|
|
};
|
|
}
|
|
|
|
var routes = function () {
|
|
router.get('/downloadOrdemServicoPdf/:id', async function (req, res) {
|
|
try {
|
|
const id = positiveInteger(req.params.id, 'Ordem de serviço');
|
|
const interno =
|
|
req.query.interno === '1' ||
|
|
req.query.interno === 'true';
|
|
|
|
const ordem = await loadFullOrder(id);
|
|
|
|
gerarOrdemServicoPdf(res, ordem, {
|
|
interno
|
|
});
|
|
} catch (error) {
|
|
sendError(req, res, error);
|
|
}
|
|
});
|
|
|
|
router.get('/downloadRelatorioOrdensServicoPdf', async function (req, res) {
|
|
try {
|
|
const resultado = await loadReportOrders(req.query || {});
|
|
|
|
gerarRelatorioOrdensServicoPdf(
|
|
res,
|
|
resultado.ordens,
|
|
resultado.filtros
|
|
);
|
|
} catch (error) {
|
|
sendError(req, res, error);
|
|
}
|
|
});
|
|
|
|
return router;
|
|
};
|
|
|
|
module.exports = routes;
|