'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;