adicionado criacao de OSs Oriontard
This commit is contained in:
parent
56941e2978
commit
445b7b5812
|
|
@ -124,3 +124,31 @@ global.ExecuteQueryAgro = function ExecuteQueryAgro(sqlQuery, req, res, params =
|
|||
|
||||
//#endregion
|
||||
|
||||
//#region ORIONTARD
|
||||
var ConfigMySQLOriontard = {
|
||||
connectionLimit : 10,
|
||||
host : 'zendioninc.com.br',
|
||||
port : '3306',
|
||||
user : 'zendion',
|
||||
password : 'Z210203!',
|
||||
database : 'oriontard',
|
||||
debug : false,
|
||||
multipleStatements : true
|
||||
};
|
||||
|
||||
global.AbreConexaoOriontardMySQL = function AbreConexaoOriontardMySQL() {
|
||||
global.ConexaoMySQL_Oriontard = mysql.createPool(ConfigMySQLOriontard);
|
||||
console.log("MySQL Conectado! Oriontard");
|
||||
}
|
||||
AbreConexaoOriontardMySQL();
|
||||
|
||||
global.ExecuteQueryOriontard = function ExecuteQueryOriontard(sqlQuery, req, res, params = [], callback = null) {
|
||||
ConexaoMySQL_Oriontard.query(sqlQuery, params, function (error, results, fields) {
|
||||
console.log(`${new Date().toUTCString()} - ${req.originalUrl} ${(error) ? ' 500 error' : ' 200 success'}`);
|
||||
res.send(JSON.stringify({ "status": ((error) ? 500 : 200), "error": error, "response": results }))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ var routes = function () {
|
|||
const produto = results[0];
|
||||
if (produto['idtiposlicenca'] == 6) {
|
||||
const sqlGerarChave = `CALL GerarInserirChave(?, ?, ?, @SerialKey, @idChave);`;
|
||||
mysqlConnection.query(sqlGerarChave, [produto['cpf'], 30, produto['idsistemas']], function (chaveError, resChave) {
|
||||
mysqlConnection.query(sqlGerarChave, [produto['cpf'], 90, produto['idsistemas']], function (chaveError, resChave) {
|
||||
if (chaveError) {
|
||||
return res.status(500).send({ error: 'Erro ao gerar chave.' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,424 @@
|
|||
'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;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -203,6 +203,16 @@
|
|||
"yargs": "^16.2.0"
|
||||
}
|
||||
},
|
||||
"@noble/ciphers": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
|
||||
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="
|
||||
},
|
||||
"@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="
|
||||
},
|
||||
"@panva/asn1.js": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz",
|
||||
|
|
@ -298,6 +308,21 @@
|
|||
"@sendgrid/helpers": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"@swc/helpers": {
|
||||
"version": "0.5.23",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
||||
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
|
||||
"requires": {
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
|
|
@ -510,8 +535,7 @@
|
|||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"optional": true
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "4.0.4",
|
||||
|
|
@ -569,6 +593,29 @@
|
|||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"brotli": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
|
||||
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"browserify-zlib": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
|
||||
"integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
|
||||
"requires": {
|
||||
"pako": "~1.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
|
|
@ -605,6 +652,11 @@
|
|||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -738,6 +790,11 @@
|
|||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"dfa": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
|
||||
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="
|
||||
},
|
||||
"dicer": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz",
|
||||
|
|
@ -997,8 +1054,7 @@
|
|||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"optional": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-text-encoding": {
|
||||
"version": "1.0.3",
|
||||
|
|
@ -1113,6 +1169,22 @@
|
|||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"fontkit": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
|
||||
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
|
||||
"requires": {
|
||||
"@swc/helpers": "^0.5.12",
|
||||
"brotli": "^1.3.2",
|
||||
"clone": "^2.1.2",
|
||||
"dfa": "^1.2.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"restructure": "^3.0.0",
|
||||
"tiny-inflate": "^1.0.3",
|
||||
"unicode-properties": "^1.4.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
|
|
@ -1571,6 +1643,11 @@
|
|||
"@panva/asn1.js": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"js-md5": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
|
||||
"integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="
|
||||
},
|
||||
"json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
|
|
@ -1671,6 +1748,22 @@
|
|||
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
|
||||
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
|
||||
},
|
||||
"linebreak": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
||||
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
||||
"requires": {
|
||||
"base64-js": "0.0.8",
|
||||
"unicode-trie": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-js": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||
|
|
@ -1976,6 +2069,11 @@
|
|||
"yocto-queue": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
||||
|
|
@ -1996,11 +2094,32 @@
|
|||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"pdfkit": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.19.1.tgz",
|
||||
"integrity": "sha512-6Gzk+wDwTs4VSxsR5rCMTnIl5nlmkye1oWB0l2hDB1EX6ZNSIBroKQEv+2+fPPn+stVjyqzmsqRJVDfB9fo5DA==",
|
||||
"requires": {
|
||||
"@noble/ciphers": "^1.0.0",
|
||||
"@noble/hashes": "^1.6.0",
|
||||
"fontkit": "^2.0.4",
|
||||
"js-md5": "^0.8.3",
|
||||
"linebreak": "^1.1.0",
|
||||
"png-js": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="
|
||||
},
|
||||
"png-js": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz",
|
||||
"integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==",
|
||||
"requires": {
|
||||
"browserify-zlib": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
|
|
@ -2147,6 +2266,11 @@
|
|||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"restructure": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
|
||||
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="
|
||||
},
|
||||
"retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
|
|
@ -2354,6 +2478,11 @@
|
|||
"uuid": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
@ -2400,6 +2529,24 @@
|
|||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
|
||||
},
|
||||
"unicode-properties": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
|
||||
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"requires": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"unique-string": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"mysql": "^2.15.0",
|
||||
"nodemailer": "^6.10.1",
|
||||
"nodemon": "^3.1.14",
|
||||
"pdfkit": "^0.19.1",
|
||||
"shelljs": "^0.8.4",
|
||||
"unzipper": "^0.12.3"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ app.use('/email', require('./controllers/emailController').router);
|
|||
app.use('/checkout', require('./controllers/checkoutController')());
|
||||
|
||||
app.use('/otp', require('./controllers/oriontardproController')());
|
||||
app.use('/otp', require('./controllers/oriontard.pdf.controller')());
|
||||
|
||||
|
||||
// Allteeth
|
||||
app.use('/api_5/sincronia', require('./controllers/allteeth/sincroniaController')());
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,7 +26,7 @@ import { BingoSortComponent } from './bingo-sort/bingo-sort.component';
|
|||
import { BingoComponent } from './bingo/bingo.component';
|
||||
import { BingoDashboardComponent } from './bingo-dashboard/bingo-dashboard.component';
|
||||
import { BingoService } from './services/bingoService';
|
||||
import { OrionDashboardComponent } from './orion-dashboard/orion-dashboard.component';
|
||||
import { OrionDashboardComponent } from './oriontard/orion-dashboard/orion-dashboard.component';
|
||||
import { OrionService } from './services/orionService';
|
||||
import { ProdutosComponent } from './produtos/produtos.component';
|
||||
import { ProdutosService } from './services/produtosService';
|
||||
|
|
@ -36,6 +36,8 @@ import { AgrobaseService } from './services/agrobase.service';
|
|||
import { TermosUsoComponent } from './termos-uso/termos-uso.component';
|
||||
import { PoliticaPrivacidadeComponent } from './politica-privacidade/politica-privacidade.component';
|
||||
import { SobreComponent } from './sobre/sobre.component';
|
||||
import { OrionOrdensServicoComponent } from './oriontard/orion-ordens-servico/orion-ordens-servico.component';
|
||||
import { OrionOrdemServicoFormComponent } from './oriontard/orion-ordem-servico-form/orion-ordem-servico-form.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
|
|
@ -149,10 +151,24 @@ const routes: Routes = [
|
|||
component: BingoSortComponent,
|
||||
},
|
||||
]
|
||||
}, {
|
||||
},
|
||||
{
|
||||
path: 'orion-dashboard',
|
||||
component: OrionDashboardComponent
|
||||
}, {
|
||||
},
|
||||
{
|
||||
path: 'orion-ordens-servico/nova',
|
||||
component: OrionOrdemServicoFormComponent
|
||||
},
|
||||
{
|
||||
path: 'orion-ordens-servico/:id/editar',
|
||||
component: OrionOrdemServicoFormComponent
|
||||
},
|
||||
{
|
||||
path: 'orion-ordens-servico',
|
||||
component: OrionOrdensServicoComponent
|
||||
},
|
||||
{
|
||||
path: 'agrobase',
|
||||
children: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import { EasyAVAComponent } from './easy-ava/easy-ava.component';
|
|||
import { BingoSortComponent } from './bingo-sort/bingo-sort.component';
|
||||
import { BingoComponent } from './bingo/bingo.component';
|
||||
import { BingoDashboardComponent } from './bingo-dashboard/bingo-dashboard.component';
|
||||
import { OrionDashboardComponent } from './orion-dashboard/orion-dashboard.component';
|
||||
import { OrionDashboardComponent } from './oriontard/orion-dashboard/orion-dashboard.component';
|
||||
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
|
|
@ -49,6 +49,8 @@ import { TipoArquivoNomePipe } from './pipes/tipo-arquivo-nome.pipe';
|
|||
import { SobreComponent } from './sobre/sobre.component';
|
||||
import { PoliticaPrivacidadeComponent } from './politica-privacidade/politica-privacidade.component';
|
||||
import { TermosUsoComponent } from './termos-uso/termos-uso.component';
|
||||
import { OrionOrdensServicoComponent } from './oriontard/orion-ordens-servico/orion-ordens-servico.component';
|
||||
import { OrionOrdemServicoFormComponent } from './oriontard/orion-ordem-servico-form/orion-ordem-servico-form.component';
|
||||
|
||||
|
||||
// Registrar a localidade pt-BR
|
||||
|
|
@ -79,7 +81,7 @@ registerLocaleData(localePt);
|
|||
ProdutosComponent,
|
||||
AgrobaseLoginComponent,
|
||||
AgrobaseVersionamentoComponent,
|
||||
TipoArquivoNomePipe, SobreComponent, PoliticaPrivacidadeComponent, TermosUsoComponent
|
||||
TipoArquivoNomePipe, SobreComponent, PoliticaPrivacidadeComponent, TermosUsoComponent, OrionOrdensServicoComponent, OrionOrdemServicoFormComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export class ChavesComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
if (confirm(Mensagem)) {
|
||||
this.GerarChave(90);
|
||||
this.GerarChave(30);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -149,12 +149,33 @@ export class DashboardComponent implements OnInit {
|
|||
{
|
||||
index: 5,
|
||||
texto: "OrionTard Pro",
|
||||
link: "/orion-dashboard",
|
||||
havesub: false,
|
||||
link: "",
|
||||
havesub: true,
|
||||
dropicon: "arrow_drop_down",
|
||||
icon: "home",
|
||||
issub: false,
|
||||
sub: null,
|
||||
sub: [
|
||||
{
|
||||
index: 0,
|
||||
texto: "Logs",
|
||||
link: "/orion-dashboard",
|
||||
icon: "update",
|
||||
havesub: false,
|
||||
dropicon: "",
|
||||
issub: true,
|
||||
sub: null,
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
texto: "Ordens de Serviço",
|
||||
link: "/orion-ordens-servico",
|
||||
icon: "update",
|
||||
havesub: false,
|
||||
dropicon: "",
|
||||
issub: true,
|
||||
sub: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
index: 6,
|
||||
|
|
|
|||
|
|
@ -1,92 +1,513 @@
|
|||
export class OrionLogsModel {
|
||||
dataLog: Date;
|
||||
nome: string;
|
||||
coordenadas: string;
|
||||
perfil: string;
|
||||
log: string;
|
||||
logObj: OrionLogsDetalhesModel;
|
||||
dataLog: Date;
|
||||
nome: string;
|
||||
coordenadas: string;
|
||||
perfil: string;
|
||||
log: string;
|
||||
logObj: OrionLogsDetalhesModel;
|
||||
}
|
||||
|
||||
export class OrionLogsDetalhesModel {
|
||||
data: Date;
|
||||
firmwareVersao: number;
|
||||
robotNs: number;
|
||||
modelo: string;
|
||||
versao: string;
|
||||
cpf: string;
|
||||
usuario: string;
|
||||
perfil: string;
|
||||
camera: string;
|
||||
servico: OrionLogServicoModel;
|
||||
odometroTotal: number;
|
||||
odometroConectado: number;
|
||||
odometroMotores: number;
|
||||
odometroMotoresParcial: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
altitude: number;
|
||||
tempoTotal: number;
|
||||
tempoConectado: number;
|
||||
sensores: OrionLogSensorModel[];
|
||||
portaCOM: string;
|
||||
baudRate: number;
|
||||
conectado: boolean;
|
||||
distancia: number;
|
||||
data: Date;
|
||||
firmwareVersao: number;
|
||||
robotNs: number;
|
||||
modelo: string;
|
||||
versao: string;
|
||||
cpf: string;
|
||||
usuario: string;
|
||||
perfil: string;
|
||||
camera: string;
|
||||
servico: OrionLogServicoModel;
|
||||
odometroTotal: number;
|
||||
odometroConectado: number;
|
||||
odometroMotores: number;
|
||||
odometroMotoresParcial: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
altitude: number;
|
||||
tempoTotal: number;
|
||||
tempoConectado: number;
|
||||
sensores: OrionLogSensorModel[];
|
||||
portaCOM: string;
|
||||
baudRate: number;
|
||||
conectado: boolean;
|
||||
distancia: number;
|
||||
|
||||
HoraInicio: string;
|
||||
HoraFim: string;
|
||||
Duracao: string;
|
||||
Coordenadas: string;
|
||||
Localizacao: string;
|
||||
HoraInicio: string;
|
||||
HoraFim: string;
|
||||
Duracao: string;
|
||||
Coordenadas: string;
|
||||
Localizacao: string;
|
||||
}
|
||||
|
||||
export class OrionLogServicoModel {
|
||||
Nome: string;
|
||||
Cidade: string;
|
||||
Responsavel: string;
|
||||
Servico: string;
|
||||
Path: string;
|
||||
DataCadastro: Date;
|
||||
Nome: string;
|
||||
Cidade: string;
|
||||
Responsavel: string;
|
||||
Servico: string;
|
||||
Path: string;
|
||||
DataCadastro: Date;
|
||||
}
|
||||
|
||||
export class OrionLogSensorModel {
|
||||
sensor: number;
|
||||
descricao: string;
|
||||
minimo: number;
|
||||
maximo: number;
|
||||
media: number;
|
||||
moda: number;
|
||||
mediana: number;
|
||||
lista: number[];
|
||||
sensor: number;
|
||||
descricao: string;
|
||||
minimo: number;
|
||||
maximo: number;
|
||||
media: number;
|
||||
moda: number;
|
||||
mediana: number;
|
||||
lista: number[];
|
||||
}
|
||||
|
||||
export class GeoCoordenadasResponseModel {
|
||||
data: GeoCoordenadasModel[];
|
||||
data: GeoCoordenadasModel[];
|
||||
}
|
||||
|
||||
export class GeoCoordenadasModel {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
type: string;
|
||||
distance: number;
|
||||
name: string;
|
||||
number: string;
|
||||
postal_code: string;
|
||||
street: string;
|
||||
confidence: number;
|
||||
region: string;
|
||||
region_code: string;
|
||||
county: string;
|
||||
lacality: string;
|
||||
administrative_area: string;
|
||||
neighborhood: string;
|
||||
country: string;
|
||||
country_code: string;
|
||||
continent: string;
|
||||
label: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
type: string;
|
||||
distance: number;
|
||||
name: string;
|
||||
number: string;
|
||||
postal_code: string;
|
||||
street: string;
|
||||
confidence: number;
|
||||
region: string;
|
||||
region_code: string;
|
||||
county: string;
|
||||
lacality: string;
|
||||
administrative_area: string;
|
||||
neighborhood: string;
|
||||
country: string;
|
||||
country_code: string;
|
||||
continent: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export class LogAgrupado {
|
||||
key: string;
|
||||
value: OrionLogsDetalhesModel[];
|
||||
key: string;
|
||||
value: OrionLogsDetalhesModel[];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RESPOSTA PADRÃO DA API
|
||||
// ============================================================
|
||||
|
||||
export interface OrionApiErrorModel {
|
||||
code?: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface OrionApiResponseModel<T> {
|
||||
status: number;
|
||||
error: OrionApiErrorModel | null;
|
||||
response: T;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// CADASTROS AUXILIARES
|
||||
// ============================================================
|
||||
|
||||
export interface ClienteManutencaoModel {
|
||||
id: number;
|
||||
nome_fantasia: string;
|
||||
razao_social?: string;
|
||||
documento?: string;
|
||||
}
|
||||
|
||||
export interface EquipamentoManutencaoModel {
|
||||
id: number;
|
||||
cliente_id?: number;
|
||||
modelo_id?: number;
|
||||
codigo_interno?: string;
|
||||
nome: string;
|
||||
numero_serie: string;
|
||||
versao?: string;
|
||||
|
||||
odometro_total_segundos: number;
|
||||
odometro_conectado_segundos: number;
|
||||
odometro_movimento_segundos: number;
|
||||
odometro_parcial_segundos: number;
|
||||
odometro_atualizado_em?: string;
|
||||
|
||||
modelo_nome?: string;
|
||||
cliente_nome?: string;
|
||||
}
|
||||
|
||||
export interface TecnicoManutencaoModel {
|
||||
id: number;
|
||||
usuario_sistema_id?: number;
|
||||
nome: string;
|
||||
especialidade?: string;
|
||||
valor_hora_expediente_padrao: number;
|
||||
valor_hora_extra_padrao: number;
|
||||
}
|
||||
|
||||
export interface MaterialManutencaoModel {
|
||||
id: number;
|
||||
codigo?: string;
|
||||
descricao: string;
|
||||
tipo: MaterialManutencaoTipo;
|
||||
unidade: string;
|
||||
custo_padrao: number;
|
||||
preco_venda_padrao: number;
|
||||
}
|
||||
|
||||
export type MaterialManutencaoTipo =
|
||||
| 'PECA'
|
||||
| 'INSUMO'
|
||||
| 'SERVICO_TERCEIRO'
|
||||
| 'FRETE'
|
||||
| 'FERRAMENTA'
|
||||
| 'OUTRO';
|
||||
|
||||
// ============================================================
|
||||
// ORDENS DE SERVIÇO
|
||||
// ============================================================
|
||||
|
||||
export interface OrdemServicoListaModel {
|
||||
id: number;
|
||||
|
||||
cliente_id?: number;
|
||||
equipamento_id?: number;
|
||||
tecnico_responsavel_id?: number;
|
||||
|
||||
cliente_nome_snapshot?: string;
|
||||
equipamento_nome_snapshot?: string;
|
||||
numero_serie_snapshot?: string;
|
||||
modelo_snapshot?: string;
|
||||
versao_snapshot?: string;
|
||||
|
||||
tipo: OrdemServicoTipo;
|
||||
status: OrdemServicoStatus;
|
||||
status_pagamento: OrdemServicoStatusPagamento;
|
||||
prioridade: OrdemServicoPrioridade;
|
||||
|
||||
data_entrada: string;
|
||||
data_previsao?: string;
|
||||
data_finalizacao?: string;
|
||||
data_retirada?: string;
|
||||
|
||||
problema_relatado?: string;
|
||||
|
||||
total_horas_expediente: number;
|
||||
total_horas_extras: number;
|
||||
|
||||
valor_mao_obra: number;
|
||||
valor_insumos: number;
|
||||
valor_desconto: number;
|
||||
valor_acrescimo: number;
|
||||
valor_total: number;
|
||||
valor_pago: number;
|
||||
valor_pendente: number;
|
||||
|
||||
tecnico_responsavel_nome?: string;
|
||||
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface OrdemServicoPaginacaoModel {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface OrdemServicoListaResponseModel {
|
||||
items: OrdemServicoListaModel[];
|
||||
pagination: OrdemServicoPaginacaoModel;
|
||||
}
|
||||
|
||||
export interface OrdemServicoFiltrosModel {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
busca?: string;
|
||||
status?: OrdemServicoStatus | '';
|
||||
status_pagamento?: OrdemServicoStatusPagamento | '';
|
||||
tipo?: OrdemServicoTipo | '';
|
||||
equipamento_id?: number;
|
||||
cliente_id?: number;
|
||||
data_inicio?: string;
|
||||
data_fim?: string;
|
||||
}
|
||||
|
||||
export interface OrdemServicoDetalhesModel extends OrdemServicoListaModel {
|
||||
diagnostico?: string;
|
||||
solucao_resumo?: string;
|
||||
|
||||
responsavel_entrega?: string;
|
||||
responsavel_retirada?: string;
|
||||
|
||||
observacoes_internas?: string;
|
||||
observacoes_cliente?: string;
|
||||
|
||||
garantia_dias?: number;
|
||||
garantia_ate?: string;
|
||||
|
||||
custo_insumos: number;
|
||||
justificativa_ajuste_valor?: string;
|
||||
origem?: string;
|
||||
|
||||
atividades: OrdemServicoAtividadeModel[];
|
||||
odometros_da_os: OrdemServicoOdometroModel[];
|
||||
intervalo_desde_leitura_anterior?: OrdemServicoIntervaloOdometroModel;
|
||||
pagamentos: OrdemServicoPagamentoModel[];
|
||||
historico_status: OrdemServicoHistoricoStatusModel[];
|
||||
}
|
||||
|
||||
export interface OrdemServicoAtividadeModel {
|
||||
id: number;
|
||||
ordem_servico_id: number;
|
||||
|
||||
data_atividade: string;
|
||||
descricao: string;
|
||||
ordem_exibicao: number;
|
||||
|
||||
horas_expediente: number;
|
||||
horas_extras: number;
|
||||
valor_hora_expediente: number;
|
||||
valor_hora_extra: number;
|
||||
valor_mao_obra: number;
|
||||
|
||||
cobravel: boolean | number;
|
||||
|
||||
observacoes_internas?: string;
|
||||
observacoes_cliente?: string;
|
||||
|
||||
tecnicos: OrdemServicoAtividadeTecnicoModel[];
|
||||
materiais: OrdemServicoAtividadeMaterialModel[];
|
||||
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface OrdemServicoAtividadeTecnicoModel {
|
||||
atividade_id?: number;
|
||||
tecnico_id: number;
|
||||
tecnico_nome?: string;
|
||||
papel: OrdemServicoTecnicoPapel;
|
||||
horas_trabalhadas_expediente?: number;
|
||||
horas_trabalhadas_extras?: number;
|
||||
}
|
||||
|
||||
export interface OrdemServicoAtividadeMaterialModel {
|
||||
id?: number;
|
||||
atividade_id?: number;
|
||||
material_id?: number;
|
||||
|
||||
descricao_snapshot: string;
|
||||
unidade_snapshot: string;
|
||||
|
||||
quantidade: number;
|
||||
valor_custo_unitario: number;
|
||||
valor_cobrado_unitario: number;
|
||||
valor_custo_total?: number;
|
||||
valor_cobrado_total?: number;
|
||||
|
||||
cobravel: boolean | number;
|
||||
observacoes?: string;
|
||||
}
|
||||
|
||||
export interface OrdemServicoOdometroModel {
|
||||
id?: number;
|
||||
equipamento_id?: number;
|
||||
ordem_servico_id?: number;
|
||||
|
||||
tipo_leitura: OrdemServicoTipoLeituraOdometro;
|
||||
origem?: string;
|
||||
leitura_em?: string;
|
||||
|
||||
odometro_total_segundos: number;
|
||||
odometro_conectado_segundos: number;
|
||||
odometro_movimento_segundos: number;
|
||||
odometro_parcial_segundos: number;
|
||||
|
||||
odometro_total_anterior?: number;
|
||||
odometro_conectado_anterior?: number;
|
||||
odometro_movimento_anterior?: number;
|
||||
|
||||
observacoes?: string;
|
||||
usuario_id?: number;
|
||||
}
|
||||
|
||||
export interface OrdemServicoIntervaloOdometroModel {
|
||||
total_segundos?: number;
|
||||
conectado_segundos?: number;
|
||||
movimento_segundos?: number;
|
||||
}
|
||||
|
||||
export interface OrdemServicoPagamentoModel {
|
||||
id?: number;
|
||||
ordem_servico_id?: number;
|
||||
data_pagamento?: string;
|
||||
valor: number;
|
||||
forma_pagamento: OrdemServicoFormaPagamento;
|
||||
referencia?: string;
|
||||
observacoes?: string;
|
||||
usuario_id?: number;
|
||||
cancelado_em?: string;
|
||||
}
|
||||
|
||||
export interface OrdemServicoHistoricoStatusModel {
|
||||
id: number;
|
||||
ordem_servico_id: number;
|
||||
status_anterior?: OrdemServicoStatus;
|
||||
status_novo: OrdemServicoStatus;
|
||||
observacoes?: string;
|
||||
alterado_por_usuario_id?: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface OrdemServicoSalvarModel {
|
||||
cliente_id?: number;
|
||||
equipamento_id?: number;
|
||||
tecnico_responsavel_id?: number;
|
||||
|
||||
equipamento_nome?: string;
|
||||
numero_serie?: string;
|
||||
modelo?: string;
|
||||
versao?: string;
|
||||
|
||||
tipo?: OrdemServicoTipo;
|
||||
status?: OrdemServicoStatus;
|
||||
status_pagamento?: OrdemServicoStatusPagamento;
|
||||
prioridade?: OrdemServicoPrioridade;
|
||||
|
||||
data_entrada?: string;
|
||||
data_previsao?: string;
|
||||
data_finalizacao?: string;
|
||||
data_retirada?: string;
|
||||
|
||||
problema_relatado?: string;
|
||||
diagnostico?: string;
|
||||
solucao_resumo?: string;
|
||||
|
||||
responsavel_entrega?: string;
|
||||
responsavel_retirada?: string;
|
||||
|
||||
observacoes_internas?: string;
|
||||
observacoes_cliente?: string;
|
||||
|
||||
garantia_dias?: number;
|
||||
garantia_ate?: string;
|
||||
|
||||
valor_desconto?: number;
|
||||
valor_acrescimo?: number;
|
||||
justificativa_ajuste_valor?: string;
|
||||
|
||||
observacao_status?: string;
|
||||
usuario_id?: number;
|
||||
|
||||
odometro_entrada?: OrdemServicoOdometroModel;
|
||||
}
|
||||
|
||||
export interface OrdemServicoAtividadeSalvarModel {
|
||||
data_atividade?: string;
|
||||
descricao: string;
|
||||
ordem_exibicao?: number;
|
||||
|
||||
horas_expediente?: number;
|
||||
horas_extras?: number;
|
||||
valor_hora_expediente?: number;
|
||||
valor_hora_extra?: number;
|
||||
cobravel?: boolean;
|
||||
|
||||
observacoes_internas?: string;
|
||||
observacoes_cliente?: string;
|
||||
usuario_id?: number;
|
||||
|
||||
tecnicos?: OrdemServicoAtividadeTecnicoModel[];
|
||||
materiais?: OrdemServicoAtividadeMaterialModel[];
|
||||
}
|
||||
|
||||
export interface OrdemServicoTotaisModel {
|
||||
total_horas_expediente: number;
|
||||
total_horas_extras: number;
|
||||
valor_mao_obra: number;
|
||||
custo_insumos: number;
|
||||
valor_insumos: number;
|
||||
valor_desconto: number;
|
||||
valor_acrescimo: number;
|
||||
valor_total: number;
|
||||
valor_pago: number;
|
||||
valor_pendente: number;
|
||||
status_pagamento: OrdemServicoStatusPagamento;
|
||||
}
|
||||
|
||||
export interface OrdemServicoOperacaoResultModel {
|
||||
id: number;
|
||||
ordem_servico_id?: number;
|
||||
equipamento_id?: number;
|
||||
excluida?: boolean;
|
||||
cancelado?: boolean;
|
||||
tipo_leitura?: OrdemServicoTipoLeituraOdometro;
|
||||
totais_os?: OrdemServicoTotaisModel;
|
||||
}
|
||||
|
||||
export interface OrionLogInsertModel {
|
||||
cpf?: string;
|
||||
perfil?: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
log: string;
|
||||
}
|
||||
|
||||
export type OrdemServicoTipo =
|
||||
| 'CORRETIVA'
|
||||
| 'PREVENTIVA'
|
||||
| 'REFORMA'
|
||||
| 'DESENVOLVIMENTO'
|
||||
| 'FABRICACAO'
|
||||
| 'GARANTIA'
|
||||
| 'CORTESIA'
|
||||
| 'INTERNA'
|
||||
| 'OUTRO';
|
||||
|
||||
export type OrdemServicoStatus =
|
||||
| 'RASCUNHO'
|
||||
| 'ABERTA'
|
||||
| 'EM_DIAGNOSTICO'
|
||||
| 'AGUARDANDO_APROVACAO'
|
||||
| 'AGUARDANDO_PECA'
|
||||
| 'EM_EXECUCAO'
|
||||
| 'EM_TESTES'
|
||||
| 'FINALIZADA'
|
||||
| 'AGUARDANDO_RETIRADA'
|
||||
| 'RETIRADA'
|
||||
| 'CANCELADA';
|
||||
|
||||
export type OrdemServicoStatusPagamento =
|
||||
| 'NAO_APLICAVEL'
|
||||
| 'PENDENTE'
|
||||
| 'PARCIAL'
|
||||
| 'RECEBIDO'
|
||||
| 'CORTESIA';
|
||||
|
||||
export type OrdemServicoPrioridade =
|
||||
| 'BAIXA'
|
||||
| 'NORMAL'
|
||||
| 'ALTA'
|
||||
| 'URGENTE';
|
||||
|
||||
export type OrdemServicoTecnicoPapel =
|
||||
| 'RESPONSAVEL'
|
||||
| 'EXECUTOR'
|
||||
| 'APOIO';
|
||||
|
||||
export type OrdemServicoFormaPagamento =
|
||||
| 'DINHEIRO'
|
||||
| 'PIX'
|
||||
| 'TRANSFERENCIA'
|
||||
| 'BOLETO'
|
||||
| 'CARTAO'
|
||||
| 'OUTRO';
|
||||
|
||||
export type OrdemServicoTipoLeituraOdometro =
|
||||
| 'CADASTRO_INICIAL'
|
||||
| 'ENTRADA_MANUTENCAO'
|
||||
| 'SAIDA_MANUTENCAO'
|
||||
| 'ATUALIZACAO_MANUAL'
|
||||
| 'TELEMETRIA';
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { Chart } from 'chart.js';
|
||||
import { LogAgrupado, OrionLogsDetalhesModel, OrionLogSensorModel, OrionLogsModel } from '../models/orionModel';
|
||||
import { OrionService } from '../services/orionService';
|
||||
import { LogAgrupado, OrionLogsDetalhesModel, OrionLogSensorModel, OrionLogsModel } from '../../models/orionModel';
|
||||
import { OrionService } from '../../services/orionService';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatTabChangeEvent } from '@angular/material';
|
||||
|
||||
|
|
@ -0,0 +1,720 @@
|
|||
<div class="os-form-page">
|
||||
|
||||
<header class="page-header">
|
||||
<div class="header-main">
|
||||
<button mat-stroked-button type="button" (click)="voltar()">Voltar</button>
|
||||
|
||||
<div>
|
||||
<span class="eyebrow">ORIONTARD · MANUTENÇÃO</span>
|
||||
<h1 *ngIf="!modoEdicao">Nova ordem de serviço</h1>
|
||||
<h1 *ngIf="modoEdicao">OS #{{ ordemServicoId }}</h1>
|
||||
<p *ngIf="!modoEdicao">Registre a entrada do equipamento e os odômetros recebidos.</p>
|
||||
<p *ngIf="modoEdicao && ordem">
|
||||
{{ ordem.equipamento_nome_snapshot || ordem.modelo_snapshot }}
|
||||
<span *ngIf="ordem.numero_serie_snapshot">· NS {{ ordem.numero_serie_snapshot }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<ng-container *ngIf="modoEdicao && ordem">
|
||||
<span class="status-badge" [ngClass]="classeStatus(ordem.status)">
|
||||
{{ labelStatus(ordem.status) }}
|
||||
</span>
|
||||
<span class="status-badge" [ngClass]="classePagamento(ordem.status_pagamento)">
|
||||
{{ labelPagamento(ordem.status_pagamento) }}
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
<button mat-raised-button color="primary" type="button"
|
||||
(click)="salvarOrdem()" [disabled]="carregando || salvando">
|
||||
{{ salvando ? 'Salvando...' : (modoEdicao ? 'Salvar alterações' : 'Criar OS') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
*ngIf="modoEdicao"
|
||||
(click)="baixarPdfCliente()"
|
||||
[disabled]="baixandoPdf || salvando">
|
||||
{{ baixandoPdf ? 'Gerando PDF...' : 'Baixar OS' }}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="message message-error" *ngIf="erro">{{ erro }}</div>
|
||||
<div class="message message-success" *ngIf="sucesso">{{ sucesso }}</div>
|
||||
|
||||
<div class="loading-card" *ngIf="carregando">
|
||||
<div class="loading-bar"></div>
|
||||
<span>Carregando dados da ordem de serviço...</span>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!carregando">
|
||||
|
||||
<form [formGroup]="formOrdem" class="section-card">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="section-kicker">DADOS GERAIS</span>
|
||||
<h2>Entrada e identificação</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-grid form-grid-4">
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Equipamento</mat-label>
|
||||
<mat-select formControlName="equipamento_id"
|
||||
(selectionChange)="equipamentoSelecionado($event.value)">
|
||||
<mat-option *ngFor="let equipamento of equipamentos" [value]="equipamento.id">
|
||||
{{ equipamento.nome }} · NS {{ equipamento.numero_serie }}
|
||||
<span *ngIf="equipamento.modelo_nome">· {{ equipamento.modelo_nome }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>Selecione o equipamento.</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Cliente</mat-label>
|
||||
<mat-select formControlName="cliente_id">
|
||||
<mat-option [value]="null">Não informado</mat-option>
|
||||
<mat-option *ngFor="let cliente of clientes" [value]="cliente.id">
|
||||
{{ cliente.nome_fantasia }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Técnico responsável</mat-label>
|
||||
<mat-select formControlName="tecnico_responsavel_id">
|
||||
<mat-option [value]="null">Não definido</mat-option>
|
||||
<mat-option *ngFor="let tecnico of tecnicos" [value]="tecnico.id">
|
||||
{{ tecnico.nome }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Tipo</mat-label>
|
||||
|
||||
<mat-select
|
||||
formControlName="tipo"
|
||||
(selectionChange)="tipoOrdemAlterado($event.value)">
|
||||
|
||||
<mat-option
|
||||
*ngFor="let opcao of tipoOpcoes"
|
||||
[value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Situação financeira</mat-label>
|
||||
|
||||
<mat-select formControlName="status_pagamento" disabled>
|
||||
<mat-option value="NAO_APLICAVEL">
|
||||
Não aplicável
|
||||
</mat-option>
|
||||
|
||||
<mat-option value="PENDENTE">
|
||||
Pendente
|
||||
</mat-option>
|
||||
|
||||
<mat-option value="PARCIAL">
|
||||
Parcial
|
||||
</mat-option>
|
||||
|
||||
<mat-option value="RECEBIDO">
|
||||
Recebido
|
||||
</mat-option>
|
||||
|
||||
<mat-option value="CORTESIA">
|
||||
Cortesia
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Status</mat-label>
|
||||
<mat-select formControlName="status">
|
||||
<mat-option *ngFor="let opcao of statusOpcoes" [value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Prioridade</mat-label>
|
||||
<mat-select formControlName="prioridade">
|
||||
<mat-option *ngFor="let opcao of prioridadeOpcoes" [value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Data e hora de entrada</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="data_entrada">
|
||||
<mat-error>Informe a data de entrada.</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Previsão</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="data_previsao">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Finalização</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="data_finalizacao">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Retirada</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="data_retirada">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Responsável pela entrega</mat-label>
|
||||
<input matInput formControlName="responsavel_entrega">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Responsável pela retirada</mat-label>
|
||||
<input matInput formControlName="responsavel_retirada">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-4">
|
||||
<mat-label>Problema relatado pelo cliente</mat-label>
|
||||
<textarea matInput rows="3" formControlName="problema_relatado"></textarea>
|
||||
<mat-error>Descreva o problema relatado.</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Diagnóstico</mat-label>
|
||||
<textarea matInput rows="4" formControlName="diagnostico"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Solução resumida</mat-label>
|
||||
<textarea matInput rows="4" formControlName="solucao_resumo"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Observações internas</mat-label>
|
||||
<textarea matInput rows="3" formControlName="observacoes_internas"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Observações para o cliente</mat-label>
|
||||
<textarea matInput rows="3" formControlName="observacoes_cliente"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Garantia em dias</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="garantia_dias">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Garantia até</mat-label>
|
||||
<input matInput type="date" formControlName="garantia_ate">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Desconto</mat-label>
|
||||
<input matInput type="number" min="0" step="0.01" formControlName="valor_desconto">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Acréscimo</mat-label>
|
||||
<input matInput type="number" min="0" step="0.01" formControlName="valor_acrescimo">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Justificativa do ajuste financeiro</mat-label>
|
||||
<input matInput formControlName="justificativa_ajuste_valor">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Observação da alteração de status</mat-label>
|
||||
<input matInput formControlName="observacao_status">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="section-card" *ngIf="!modoEdicao" [formGroup]="formOdometroEntrada">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<span class="section-kicker">LEITURA DE ENTRADA</span>
|
||||
<h2>Odômetros recebidos</h2>
|
||||
<p>Confirme os valores exibidos pelo robô quando ele chegou ao laboratório.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-grid form-grid-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Total em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_total_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometroEntrada.get('odometro_total_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Conectado em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_conectado_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometroEntrada.get('odometro_conectado_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Movimento em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_movimento_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometroEntrada.get('odometro_movimento_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Parcial em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_parcial_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometroEntrada.get('odometro_parcial_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Momento da leitura</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="leitura_em">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-3">
|
||||
<mat-label>Observações da leitura</mat-label>
|
||||
<input matInput formControlName="observacoes">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<mat-tab-group *ngIf="modoEdicao && ordem" class="os-tabs">
|
||||
|
||||
<mat-tab label="Atividades">
|
||||
<div class="tab-content">
|
||||
<div class="tab-toolbar">
|
||||
<div>
|
||||
<h2>Atividades realizadas</h2>
|
||||
<p>Adicione um registro para cada dia ou etapa da manutenção.</p>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" type="button"
|
||||
(click)="abrirNovaAtividade()" *ngIf="!exibirFormAtividade">
|
||||
Adicionar atividade
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form class="inline-form-card" [formGroup]="formAtividade" *ngIf="exibirFormAtividade">
|
||||
<div class="inline-form-header">
|
||||
<div>
|
||||
<span class="section-kicker">ATIVIDADE</span>
|
||||
<h3>{{ atividadeEditandoId ? 'Editar atividade' : 'Nova atividade' }}</h3>
|
||||
</div>
|
||||
<button mat-button type="button" (click)="cancelarEdicaoAtividade()">Cancelar</button>
|
||||
</div>
|
||||
|
||||
<div class="form-grid form-grid-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Data</mat-label>
|
||||
<input matInput type="date" formControlName="data_atividade">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Técnicos</mat-label>
|
||||
<mat-select multiple formControlName="tecnico_ids">
|
||||
<mat-option *ngFor="let tecnico of tecnicos" [value]="tecnico.id">
|
||||
{{ tecnico.nome }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Ordem de exibição</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="ordem_exibicao">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-4">
|
||||
<mat-label>Descrição do serviço realizado</mat-label>
|
||||
<textarea matInput rows="4" formControlName="descricao"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Horas de expediente</mat-label>
|
||||
<input matInput type="number" min="0" step="0.25" formControlName="horas_expediente">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Horas extras</mat-label>
|
||||
<input matInput type="number" min="0" step="0.25" formControlName="horas_extras">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Valor hora expediente</mat-label>
|
||||
<input matInput type="number" min="0" step="0.01" formControlName="valor_hora_expediente">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Valor hora extra</mat-label>
|
||||
<input matInput type="number" min="0" step="0.01" formControlName="valor_hora_extra">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<label class="checkbox-field span-4">
|
||||
<input type="checkbox" formControlName="cobravel">
|
||||
<span>Atividade cobrável</span>
|
||||
</label>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Observações internas</mat-label>
|
||||
<textarea matInput rows="2" formControlName="observacoes_internas"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Observações para o cliente</mat-label>
|
||||
<textarea matInput rows="2" formControlName="observacoes_cliente"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="materials-section" formArrayName="materiais">
|
||||
<div class="subsection-heading">
|
||||
<div>
|
||||
<h4>Materiais e insumos</h4>
|
||||
<p>Use o catálogo ou registre um item avulso.</p>
|
||||
</div>
|
||||
<button mat-stroked-button type="button" (click)="adicionarMaterialAtividade()">
|
||||
Adicionar material
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="material-row"
|
||||
*ngFor="let materialControl of materiaisAtividade.controls; let i = index"
|
||||
[formGroupName]="i">
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Catálogo</mat-label>
|
||||
<mat-select formControlName="material_id"
|
||||
(selectionChange)="materialCatalogoSelecionado(i, $event.value)">
|
||||
<mat-option [value]="null">Item avulso</mat-option>
|
||||
<mat-option *ngFor="let material of materiais" [value]="material.id">
|
||||
{{ material.descricao }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="material-description">
|
||||
<mat-label>Descrição</mat-label>
|
||||
<input matInput formControlName="descricao_snapshot">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Unidade</mat-label>
|
||||
<input matInput formControlName="unidade_snapshot">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Quantidade</mat-label>
|
||||
<input matInput type="number" min="0.001" step="0.001" formControlName="quantidade">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Custo unitário</mat-label>
|
||||
<input matInput type="number" min="0" step="0.01" formControlName="valor_custo_unitario">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Valor cobrado</mat-label>
|
||||
<input matInput type="number" min="0" step="0.01" formControlName="valor_cobrado_unitario">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<label class="checkbox-field compact-checkbox">
|
||||
<input type="checkbox" formControlName="cobravel">
|
||||
<span>Cobrável</span>
|
||||
</label>
|
||||
|
||||
<button mat-button type="button" class="danger-button"
|
||||
(click)="removerMaterialAtividade(i)">
|
||||
Remover
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="empty-subsection" *ngIf="materiaisAtividade.length === 0">
|
||||
Nenhum material adicionado nesta atividade.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-form-actions">
|
||||
<button mat-button type="button" (click)="cancelarEdicaoAtividade()">Cancelar</button>
|
||||
<button mat-raised-button color="primary" type="button"
|
||||
(click)="salvarAtividade()" [disabled]="salvandoAtividade">
|
||||
{{ salvandoAtividade ? 'Salvando...' : 'Salvar atividade' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="activity-card" *ngFor="let atividade of atividadesOrdenadas">
|
||||
<div class="activity-header">
|
||||
<div>
|
||||
<span class="activity-date">{{ atividade.data_atividade | date:'dd/MM/yyyy' }}</span>
|
||||
<h3>{{ atividade.descricao }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="activity-actions">
|
||||
<button mat-button type="button" (click)="editarAtividade(atividade)">Editar</button>
|
||||
<button mat-button type="button" class="danger-button"
|
||||
(click)="excluirAtividade(atividade)">Excluir</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-meta-grid">
|
||||
<div>
|
||||
<span>Técnicos</span>
|
||||
<strong>{{ nomeTecnicosAtividade(atividade) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Horas</span>
|
||||
<strong>{{ totalHorasAtividade(atividade) | number:'1.2-2' }}</strong>
|
||||
<small>{{ atividade.horas_expediente | number:'1.2-2' }} exp. · {{ atividade.horas_extras | number:'1.2-2' }} extra</small>
|
||||
</div>
|
||||
<div>
|
||||
<span>Mão de obra</span>
|
||||
<strong>{{ atividade.valor_mao_obra | currency:'BRL':'symbol':'1.2-2' }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Materiais</span>
|
||||
<strong>{{ valorMateriaisAtividade(atividade) | currency:'BRL':'symbol':'1.2-2' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="materials-list" *ngIf="atividade.materiais && atividade.materiais.length > 0">
|
||||
<div class="material-chip" *ngFor="let material of atividade.materiais">
|
||||
{{ material.quantidade | number:'1.0-3' }} {{ material.unidade_snapshot }} · {{ material.descricao_snapshot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="empty-state" *ngIf="atividadesOrdenadas.length === 0 && !exibirFormAtividade">
|
||||
<strong>Nenhuma atividade registrada.</strong>
|
||||
<p>Adicione o primeiro serviço realizado nesta manutenção.</p>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Financeiro">
|
||||
<div class="tab-content">
|
||||
<div class="financial-grid">
|
||||
<article><span>Mão de obra</span><strong>{{ ordem.valor_mao_obra | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
<article><span>Insumos</span><strong>{{ ordem.valor_insumos | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
<article><span>Desconto</span><strong>{{ ordem.valor_desconto | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
<article><span>Acréscimo</span><strong>{{ ordem.valor_acrescimo | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
<article class="financial-total"><span>Valor total</span><strong>{{ ordem.valor_total | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
<article><span>Pago</span><strong>{{ ordem.valor_pago | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
<article><span>Pendente</span><strong>{{ valorPendente | currency:'BRL':'symbol':'1.2-2' }}</strong></article>
|
||||
</div>
|
||||
|
||||
<div class="tab-toolbar">
|
||||
<div>
|
||||
<h2>Pagamentos</h2>
|
||||
<p>O status financeiro é calculado pelos pagamentos registrados.</p>
|
||||
</div>
|
||||
<button mat-raised-button color="primary" type="button" (click)="exibirFormPagamento = true" *ngIf="!exibirFormPagamento || (formOrdem.get('tipo').value !== 'CORTESIA' && formOrdem.get('tipo').value !== 'GARANTIA')">
|
||||
Registrar pagamento
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form class="inline-form-card" [formGroup]="formPagamento" *ngIf="exibirFormPagamento">
|
||||
<div class="form-grid form-grid-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Data e hora</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="data_pagamento">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Valor</mat-label>
|
||||
<input matInput type="number" min="0.01" step="0.01" formControlName="valor">
|
||||
<span matPrefix>R$ </span>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Forma</mat-label>
|
||||
<mat-select formControlName="forma_pagamento">
|
||||
<mat-option *ngFor="let opcao of formaPagamentoOpcoes" [value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Referência</mat-label>
|
||||
<input matInput formControlName="referencia">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-4">
|
||||
<mat-label>Observações</mat-label>
|
||||
<textarea matInput rows="2" formControlName="observacoes"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="inline-form-actions">
|
||||
<button mat-button type="button" (click)="exibirFormPagamento = false">Cancelar</button>
|
||||
<button mat-raised-button color="primary" type="button" (click)="registrarPagamento()" [disabled]="salvandoPagamento">
|
||||
{{ salvandoPagamento ? 'Registrando...' : 'Registrar pagamento' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="simple-list-item" *ngFor="let pagamento of ordem.pagamentos">
|
||||
<div>
|
||||
<strong>{{ pagamento.valor | currency:'BRL':'symbol':'1.2-2' }}</strong>
|
||||
<span>{{ pagamento.data_pagamento | date:'dd/MM/yyyy HH:mm' }} · {{ pagamento.forma_pagamento }}</span>
|
||||
<small *ngIf="pagamento.referencia">{{ pagamento.referencia }}</small>
|
||||
</div>
|
||||
<button mat-button type="button" class="danger-button" *ngIf="!pagamento.cancelado_em" (click)="cancelarPagamento(pagamento)">
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="empty-state" *ngIf="(!ordem.pagamentos || ordem.pagamentos.length === 0) && formOrdem.get('tipo').value != 'CORTESIA' && formOrdem.get('tipo').value != 'GARANTIA'">
|
||||
<strong>Nenhum pagamento registrado.</strong>
|
||||
</div>
|
||||
<div
|
||||
class="billing-notice"
|
||||
*ngIf="formOrdem.get('tipo').value === 'CORTESIA'">
|
||||
|
||||
Esta OS é uma cortesia. Não é necessário registrar pagamento.
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="billing-notice"
|
||||
*ngIf="formOrdem.get('tipo').value === 'GARANTIA'">
|
||||
|
||||
Esta OS está em garantia. As horas e os materiais continuam
|
||||
registrados para controle interno, mas não são cobrados.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Odômetros">
|
||||
<div class="tab-content">
|
||||
<div class="tab-toolbar">
|
||||
<div>
|
||||
<h2>Histórico de odômetros</h2>
|
||||
<p>Leituras registradas na entrada, durante os testes e na saída.</p>
|
||||
</div>
|
||||
<button mat-raised-button color="primary" type="button"
|
||||
(click)="exibirFormOdometro = true" *ngIf="!exibirFormOdometro">
|
||||
Nova leitura
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form class="inline-form-card" [formGroup]="formOdometro" *ngIf="exibirFormOdometro">
|
||||
<div class="form-grid form-grid-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Tipo de leitura</mat-label>
|
||||
<mat-select formControlName="tipo_leitura">
|
||||
<mat-option *ngFor="let opcao of tipoLeituraOpcoes" [value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Data e hora</mat-label>
|
||||
<input matInput type="datetime-local" formControlName="leitura_em">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Total em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_total_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometro.get('odometro_total_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Conectado em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_conectado_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometro.get('odometro_conectado_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Movimento em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_movimento_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometro.get('odometro_movimento_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Parcial em segundos</mat-label>
|
||||
<input matInput type="number" min="0" formControlName="odometro_parcial_segundos">
|
||||
<mat-hint>{{ segundosParaTempo(formOdometro.get('odometro_parcial_segundos').value) }}</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="span-2">
|
||||
<mat-label>Observações</mat-label>
|
||||
<input matInput formControlName="observacoes">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="inline-form-actions">
|
||||
<button mat-button type="button" (click)="exibirFormOdometro = false">Cancelar</button>
|
||||
<button mat-raised-button color="primary" type="button"
|
||||
(click)="registrarOdometro()" [disabled]="salvandoOdometro">
|
||||
{{ salvandoOdometro ? 'Registrando...' : 'Registrar leitura' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="odometer-card" *ngFor="let leitura of ordem.odometros_da_os">
|
||||
<div class="odometer-header">
|
||||
<div>
|
||||
<strong>{{ leitura.tipo_leitura }}</strong>
|
||||
<span>{{ leitura.leitura_em | date:'dd/MM/yyyy HH:mm' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="odometer-grid">
|
||||
<div><span>Total</span><strong>{{ segundosParaTempo(leitura.odometro_total_segundos) }}</strong></div>
|
||||
<div><span>Conectado</span><strong>{{ segundosParaTempo(leitura.odometro_conectado_segundos) }}</strong></div>
|
||||
<div><span>Movimento</span><strong>{{ segundosParaTempo(leitura.odometro_movimento_segundos) }}</strong></div>
|
||||
<div><span>Parcial</span><strong>{{ segundosParaTempo(leitura.odometro_parcial_segundos) }}</strong></div>
|
||||
</div>
|
||||
|
||||
<p *ngIf="leitura.observacoes">{{ leitura.observacoes }}</p>
|
||||
</div>
|
||||
|
||||
<div class="empty-state" *ngIf="!ordem.odometros_da_os || ordem.odometros_da_os.length === 0">
|
||||
<strong>Nenhuma leitura vinculada a esta OS.</strong>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Histórico">
|
||||
<div class="tab-content">
|
||||
<div class="tab-toolbar">
|
||||
<div>
|
||||
<h2>Histórico de status</h2>
|
||||
<p>Alterações registradas durante a vida da OS.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item" *ngFor="let item of ordem.historico_status">
|
||||
<div class="timeline-dot"></div>
|
||||
<div>
|
||||
<strong>{{ labelStatus(item.status_novo) }}</strong>
|
||||
<span>{{ item.created_at | date:'dd/MM/yyyy HH:mm' }}</span>
|
||||
<p *ngIf="item.observacoes">{{ item.observacoes }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="empty-state" *ngIf="!ordem.historico_status || ordem.historico_status.length === 0">
|
||||
<strong>Nenhuma alteração de status registrada.</strong>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,589 @@
|
|||
:host {
|
||||
display: block;
|
||||
min-height: 100vh;
|
||||
background: #f4f6f8;
|
||||
color: #1f2933;
|
||||
}
|
||||
|
||||
.os-form-page {
|
||||
max-width: 1680px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.header-main,
|
||||
.header-actions,
|
||||
.activity-actions,
|
||||
.inline-form-actions,
|
||||
.section-heading,
|
||||
.inline-form-header,
|
||||
.subsection-heading,
|
||||
.tab-toolbar,
|
||||
.activity-header,
|
||||
.odometer-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.section-heading,
|
||||
.inline-form-header,
|
||||
.subsection-heading,
|
||||
.tab-toolbar,
|
||||
.activity-header,
|
||||
.odometer-header {
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.header-main,
|
||||
.header-actions,
|
||||
.activity-actions,
|
||||
.inline-form-actions {
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-main {
|
||||
align-items: flex-start;
|
||||
|
||||
h1 {
|
||||
margin: 4px 0 5px;
|
||||
color: #101828;
|
||||
font-size: 30px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #667085;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.section-kicker {
|
||||
color: #1769aa;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.1px;
|
||||
}
|
||||
|
||||
.section-card,
|
||||
.inline-form-card,
|
||||
.activity-card,
|
||||
.loading-card,
|
||||
.odometer-card,
|
||||
.simple-list-item,
|
||||
.os-tabs {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ec;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(16, 24, 40, 0.04);
|
||||
}
|
||||
|
||||
.section-card {
|
||||
margin-bottom: 18px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section-heading,
|
||||
.inline-form-header,
|
||||
.subsection-heading {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-heading h2,
|
||||
.tab-toolbar h2,
|
||||
.inline-form-header h3,
|
||||
.subsection-heading h4 {
|
||||
margin: 4px 0 0;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.section-heading p,
|
||||
.tab-toolbar p,
|
||||
.subsection-heading p {
|
||||
margin: 5px 0 0;
|
||||
color: #667085;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.form-grid-4 {
|
||||
grid-template-columns: repeat(4, minmax(160px, 1fr));
|
||||
}
|
||||
|
||||
.span-2 { grid-column: span 2; }
|
||||
.span-3 { grid-column: span 3; }
|
||||
.span-4 { grid-column: span 4; }
|
||||
|
||||
.form-grid .mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.os-tabs {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tab-toolbar {
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.inline-form-card {
|
||||
margin-bottom: 18px;
|
||||
padding: 18px;
|
||||
background: #fbfcfe;
|
||||
}
|
||||
|
||||
.inline-form-actions {
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 38px;
|
||||
color: #344054;
|
||||
font-size: 13px;
|
||||
|
||||
input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.compact-checkbox {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.materials-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #eaecf0;
|
||||
}
|
||||
|
||||
.material-row {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
minmax(170px, 1.1fr)
|
||||
minmax(220px, 1.8fr)
|
||||
90px
|
||||
110px
|
||||
140px
|
||||
140px
|
||||
90px
|
||||
auto;
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
margin-bottom: 10px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #eaecf0;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.empty-subsection,
|
||||
.empty-state {
|
||||
padding: 28px;
|
||||
color: #667085;
|
||||
text-align: center;
|
||||
background: #f8fafc;
|
||||
border: 1px dashed #d0d5dd;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
strong {
|
||||
display: block;
|
||||
color: #344054;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
margin-bottom: 14px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.activity-date {
|
||||
color: #1769aa;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.activity-header h3 {
|
||||
max-width: 1000px;
|
||||
margin: 6px 0 0;
|
||||
color: #344054;
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.activity-meta-grid,
|
||||
.financial-grid,
|
||||
.odometer-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.activity-meta-grid {
|
||||
grid-template-columns: 2fr repeat(3, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
.activity-meta-grid > div,
|
||||
.odometer-grid > div {
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.activity-meta-grid span,
|
||||
.odometer-grid span,
|
||||
.financial-grid span {
|
||||
display: block;
|
||||
color: #667085;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.35px;
|
||||
}
|
||||
|
||||
.activity-meta-grid strong,
|
||||
.odometer-grid strong {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #344054;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.activity-meta-grid small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: #98a2b3;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.materials-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.material-chip {
|
||||
padding: 5px 9px;
|
||||
background: #eff8ff;
|
||||
color: #175cd3;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.financial-grid {
|
||||
grid-template-columns: repeat(7, minmax(140px, 1fr));
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.financial-grid article {
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
border-radius: 9px;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
color: #101828;
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.financial-grid .financial-total {
|
||||
background: #eef6ff;
|
||||
|
||||
strong {
|
||||
color: #175cd3;
|
||||
}
|
||||
}
|
||||
|
||||
.simple-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
margin-bottom: 10px;
|
||||
padding: 14px 16px;
|
||||
|
||||
strong,
|
||||
span,
|
||||
small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
strong { color: #344054; }
|
||||
|
||||
span,
|
||||
small {
|
||||
margin-top: 3px;
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.odometer-card {
|
||||
margin-bottom: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.odometer-header {
|
||||
strong,
|
||||
span { display: block; }
|
||||
strong { color: #344054; }
|
||||
span {
|
||||
margin-top: 3px;
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.odometer-grid {
|
||||
grid-template-columns: repeat(4, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
.odometer-card p {
|
||||
margin: 12px 0 0;
|
||||
color: #667085;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 18px 1fr;
|
||||
gap: 12px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
bottom: -2px;
|
||||
left: 6px;
|
||||
width: 2px;
|
||||
background: #e4e7ec;
|
||||
}
|
||||
|
||||
&:last-child::before { display: none; }
|
||||
|
||||
strong,
|
||||
span { display: block; }
|
||||
strong { color: #344054; }
|
||||
span {
|
||||
margin-top: 3px;
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
}
|
||||
p {
|
||||
margin: 7px 0 0;
|
||||
color: #475467;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-top: 3px;
|
||||
background: #1769aa;
|
||||
border: 3px solid #eaf4ff;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-sucesso { color: #027a48; background: #ecfdf3; }
|
||||
.badge-alerta { color: #b54708; background: #fffaeb; }
|
||||
.badge-erro { color: #b42318; background: #fef3f2; }
|
||||
.badge-info { color: #175cd3; background: #eff8ff; }
|
||||
.badge-neutro { color: #475467; background: #f2f4f7; }
|
||||
|
||||
.message {
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 15px;
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.message-error {
|
||||
color: #b42318;
|
||||
background: #fef3f2;
|
||||
border: 1px solid #fecdca;
|
||||
}
|
||||
|
||||
.message-success {
|
||||
color: #027a48;
|
||||
background: #ecfdf3;
|
||||
border: 1px solid #abefc6;
|
||||
}
|
||||
|
||||
.loading-card {
|
||||
min-height: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
width: 170px;
|
||||
height: 4px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #d0d5dd 0%, #1769aa 45%, #d0d5dd 100%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.2s linear infinite;
|
||||
}
|
||||
|
||||
.danger-button { color: #b42318 !important; }
|
||||
|
||||
@keyframes loading {
|
||||
from { background-position: 200% 0; }
|
||||
to { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Blindagem contra o tema global antigo */
|
||||
:host ::ng-deep .os-form-page .mat-form-field,
|
||||
:host ::ng-deep .os-form-page .mat-input-element,
|
||||
:host ::ng-deep .os-form-page .mat-select-value,
|
||||
:host ::ng-deep .os-form-page .mat-select-value-text {
|
||||
color: #101828 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-form-field-label,
|
||||
:host ::ng-deep .os-form-page .mat-select-arrow,
|
||||
:host ::ng-deep .os-form-page .mat-hint {
|
||||
color: #667085 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-form-field-outline {
|
||||
color: #d0d5dd !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-form-field-outline-thick {
|
||||
color: #1769aa !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-form-field-flex {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-tab-label {
|
||||
color: #344054 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-tab-label-active {
|
||||
color: #1769aa !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-form-page .mat-tab-header {
|
||||
background: #f8fafc !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1350px) {
|
||||
.form-grid-4 { grid-template-columns: repeat(2, minmax(180px, 1fr)); }
|
||||
.span-3,
|
||||
.span-4 { grid-column: span 2; }
|
||||
.material-row { grid-template-columns: repeat(3, minmax(160px, 1fr)); }
|
||||
.financial-grid { grid-template-columns: repeat(4, minmax(150px, 1fr)); }
|
||||
.activity-meta-grid { grid-template-columns: repeat(2, minmax(180px, 1fr)); }
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.os-form-page { padding: 14px; }
|
||||
|
||||
.page-header,
|
||||
.header-main,
|
||||
.header-actions,
|
||||
.section-heading,
|
||||
.tab-toolbar,
|
||||
.activity-header,
|
||||
.subsection-heading {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
|
||||
button { width: 100%; }
|
||||
}
|
||||
|
||||
.form-grid-4,
|
||||
.material-row,
|
||||
.financial-grid,
|
||||
.activity-meta-grid,
|
||||
.odometer-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.span-2,
|
||||
.span-3,
|
||||
.span-4 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
.billing-notice {
|
||||
grid-column: span 4;
|
||||
padding: 12px 14px;
|
||||
color: #175cd3;
|
||||
background: #eff8ff;
|
||||
border: 1px solid #b2ddff;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OrionOrdemServicoFormComponent } from './orion-ordem-servico-form.component';
|
||||
|
||||
describe('OrionOrdemServicoFormComponent', () => {
|
||||
let component: OrionOrdemServicoFormComponent;
|
||||
let fixture: ComponentFixture<OrionOrdemServicoFormComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ OrionOrdemServicoFormComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OrionOrdemServicoFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,911 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormArray,
|
||||
FormBuilder,
|
||||
FormGroup,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import {
|
||||
ClienteManutencaoModel,
|
||||
EquipamentoManutencaoModel,
|
||||
MaterialManutencaoModel,
|
||||
OrdemServicoAtividadeMaterialModel,
|
||||
OrdemServicoAtividadeModel,
|
||||
OrdemServicoAtividadeSalvarModel,
|
||||
OrdemServicoAtividadeTecnicoModel,
|
||||
OrdemServicoDetalhesModel,
|
||||
OrdemServicoOdometroModel,
|
||||
OrdemServicoPagamentoModel,
|
||||
OrdemServicoSalvarModel,
|
||||
OrdemServicoStatus,
|
||||
OrdemServicoStatusPagamento,
|
||||
OrdemServicoTipo,
|
||||
TecnicoManutencaoModel
|
||||
} from '../../models/orionModel';
|
||||
|
||||
import { OrionService } from '../../services/orionService';
|
||||
|
||||
@Component({
|
||||
selector: 'app-orion-ordem-servico-form',
|
||||
templateUrl: './orion-ordem-servico-form.component.html',
|
||||
styleUrls: ['./orion-ordem-servico-form.component.scss']
|
||||
})
|
||||
export class OrionOrdemServicoFormComponent implements OnInit {
|
||||
|
||||
modoEdicao = false;
|
||||
ordemServicoId: number = null;
|
||||
ordem: OrdemServicoDetalhesModel = null;
|
||||
|
||||
carregando = true;
|
||||
salvando = false;
|
||||
salvandoAtividade = false;
|
||||
salvandoPagamento = false;
|
||||
salvandoOdometro = false;
|
||||
|
||||
erro = '';
|
||||
sucesso = '';
|
||||
|
||||
formOrdem: FormGroup;
|
||||
formOdometroEntrada: FormGroup;
|
||||
formAtividade: FormGroup;
|
||||
formPagamento: FormGroup;
|
||||
formOdometro: FormGroup;
|
||||
|
||||
clientes: ClienteManutencaoModel[] = [];
|
||||
equipamentos: EquipamentoManutencaoModel[] = [];
|
||||
tecnicos: TecnicoManutencaoModel[] = [];
|
||||
materiais: MaterialManutencaoModel[] = [];
|
||||
|
||||
exibirFormAtividade = false;
|
||||
atividadeEditandoId: number = null;
|
||||
exibirFormPagamento = false;
|
||||
exibirFormOdometro = false;
|
||||
|
||||
statusOpcoes = [
|
||||
{ valor: 'RASCUNHO', texto: 'Rascunho' },
|
||||
{ valor: 'ABERTA', texto: 'Aberta' },
|
||||
{ valor: 'EM_DIAGNOSTICO', texto: 'Em diagnóstico' },
|
||||
{ valor: 'AGUARDANDO_APROVACAO', texto: 'Aguardando aprovação' },
|
||||
{ valor: 'AGUARDANDO_PECA', texto: 'Aguardando peça' },
|
||||
{ valor: 'EM_EXECUCAO', texto: 'Em execução' },
|
||||
{ valor: 'EM_TESTES', texto: 'Em testes' },
|
||||
{ valor: 'FINALIZADA', texto: 'Finalizada' },
|
||||
{ valor: 'AGUARDANDO_RETIRADA', texto: 'Aguardando retirada' },
|
||||
{ valor: 'RETIRADA', texto: 'Retirada' },
|
||||
{ valor: 'CANCELADA', texto: 'Cancelada' }
|
||||
];
|
||||
|
||||
tipoOpcoes = [
|
||||
{ valor: 'CORRETIVA', texto: 'Corretiva' },
|
||||
{ valor: 'PREVENTIVA', texto: 'Preventiva' },
|
||||
{ valor: 'REFORMA', texto: 'Reforma' },
|
||||
{ valor: 'DESENVOLVIMENTO', texto: 'Desenvolvimento' },
|
||||
{ valor: 'FABRICACAO', texto: 'Fabricação' },
|
||||
{ valor: 'GARANTIA', texto: 'Garantia' },
|
||||
{ valor: 'CORTESIA', texto: 'Cortesia' },
|
||||
{ valor: 'INTERNA', texto: 'Interna' },
|
||||
{ valor: 'OUTRO', texto: 'Outro' }
|
||||
];
|
||||
|
||||
prioridadeOpcoes = [
|
||||
{ valor: 'BAIXA', texto: 'Baixa' },
|
||||
{ valor: 'NORMAL', texto: 'Normal' },
|
||||
{ valor: 'ALTA', texto: 'Alta' },
|
||||
{ valor: 'URGENTE', texto: 'Urgente' }
|
||||
];
|
||||
|
||||
formaPagamentoOpcoes = [
|
||||
{ valor: 'DINHEIRO', texto: 'Dinheiro' },
|
||||
{ valor: 'PIX', texto: 'PIX' },
|
||||
{ valor: 'TRANSFERENCIA', texto: 'Transferência' },
|
||||
{ valor: 'BOLETO', texto: 'Boleto' },
|
||||
{ valor: 'CARTAO', texto: 'Cartão' },
|
||||
{ valor: 'OUTRO', texto: 'Outro' }
|
||||
];
|
||||
|
||||
tipoLeituraOpcoes = [
|
||||
{ valor: 'SAIDA_MANUTENCAO', texto: 'Saída da manutenção' },
|
||||
{ valor: 'ATUALIZACAO_MANUAL', texto: 'Atualização manual' },
|
||||
{ valor: 'TELEMETRIA', texto: 'Telemetria' }
|
||||
];
|
||||
|
||||
baixandoPdf: boolean = false;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private orionService: OrionService
|
||||
) {
|
||||
this.criarFormularios();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
var idParam = this.route.snapshot.paramMap.get('id');
|
||||
var id = Number(idParam);
|
||||
|
||||
this.modoEdicao = !!idParam && !isNaN(id) && id > 0;
|
||||
this.ordemServicoId = this.modoEdicao ? id : null;
|
||||
|
||||
this.carregarDadosIniciais();
|
||||
}
|
||||
|
||||
tipoOrdemAlterado(tipo: OrdemServicoTipo) {
|
||||
var statusPagamentoAtual =
|
||||
this.formOrdem.get('status_pagamento').value;
|
||||
|
||||
if (tipo === 'CORTESIA') {
|
||||
this.formOrdem.patchValue({
|
||||
status_pagamento: 'CORTESIA'
|
||||
});
|
||||
|
||||
this.formAtividade.patchValue({
|
||||
cobravel: false
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (tipo === 'GARANTIA') {
|
||||
this.formOrdem.patchValue({
|
||||
status_pagamento: 'NAO_APLICAVEL'
|
||||
});
|
||||
|
||||
this.formAtividade.patchValue({
|
||||
cobravel: false
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
statusPagamentoAtual === 'CORTESIA' ||
|
||||
statusPagamentoAtual === 'NAO_APLICAVEL'
|
||||
) {
|
||||
this.formOrdem.patchValue({
|
||||
status_pagamento: 'PENDENTE'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private criarFormularios() {
|
||||
this.formOrdem = this.formBuilder.group({
|
||||
cliente_id: [null],
|
||||
equipamento_id: [null, Validators.required],
|
||||
tecnico_responsavel_id: [null],
|
||||
tipo: ['CORRETIVA', Validators.required],
|
||||
status: ['ABERTA', Validators.required],
|
||||
status_pagamento: ['PENDENTE'],
|
||||
prioridade: ['NORMAL', Validators.required],
|
||||
data_entrada: [this.formatarDataHoraLocal(new Date()), Validators.required],
|
||||
data_previsao: [''],
|
||||
data_finalizacao: [''],
|
||||
data_retirada: [''],
|
||||
problema_relatado: ['', Validators.required],
|
||||
diagnostico: [''],
|
||||
solucao_resumo: [''],
|
||||
responsavel_entrega: [''],
|
||||
responsavel_retirada: [''],
|
||||
observacoes_internas: [''],
|
||||
observacoes_cliente: [''],
|
||||
garantia_dias: [null],
|
||||
garantia_ate: [''],
|
||||
valor_desconto: [0],
|
||||
valor_acrescimo: [0],
|
||||
justificativa_ajuste_valor: [''],
|
||||
observacao_status: ['']
|
||||
});
|
||||
|
||||
this.formOdometroEntrada = this.formBuilder.group({
|
||||
odometro_total_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
odometro_conectado_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
odometro_movimento_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
odometro_parcial_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
leitura_em: [this.formatarDataHoraLocal(new Date())],
|
||||
observacoes: ['']
|
||||
});
|
||||
|
||||
this.formAtividade = this.formBuilder.group({
|
||||
data_atividade: [this.formatarData(new Date()), Validators.required],
|
||||
descricao: ['', Validators.required],
|
||||
ordem_exibicao: [0],
|
||||
horas_expediente: [0, [Validators.min(0)]],
|
||||
horas_extras: [0, [Validators.min(0)]],
|
||||
valor_hora_expediente: [60, [Validators.min(0)]],
|
||||
valor_hora_extra: [90, [Validators.min(0)]],
|
||||
cobravel: [true],
|
||||
observacoes_internas: [''],
|
||||
observacoes_cliente: [''],
|
||||
tecnico_ids: [[]],
|
||||
materiais: this.formBuilder.array([])
|
||||
});
|
||||
|
||||
this.formPagamento = this.formBuilder.group({
|
||||
data_pagamento: [this.formatarDataHoraLocal(new Date()), Validators.required],
|
||||
valor: [0, [Validators.required, Validators.min(0.01)]],
|
||||
forma_pagamento: ['PIX', Validators.required],
|
||||
referencia: [''],
|
||||
observacoes: ['']
|
||||
});
|
||||
|
||||
this.formOdometro = this.formBuilder.group({
|
||||
tipo_leitura: ['SAIDA_MANUTENCAO', Validators.required],
|
||||
leitura_em: [this.formatarDataHoraLocal(new Date()), Validators.required],
|
||||
odometro_total_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
odometro_conectado_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
odometro_movimento_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
odometro_parcial_segundos: [0, [Validators.required, Validators.min(0)]],
|
||||
observacoes: ['']
|
||||
});
|
||||
}
|
||||
|
||||
private carregarDadosIniciais() {
|
||||
this.carregando = true;
|
||||
this.erro = '';
|
||||
|
||||
Promise.all([
|
||||
this.orionService.getClientesManutencao(),
|
||||
this.orionService.getEquipamentosManutencao(),
|
||||
this.orionService.getTecnicosManutencao(),
|
||||
this.orionService.getMateriaisManutencao()
|
||||
])
|
||||
.then(resultados => {
|
||||
this.clientes = resultados[0] || [];
|
||||
this.equipamentos = resultados[1] || [];
|
||||
this.tecnicos = resultados[2] || [];
|
||||
this.materiais = resultados[3] || [];
|
||||
|
||||
if (this.modoEdicao) {
|
||||
return this.carregarOrdem();
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[OS FORM] Erro ao carregar dados:', error);
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível carregar os dados necessários.');
|
||||
})
|
||||
.then(() => {
|
||||
this.carregando = false;
|
||||
});
|
||||
}
|
||||
|
||||
private carregarOrdem(): Promise<any> {
|
||||
return this.orionService.getOrdemServico(this.ordemServicoId)
|
||||
.then(ordem => {
|
||||
this.ordem = ordem;
|
||||
this.preencherFormularioOrdem(ordem);
|
||||
this.preencherNovoOdometroComUltimaLeitura();
|
||||
});
|
||||
}
|
||||
|
||||
private preencherFormularioOrdem(ordem: OrdemServicoDetalhesModel) {
|
||||
this.formOrdem.patchValue({
|
||||
cliente_id: ordem.cliente_id || null,
|
||||
equipamento_id: ordem.equipamento_id || null,
|
||||
tecnico_responsavel_id: ordem.tecnico_responsavel_id || null,
|
||||
tipo: ordem.tipo || 'CORRETIVA',
|
||||
status: ordem.status || 'ABERTA',
|
||||
status_pagamento: ordem.status_pagamento || 'PENDENTE',
|
||||
prioridade: ordem.prioridade || 'NORMAL',
|
||||
data_entrada: this.converterDataApiParaLocal(ordem.data_entrada),
|
||||
data_previsao: this.converterDataApiParaLocal(ordem.data_previsao),
|
||||
data_finalizacao: this.converterDataApiParaLocal(ordem.data_finalizacao),
|
||||
data_retirada: this.converterDataApiParaLocal(ordem.data_retirada),
|
||||
problema_relatado: ordem.problema_relatado || '',
|
||||
diagnostico: ordem.diagnostico || '',
|
||||
solucao_resumo: ordem.solucao_resumo || '',
|
||||
responsavel_entrega: ordem.responsavel_entrega || '',
|
||||
responsavel_retirada: ordem.responsavel_retirada || '',
|
||||
observacoes_internas: ordem.observacoes_internas || '',
|
||||
observacoes_cliente: ordem.observacoes_cliente || '',
|
||||
garantia_dias: ordem.garantia_dias || null,
|
||||
garantia_ate: ordem.garantia_ate ? String(ordem.garantia_ate).substring(0, 10) : '',
|
||||
valor_desconto: this.numero(ordem.valor_desconto),
|
||||
valor_acrescimo: this.numero(ordem.valor_acrescimo),
|
||||
justificativa_ajuste_valor: ordem.justificativa_ajuste_valor || '',
|
||||
observacao_status: ''
|
||||
});
|
||||
}
|
||||
|
||||
equipamentoSelecionado(equipamentoId: number) {
|
||||
if (!equipamentoId || this.modoEdicao) {
|
||||
return;
|
||||
}
|
||||
|
||||
var equipamento = this.buscarEquipamento(equipamentoId);
|
||||
if (!equipamento) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.formOrdem.patchValue({ cliente_id: equipamento.cliente_id || null });
|
||||
this.formOdometroEntrada.patchValue({
|
||||
odometro_total_segundos: this.numero(equipamento.odometro_total_segundos),
|
||||
odometro_conectado_segundos: this.numero(equipamento.odometro_conectado_segundos),
|
||||
odometro_movimento_segundos: this.numero(equipamento.odometro_movimento_segundos),
|
||||
odometro_parcial_segundos: this.numero(equipamento.odometro_parcial_segundos)
|
||||
});
|
||||
}
|
||||
|
||||
salvarOrdem() {
|
||||
this.limparMensagens();
|
||||
|
||||
if (this.formOrdem.invalid) {
|
||||
this.marcarFormularioComoTocado(this.formOrdem);
|
||||
this.erro = 'Preencha os campos obrigatórios da ordem de serviço.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.modoEdicao && this.formOdometroEntrada.invalid) {
|
||||
this.marcarFormularioComoTocado(this.formOdometroEntrada);
|
||||
this.erro = 'Confira os odômetros de entrada.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.salvando = true;
|
||||
var payload = this.montarPayloadOrdem();
|
||||
var operacao = this.modoEdicao
|
||||
? this.orionService.updateOrdemServico(this.ordemServicoId, payload)
|
||||
: this.orionService.insertOrdemServico(payload);
|
||||
|
||||
operacao
|
||||
.then(resultado => {
|
||||
if (!this.modoEdicao) {
|
||||
var novoId = resultado.id || resultado.ordem_servico_id;
|
||||
this.router.navigate(['/orion-ordens-servico', novoId, 'editar']);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sucesso = 'Ordem de serviço atualizada com sucesso.';
|
||||
return this.recarregarOrdem();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[OS FORM] Erro ao salvar OS:', error);
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível salvar a ordem de serviço.');
|
||||
})
|
||||
.then(() => {
|
||||
this.salvando = false;
|
||||
});
|
||||
}
|
||||
|
||||
private montarPayloadOrdem(): OrdemServicoSalvarModel {
|
||||
var valor = this.formOrdem.getRawValue();
|
||||
var payload: OrdemServicoSalvarModel = {
|
||||
cliente_id: this.numeroOuNull(valor.cliente_id),
|
||||
equipamento_id: this.numeroOuNull(valor.equipamento_id),
|
||||
tecnico_responsavel_id: this.numeroOuNull(valor.tecnico_responsavel_id),
|
||||
tipo: valor.tipo,
|
||||
status: valor.status,
|
||||
status_pagamento: valor.status_pagamento,
|
||||
prioridade: valor.prioridade,
|
||||
data_entrada: this.normalizarDataHoraApi(valor.data_entrada),
|
||||
data_previsao: this.normalizarDataHoraApi(valor.data_previsao),
|
||||
data_finalizacao: this.normalizarDataHoraApi(valor.data_finalizacao),
|
||||
data_retirada: this.normalizarDataHoraApi(valor.data_retirada),
|
||||
problema_relatado: this.textoOuNull(valor.problema_relatado),
|
||||
diagnostico: this.textoOuNull(valor.diagnostico),
|
||||
solucao_resumo: this.textoOuNull(valor.solucao_resumo),
|
||||
responsavel_entrega: this.textoOuNull(valor.responsavel_entrega),
|
||||
responsavel_retirada: this.textoOuNull(valor.responsavel_retirada),
|
||||
observacoes_internas: this.textoOuNull(valor.observacoes_internas),
|
||||
observacoes_cliente: this.textoOuNull(valor.observacoes_cliente),
|
||||
garantia_dias: this.numeroOuNull(valor.garantia_dias),
|
||||
garantia_ate: valor.garantia_ate || null,
|
||||
valor_desconto: this.numero(valor.valor_desconto),
|
||||
valor_acrescimo: this.numero(valor.valor_acrescimo),
|
||||
justificativa_ajuste_valor: this.textoOuNull(valor.justificativa_ajuste_valor),
|
||||
observacao_status: this.textoOuNull(valor.observacao_status)
|
||||
};
|
||||
|
||||
if (!this.modoEdicao) {
|
||||
var odometro = this.formOdometroEntrada.getRawValue();
|
||||
payload.odometro_entrada = {
|
||||
tipo_leitura: 'ENTRADA_MANUTENCAO',
|
||||
leitura_em: this.normalizarDataHoraApi(odometro.leitura_em),
|
||||
odometro_total_segundos: this.numero(odometro.odometro_total_segundos),
|
||||
odometro_conectado_segundos: this.numero(odometro.odometro_conectado_segundos),
|
||||
odometro_movimento_segundos: this.numero(odometro.odometro_movimento_segundos),
|
||||
odometro_parcial_segundos: this.numero(odometro.odometro_parcial_segundos),
|
||||
observacoes: this.textoOuNull(odometro.observacoes)
|
||||
};
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
abrirNovaAtividade() {
|
||||
this.atividadeEditandoId = null;
|
||||
this.exibirFormAtividade = true;
|
||||
this.resetarFormAtividade();
|
||||
}
|
||||
|
||||
editarAtividade(atividade: OrdemServicoAtividadeModel) {
|
||||
this.atividadeEditandoId = atividade.id;
|
||||
this.exibirFormAtividade = true;
|
||||
|
||||
this.formAtividade.patchValue({
|
||||
data_atividade: atividade.data_atividade ? String(atividade.data_atividade).substring(0, 10) : this.formatarData(new Date()),
|
||||
descricao: atividade.descricao || '',
|
||||
ordem_exibicao: this.numero(atividade.ordem_exibicao),
|
||||
horas_expediente: this.numero(atividade.horas_expediente),
|
||||
horas_extras: this.numero(atividade.horas_extras),
|
||||
valor_hora_expediente: this.numero(atividade.valor_hora_expediente),
|
||||
valor_hora_extra: this.numero(atividade.valor_hora_extra),
|
||||
cobravel: !!atividade.cobravel,
|
||||
observacoes_internas: atividade.observacoes_internas || '',
|
||||
observacoes_cliente: atividade.observacoes_cliente || '',
|
||||
tecnico_ids: (atividade.tecnicos || []).map(tecnico => tecnico.tecnico_id)
|
||||
});
|
||||
|
||||
this.limparMateriaisAtividade();
|
||||
(atividade.materiais || []).forEach(material => this.adicionarMaterialAtividade(material));
|
||||
}
|
||||
|
||||
cancelarEdicaoAtividade() {
|
||||
this.exibirFormAtividade = false;
|
||||
this.atividadeEditandoId = null;
|
||||
this.resetarFormAtividade();
|
||||
}
|
||||
|
||||
salvarAtividade() {
|
||||
this.limparMensagens();
|
||||
|
||||
if (this.formAtividade.invalid) {
|
||||
this.marcarFormularioComoTocado(this.formAtividade);
|
||||
this.erro = 'Preencha a data e a descrição da atividade.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.salvandoAtividade = true;
|
||||
var payload = this.montarPayloadAtividade();
|
||||
var operacao = this.atividadeEditandoId
|
||||
? this.orionService.updateAtividadeOrdemServico(this.atividadeEditandoId, payload)
|
||||
: this.orionService.insertAtividadeOrdemServico(this.ordemServicoId, payload);
|
||||
|
||||
operacao
|
||||
.then(() => {
|
||||
this.sucesso = this.atividadeEditandoId
|
||||
? 'Atividade atualizada com sucesso.'
|
||||
: 'Atividade adicionada com sucesso.';
|
||||
this.cancelarEdicaoAtividade();
|
||||
return this.recarregarOrdem();
|
||||
})
|
||||
.catch(error => {
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível salvar a atividade.');
|
||||
})
|
||||
.then(() => {
|
||||
this.salvandoAtividade = false;
|
||||
});
|
||||
}
|
||||
|
||||
excluirAtividade(atividade: OrdemServicoAtividadeModel) {
|
||||
if (!window.confirm('Deseja realmente excluir esta atividade?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.orionService.deleteAtividadeOrdemServico(atividade.id)
|
||||
.then(() => {
|
||||
this.sucesso = 'Atividade excluída com sucesso.';
|
||||
return this.recarregarOrdem();
|
||||
})
|
||||
.catch(error => {
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível excluir a atividade.');
|
||||
});
|
||||
}
|
||||
|
||||
private montarPayloadAtividade(): OrdemServicoAtividadeSalvarModel {
|
||||
var valor = this.formAtividade.getRawValue();
|
||||
var tecnicosAtividade: OrdemServicoAtividadeTecnicoModel[] = [];
|
||||
var materiaisAtividade: OrdemServicoAtividadeMaterialModel[] = [];
|
||||
|
||||
(valor.tecnico_ids || []).forEach(tecnicoId => {
|
||||
tecnicosAtividade.push({ tecnico_id: this.numero(tecnicoId), papel: 'EXECUTOR' });
|
||||
});
|
||||
|
||||
(valor.materiais || []).forEach(material => {
|
||||
materiaisAtividade.push({
|
||||
material_id: this.numeroOuNull(material.material_id),
|
||||
descricao_snapshot: material.descricao_snapshot || 'Material não informado',
|
||||
unidade_snapshot: material.unidade_snapshot || 'UN',
|
||||
quantidade: this.numero(material.quantidade),
|
||||
valor_custo_unitario: this.numero(material.valor_custo_unitario),
|
||||
valor_cobrado_unitario: this.numero(material.valor_cobrado_unitario),
|
||||
cobravel: !!material.cobravel,
|
||||
observacoes: this.textoOuNull(material.observacoes)
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
data_atividade: valor.data_atividade,
|
||||
descricao: valor.descricao,
|
||||
ordem_exibicao: this.numero(valor.ordem_exibicao),
|
||||
horas_expediente: this.numero(valor.horas_expediente),
|
||||
horas_extras: this.numero(valor.horas_extras),
|
||||
valor_hora_expediente: this.numero(valor.valor_hora_expediente),
|
||||
valor_hora_extra: this.numero(valor.valor_hora_extra),
|
||||
cobravel: !!valor.cobravel,
|
||||
observacoes_internas: this.textoOuNull(valor.observacoes_internas),
|
||||
observacoes_cliente: this.textoOuNull(valor.observacoes_cliente),
|
||||
tecnicos: tecnicosAtividade,
|
||||
materiais: materiaisAtividade
|
||||
};
|
||||
}
|
||||
|
||||
get materiaisAtividade(): FormArray {
|
||||
return this.formAtividade.get('materiais') as FormArray;
|
||||
}
|
||||
|
||||
adicionarMaterialAtividade(materialExistente?: OrdemServicoAtividadeMaterialModel) {
|
||||
this.materiaisAtividade.push(this.formBuilder.group({
|
||||
material_id: [materialExistente ? materialExistente.material_id || null : null],
|
||||
descricao_snapshot: [materialExistente ? materialExistente.descricao_snapshot || '' : '', Validators.required],
|
||||
unidade_snapshot: [materialExistente ? materialExistente.unidade_snapshot || 'UN' : 'UN'],
|
||||
quantidade: [materialExistente ? this.numero(materialExistente.quantidade) : 1, [Validators.required, Validators.min(0.001)]],
|
||||
valor_custo_unitario: [materialExistente ? this.numero(materialExistente.valor_custo_unitario) : 0, [Validators.min(0)]],
|
||||
valor_cobrado_unitario: [materialExistente ? this.numero(materialExistente.valor_cobrado_unitario) : 0, [Validators.min(0)]],
|
||||
cobravel: [materialExistente ? !!materialExistente.cobravel : true],
|
||||
observacoes: [materialExistente ? materialExistente.observacoes || '' : '']
|
||||
}));
|
||||
}
|
||||
|
||||
removerMaterialAtividade(index: number) {
|
||||
this.materiaisAtividade.removeAt(index);
|
||||
}
|
||||
|
||||
materialCatalogoSelecionado(index: number, materialId: number) {
|
||||
var material = this.buscarMaterial(materialId);
|
||||
var grupo = this.materiaisAtividade.at(index) as FormGroup;
|
||||
if (!material || !grupo) {
|
||||
return;
|
||||
}
|
||||
|
||||
grupo.patchValue({
|
||||
descricao_snapshot: material.descricao,
|
||||
unidade_snapshot: material.unidade || 'UN',
|
||||
valor_custo_unitario: this.numero(material.custo_padrao),
|
||||
valor_cobrado_unitario: this.numero(material.preco_venda_padrao)
|
||||
});
|
||||
}
|
||||
|
||||
registrarPagamento() {
|
||||
this.limparMensagens();
|
||||
if (this.formPagamento.invalid) {
|
||||
this.marcarFormularioComoTocado(this.formPagamento);
|
||||
this.erro = 'Informe a data, o valor e a forma de pagamento.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.salvandoPagamento = true;
|
||||
var valor = this.formPagamento.getRawValue();
|
||||
var payload: OrdemServicoPagamentoModel = {
|
||||
data_pagamento: this.normalizarDataHoraApi(valor.data_pagamento),
|
||||
valor: this.numero(valor.valor),
|
||||
forma_pagamento: valor.forma_pagamento,
|
||||
referencia: this.textoOuNull(valor.referencia),
|
||||
observacoes: this.textoOuNull(valor.observacoes)
|
||||
};
|
||||
|
||||
this.orionService.insertPagamentoOrdemServico(this.ordemServicoId, payload)
|
||||
.then(() => {
|
||||
this.sucesso = 'Pagamento registrado com sucesso.';
|
||||
this.exibirFormPagamento = false;
|
||||
this.resetarFormPagamento();
|
||||
return this.recarregarOrdem();
|
||||
})
|
||||
.catch(error => {
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível registrar o pagamento.');
|
||||
})
|
||||
.then(() => {
|
||||
this.salvandoPagamento = false;
|
||||
});
|
||||
}
|
||||
|
||||
cancelarPagamento(pagamento: OrdemServicoPagamentoModel) {
|
||||
if (!pagamento.id || !window.confirm('Deseja realmente cancelar este pagamento?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.orionService.cancelPagamentoOrdemServico(pagamento.id)
|
||||
.then(() => this.recarregarOrdem())
|
||||
.catch(error => {
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível cancelar o pagamento.');
|
||||
});
|
||||
}
|
||||
|
||||
registrarOdometro() {
|
||||
this.limparMensagens();
|
||||
if (this.formOdometro.invalid) {
|
||||
this.marcarFormularioComoTocado(this.formOdometro);
|
||||
this.erro = 'Confira os campos da leitura de odômetro.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.salvandoOdometro = true;
|
||||
var valor = this.formOdometro.getRawValue();
|
||||
var payload: OrdemServicoOdometroModel = {
|
||||
tipo_leitura: valor.tipo_leitura,
|
||||
leitura_em: this.normalizarDataHoraApi(valor.leitura_em),
|
||||
odometro_total_segundos: this.numero(valor.odometro_total_segundos),
|
||||
odometro_conectado_segundos: this.numero(valor.odometro_conectado_segundos),
|
||||
odometro_movimento_segundos: this.numero(valor.odometro_movimento_segundos),
|
||||
odometro_parcial_segundos: this.numero(valor.odometro_parcial_segundos),
|
||||
observacoes: this.textoOuNull(valor.observacoes)
|
||||
};
|
||||
|
||||
this.orionService.insertOdometroOrdemServico(this.ordemServicoId, payload)
|
||||
.then(() => {
|
||||
this.sucesso = 'Leitura registrada com sucesso.';
|
||||
this.exibirFormOdometro = false;
|
||||
return this.recarregarOrdem();
|
||||
})
|
||||
.catch(error => {
|
||||
this.erro = this.obterMensagemErro(error, 'Não foi possível registrar a leitura.');
|
||||
})
|
||||
.then(() => {
|
||||
this.salvandoOdometro = false;
|
||||
});
|
||||
}
|
||||
|
||||
get atividadesOrdenadas(): OrdemServicoAtividadeModel[] {
|
||||
if (!this.ordem || !this.ordem.atividades) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.ordem.atividades.slice().sort((a, b) => {
|
||||
var dataA = new Date(a.data_atividade).getTime();
|
||||
var dataB = new Date(b.data_atividade).getTime();
|
||||
return dataA === dataB
|
||||
? this.numero(a.ordem_exibicao) - this.numero(b.ordem_exibicao)
|
||||
: dataB - dataA;
|
||||
});
|
||||
}
|
||||
|
||||
totalHorasAtividade(atividade: OrdemServicoAtividadeModel): number {
|
||||
return this.numero(atividade.horas_expediente) + this.numero(atividade.horas_extras);
|
||||
}
|
||||
|
||||
valorMateriaisAtividade(atividade: OrdemServicoAtividadeModel): number {
|
||||
return (atividade.materiais || []).reduce((total, material) => {
|
||||
return total + this.numero(material.valor_cobrado_total);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
nomeTecnicosAtividade(atividade: OrdemServicoAtividadeModel): string {
|
||||
var nomes = (atividade.tecnicos || []).map(tecnico => {
|
||||
return tecnico.tecnico_nome || ('Técnico #' + tecnico.tecnico_id);
|
||||
});
|
||||
return nomes.length > 0 ? nomes.join(', ') : 'Não informado';
|
||||
}
|
||||
|
||||
get valorPendente(): number {
|
||||
if (!this.ordem) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(0, this.numero(this.ordem.valor_total) - this.numero(this.ordem.valor_pago));
|
||||
}
|
||||
|
||||
labelStatus(status: OrdemServicoStatus): string {
|
||||
return this.buscarTexto(this.statusOpcoes, status);
|
||||
}
|
||||
|
||||
labelTipo(tipo: OrdemServicoTipo): string {
|
||||
return this.buscarTexto(this.tipoOpcoes, tipo);
|
||||
}
|
||||
|
||||
labelPagamento(status: OrdemServicoStatusPagamento): string {
|
||||
return this.buscarTexto([
|
||||
{ valor: 'NAO_APLICAVEL', texto: 'Não aplicável' },
|
||||
{ valor: 'PENDENTE', texto: 'Pendente' },
|
||||
{ valor: 'PARCIAL', texto: 'Parcial' },
|
||||
{ valor: 'RECEBIDO', texto: 'Recebido' },
|
||||
{ valor: 'CORTESIA', texto: 'Cortesia' }
|
||||
], status);
|
||||
}
|
||||
|
||||
classeStatus(status: OrdemServicoStatus): string {
|
||||
if (status === 'RETIRADA' || status === 'FINALIZADA') return 'badge-sucesso';
|
||||
if (status === 'AGUARDANDO_RETIRADA' || status === 'AGUARDANDO_PECA' || status === 'AGUARDANDO_APROVACAO') return 'badge-alerta';
|
||||
if (status === 'CANCELADA') return 'badge-erro';
|
||||
if (status === 'RASCUNHO') return 'badge-neutro';
|
||||
return 'badge-info';
|
||||
}
|
||||
|
||||
classePagamento(status: OrdemServicoStatusPagamento): string {
|
||||
if (status === 'RECEBIDO') return 'badge-sucesso';
|
||||
if (status === 'PENDENTE') return 'badge-erro';
|
||||
if (status === 'PARCIAL') return 'badge-alerta';
|
||||
return 'badge-neutro';
|
||||
}
|
||||
|
||||
segundosParaTempo(segundos: number): string {
|
||||
var total = Math.max(0, Math.floor(this.numero(segundos)));
|
||||
var horas = Math.floor(total / 3600);
|
||||
var minutos = Math.floor((total % 3600) / 60);
|
||||
var segundosRestantes = total % 60;
|
||||
return this.preencherZero(horas) + ':' + this.preencherZero(minutos) + ':' + this.preencherZero(segundosRestantes);
|
||||
}
|
||||
|
||||
voltar() {
|
||||
this.router.navigate(['/orion-ordens-servico']);
|
||||
}
|
||||
|
||||
private recarregarOrdem(): Promise<any> {
|
||||
return this.modoEdicao && this.ordemServicoId ? this.carregarOrdem() : Promise.resolve();
|
||||
}
|
||||
|
||||
private preencherNovoOdometroComUltimaLeitura() {
|
||||
if (!this.ordem || !this.ordem.odometros_da_os || this.ordem.odometros_da_os.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var leituras = this.ordem.odometros_da_os.slice().sort((a, b) => {
|
||||
return new Date(b.leitura_em).getTime() - new Date(a.leitura_em).getTime();
|
||||
});
|
||||
var ultima = leituras[0];
|
||||
|
||||
this.formOdometro.patchValue({
|
||||
odometro_total_segundos: this.numero(ultima.odometro_total_segundos),
|
||||
odometro_conectado_segundos: this.numero(ultima.odometro_conectado_segundos),
|
||||
odometro_movimento_segundos: this.numero(ultima.odometro_movimento_segundos),
|
||||
odometro_parcial_segundos: this.numero(ultima.odometro_parcial_segundos),
|
||||
leitura_em: this.formatarDataHoraLocal(new Date())
|
||||
});
|
||||
}
|
||||
|
||||
private resetarFormAtividade() {
|
||||
this.formAtividade.reset({
|
||||
data_atividade: this.formatarData(new Date()),
|
||||
descricao: '',
|
||||
ordem_exibicao: 0,
|
||||
horas_expediente: 0,
|
||||
horas_extras: 0,
|
||||
valor_hora_expediente: 60,
|
||||
valor_hora_extra: 90,
|
||||
cobravel: true,
|
||||
observacoes_internas: '',
|
||||
observacoes_cliente: '',
|
||||
tecnico_ids: []
|
||||
});
|
||||
this.limparMateriaisAtividade();
|
||||
}
|
||||
|
||||
private resetarFormPagamento() {
|
||||
this.formPagamento.reset({
|
||||
data_pagamento: this.formatarDataHoraLocal(new Date()),
|
||||
valor: 0,
|
||||
forma_pagamento: 'PIX',
|
||||
referencia: '',
|
||||
observacoes: ''
|
||||
});
|
||||
}
|
||||
|
||||
private limparMateriaisAtividade() {
|
||||
while (this.materiaisAtividade.length > 0) {
|
||||
this.materiaisAtividade.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private buscarEquipamento(equipamentoId: number): EquipamentoManutencaoModel {
|
||||
var id = this.numero(equipamentoId);
|
||||
for (var i = 0; i < this.equipamentos.length; i++) {
|
||||
if (this.numero(this.equipamentos[i].id) === id) return this.equipamentos[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private buscarMaterial(materialId: number): MaterialManutencaoModel {
|
||||
var id = this.numero(materialId);
|
||||
for (var i = 0; i < this.materiais.length; i++) {
|
||||
if (this.numero(this.materiais[i].id) === id) return this.materiais[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private buscarTexto(opcoes: any[], valor: string): string {
|
||||
for (var i = 0; i < opcoes.length; i++) {
|
||||
if (opcoes[i].valor === valor) return opcoes[i].texto;
|
||||
}
|
||||
return valor || '';
|
||||
}
|
||||
|
||||
private marcarFormularioComoTocado(controle: AbstractControl) {
|
||||
if (controle instanceof FormGroup) {
|
||||
Object.keys(controle.controls).forEach(chave => this.marcarFormularioComoTocado(controle.controls[chave]));
|
||||
} else if (controle instanceof FormArray) {
|
||||
controle.controls.forEach(item => this.marcarFormularioComoTocado(item));
|
||||
} else {
|
||||
controle.markAsTouched();
|
||||
}
|
||||
}
|
||||
|
||||
private formatarData(data: Date): string {
|
||||
return data.getFullYear() + '-' + this.preencherZero(data.getMonth() + 1) + '-' + this.preencherZero(data.getDate());
|
||||
}
|
||||
|
||||
private formatarDataHoraLocal(data: Date): string {
|
||||
return this.formatarData(data) + 'T' + this.preencherZero(data.getHours()) + ':' + this.preencherZero(data.getMinutes());
|
||||
}
|
||||
|
||||
private converterDataApiParaLocal(valor: string): string {
|
||||
if (!valor) return '';
|
||||
var texto = String(valor).replace(' ', 'T');
|
||||
return texto.length >= 16 ? texto.substring(0, 16) : texto;
|
||||
}
|
||||
|
||||
private normalizarDataHoraApi(valor: string): string {
|
||||
if (!valor) return null;
|
||||
var texto = String(valor).replace('T', ' ');
|
||||
return texto.length === 16 ? texto + ':00' : texto;
|
||||
}
|
||||
|
||||
private textoOuNull(valor: any): string {
|
||||
if (valor === null || valor === undefined) return null;
|
||||
var texto = String(valor).trim();
|
||||
return texto ? texto : null;
|
||||
}
|
||||
|
||||
private numero(valor: any): number {
|
||||
var convertido = Number(valor);
|
||||
return isNaN(convertido) ? 0 : convertido;
|
||||
}
|
||||
|
||||
private numeroOuNull(valor: any): number {
|
||||
if (valor === null || valor === undefined || valor === '') return null;
|
||||
var convertido = Number(valor);
|
||||
return isNaN(convertido) ? null : convertido;
|
||||
}
|
||||
|
||||
private preencherZero(valor: number): string {
|
||||
return valor < 10 ? '0' + valor : String(valor);
|
||||
}
|
||||
|
||||
private limparMensagens() {
|
||||
this.erro = '';
|
||||
this.sucesso = '';
|
||||
}
|
||||
|
||||
private obterMensagemErro(error: any, fallback: string): string {
|
||||
return error && error.message ? error.message : fallback;
|
||||
}
|
||||
|
||||
|
||||
baixarPdfCliente() {
|
||||
if (!this.modoEdicao || !this.ordemServicoId || this.baixandoPdf) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.baixandoPdf = true;
|
||||
this.erro = '';
|
||||
|
||||
this.orionService
|
||||
.downloadOrdemServicoPdf(this.ordemServicoId, false)
|
||||
.then(blob => {
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
|
||||
link.href = url;
|
||||
link.download = 'OS_' + this.ordemServicoId + '.pdf';
|
||||
link.style.display = 'none';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
window.setTimeout(() => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 1000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[OS FORM] Erro ao gerar PDF:', error);
|
||||
this.erro =
|
||||
'Não foi possível gerar o PDF da ordem de serviço.';
|
||||
})
|
||||
.then(() => {
|
||||
this.baixandoPdf = false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,387 @@
|
|||
<div class="os-page">
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<span class="eyebrow">ORIONTARD</span>
|
||||
<h1>Ordens de Serviço</h1>
|
||||
<p>Histórico de manutenções, horas trabalhadas e valores dos equipamentos.</p>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
(click)="atualizar()"
|
||||
[disabled]="carregando">
|
||||
Atualizar
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
(click)="novaOrdem()">
|
||||
Nova OS
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
(click)="baixarRelatorioPdf()"
|
||||
[disabled]="carregando || baixandoRelatorio">
|
||||
{{ baixandoRelatorio ? 'Gerando relatório...' : 'Relatório PDF' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="filters-card">
|
||||
<div class="filters-grid">
|
||||
|
||||
<mat-form-field appearance="outline" class="field-search">
|
||||
<mat-label>Pesquisar</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="busca"
|
||||
(keyup.enter)="aplicarFiltros()"
|
||||
placeholder="OS, equipamento, NS ou problema">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Status</mat-label>
|
||||
<mat-select [(ngModel)]="statusSelecionado">
|
||||
<mat-option
|
||||
*ngFor="let opcao of statusOpcoes"
|
||||
[value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Pagamento</mat-label>
|
||||
<mat-select [(ngModel)]="pagamentoSelecionado">
|
||||
<mat-option
|
||||
*ngFor="let opcao of pagamentoOpcoes"
|
||||
[value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Tipo</mat-label>
|
||||
<mat-select [(ngModel)]="tipoSelecionado">
|
||||
<mat-option
|
||||
*ngFor="let opcao of tipoOpcoes"
|
||||
[value]="opcao.valor">
|
||||
{{ opcao.texto }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Entrada inicial</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="pickerInicio"
|
||||
[(ngModel)]="dataInicio">
|
||||
<mat-datepicker-toggle matSuffix [for]="pickerInicio"></mat-datepicker-toggle>
|
||||
<mat-datepicker #pickerInicio></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Entrada final</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="pickerFim"
|
||||
[(ngModel)]="dataFim">
|
||||
<mat-datepicker-toggle matSuffix [for]="pickerFim"></mat-datepicker-toggle>
|
||||
<mat-datepicker #pickerFim></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="filters-actions">
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="limparFiltros()"
|
||||
[disabled]="carregando">
|
||||
Limpar
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
(click)="aplicarFiltros()"
|
||||
[disabled]="carregando">
|
||||
Filtrar
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary-grid">
|
||||
<article class="summary-card">
|
||||
<span class="summary-label">OS encontradas</span>
|
||||
<strong>{{ totalRegistros }}</strong>
|
||||
<small>{{ dataSource.data.length }} nesta página</small>
|
||||
</article>
|
||||
|
||||
<article class="summary-card">
|
||||
<span class="summary-label">Horas na página</span>
|
||||
<strong>{{ totalHorasPagina | number:'1.2-2' }}</strong>
|
||||
<small>Expediente + horas extras</small>
|
||||
</article>
|
||||
|
||||
<article class="summary-card">
|
||||
<span class="summary-label">Valor na página</span>
|
||||
<strong>{{ valorTotalPagina | currency:'BRL':'symbol':'1.2-2' }}</strong>
|
||||
<small>Soma das OS exibidas</small>
|
||||
</article>
|
||||
|
||||
<article class="summary-card">
|
||||
<span class="summary-label">Pagamentos abertos</span>
|
||||
<strong>{{ pendentesNaPagina }}</strong>
|
||||
<small>Pendentes ou parciais na página</small>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="table-card">
|
||||
|
||||
<div class="loading-state" *ngIf="carregando">
|
||||
<div class="loading-bar"></div>
|
||||
<span>Carregando ordens de serviço...</span>
|
||||
</div>
|
||||
|
||||
<div class="error-state" *ngIf="!carregando && erro">
|
||||
<strong>Não foi possível carregar as OS.</strong>
|
||||
<p>{{ erro }}</p>
|
||||
<button mat-stroked-button type="button" (click)="atualizar()">
|
||||
Tentar novamente
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="empty-state"
|
||||
*ngIf="!carregando && !erro && dataSource.data.length === 0">
|
||||
<strong>Nenhuma ordem de serviço encontrada.</strong>
|
||||
<p>Altere os filtros ou confira se a importação foi concluída.</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="table-scroll"
|
||||
*ngIf="!carregando && !erro && dataSource.data.length > 0">
|
||||
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="dataSource"
|
||||
class="os-table">
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>OS</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<span class="os-number">#{{ ordem.id }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="equipamento">
|
||||
<th mat-header-cell *matHeaderCellDef>Equipamento</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<div class="equipment-cell">
|
||||
<strong>{{ nomeEquipamento(ordem) }}</strong>
|
||||
<span *ngIf="ordem.numero_serie_snapshot">
|
||||
NS {{ ordem.numero_serie_snapshot }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="data_entrada">
|
||||
<th mat-header-cell *matHeaderCellDef>Entrada</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<div class="date-cell">
|
||||
<strong>{{ ordem.data_entrada | date:'dd/MM/yyyy' }}</strong>
|
||||
<span *ngIf="ordem.data_finalizacao">
|
||||
Finalizada em {{ ordem.data_finalizacao | date:'dd/MM/yyyy' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="problema_relatado">
|
||||
<th mat-header-cell *matHeaderCellDef>Problema relatado</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<span
|
||||
class="problem-text"
|
||||
[title]="ordem.problema_relatado || ''">
|
||||
{{ ordem.problema_relatado || 'Sem descrição' }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="tipo">
|
||||
<th mat-header-cell *matHeaderCellDef>Tipo</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
{{ labelTipo(ordem.tipo) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef>Status</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<span
|
||||
class="status-badge"
|
||||
[ngClass]="classeStatus(ordem.status)">
|
||||
{{ labelStatus(ordem.status) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="status_pagamento">
|
||||
<th mat-header-cell *matHeaderCellDef>Pagamento</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<span
|
||||
class="status-badge"
|
||||
[ngClass]="classePagamento(ordem.status_pagamento)">
|
||||
{{ labelPagamento(ordem.status_pagamento) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="horas">
|
||||
<th mat-header-cell *matHeaderCellDef>Horas</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<div class="hours-cell">
|
||||
<strong>{{ totalHoras(ordem) | number:'1.2-2' }}</strong>
|
||||
<span>
|
||||
{{ ordem.total_horas_expediente | number:'1.2-2' }} exp.
|
||||
·
|
||||
{{ ordem.total_horas_extras | number:'1.2-2' }} extra
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="valor_total">
|
||||
<th mat-header-cell *matHeaderCellDef>Total</th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<div class="money-cell">
|
||||
<strong>
|
||||
{{ ordem.valor_total | currency:'BRL':'symbol':'1.2-2' }}
|
||||
</strong>
|
||||
<span *ngIf="ordem.valor_pendente > 0">
|
||||
Pendente:
|
||||
{{ ordem.valor_pendente | currency:'BRL':'symbol':'1.2-2' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="acoes">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let ordem">
|
||||
<div class="row-actions">
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
(click)="abrirOrdem(ordem)">
|
||||
Detalhes
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="baixarPdf(ordem)"
|
||||
[disabled]="baixandoPdfId === ordem.id">
|
||||
{{ baixandoPdfId === ordem.id ? 'Gerando...' : 'PDF' }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: displayedColumns;"
|
||||
[class.selected-row]="ordemSelecionada && ordemSelecionada.id === row.id"
|
||||
(dblclick)="selecionarOrdem(row)">
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<mat-paginator
|
||||
*ngIf="!erro && totalRegistros > 0"
|
||||
[length]="totalRegistros"
|
||||
[pageIndex]="paginaAtual - 1"
|
||||
[pageSize]="tamanhoPagina"
|
||||
[pageSizeOptions]="opcoesTamanhoPagina"
|
||||
[disabled]="carregando"
|
||||
showFirstLastButtons
|
||||
(page)="onPageChange($event)">
|
||||
</mat-paginator>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="quick-view" *ngIf="ordemSelecionada">
|
||||
<div class="quick-view-header">
|
||||
<div>
|
||||
<span class="eyebrow">VISUALIZAÇÃO RÁPIDA</span>
|
||||
<h2>OS #{{ ordemSelecionada.id }}</h2>
|
||||
</div>
|
||||
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="ordemSelecionada = null">
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="quick-view-grid">
|
||||
<div>
|
||||
<span>Equipamento</span>
|
||||
<strong>{{ nomeEquipamento(ordemSelecionada) }}</strong>
|
||||
<small *ngIf="ordemSelecionada.numero_serie_snapshot">
|
||||
NS {{ ordemSelecionada.numero_serie_snapshot }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>Entrada</span>
|
||||
<strong>{{ ordemSelecionada.data_entrada | date:'dd/MM/yyyy' }}</strong>
|
||||
<small>
|
||||
{{ labelTipo(ordemSelecionada.tipo) }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>Horas</span>
|
||||
<strong>{{ totalHoras(ordemSelecionada) | number:'1.2-2' }}</strong>
|
||||
<small>
|
||||
{{ ordemSelecionada.total_horas_extras | number:'1.2-2' }}
|
||||
hora(s) extra(s)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>Valor total</span>
|
||||
<strong>
|
||||
{{ ordemSelecionada.valor_total | currency:'BRL':'symbol':'1.2-2' }}
|
||||
</strong>
|
||||
<small>
|
||||
Pago:
|
||||
{{ ordemSelecionada.valor_pago | currency:'BRL':'symbol':'1.2-2' }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-view-problem">
|
||||
<span>Problema relatado</span>
|
||||
<p>{{ ordemSelecionada.problema_relatado || 'Sem descrição' }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,630 @@
|
|||
:host {
|
||||
display: block;
|
||||
min-height: 100%;
|
||||
background: #f4f6f8;
|
||||
color: #1f2933;
|
||||
}
|
||||
|
||||
.os-page {
|
||||
padding: 24px;
|
||||
max-width: 1680px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h1 {
|
||||
margin: 4px 0 6px;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #667085;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: inline-block;
|
||||
color: #1769aa;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.2px;
|
||||
}
|
||||
|
||||
.header-actions,
|
||||
.filters-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.filters-card,
|
||||
.table-card,
|
||||
.quick-view {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ec;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(16, 24, 40, 0.04);
|
||||
}
|
||||
|
||||
.filters-card {
|
||||
padding: 18px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filters-grid {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
minmax(260px, 2fr)
|
||||
repeat(3, minmax(150px, 1fr))
|
||||
repeat(2, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.filters-grid .mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filters-actions {
|
||||
justify-content: flex-end;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(180px, 1fr));
|
||||
gap: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e4e7ec;
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
box-shadow: 0 2px 8px rgba(16, 24, 40, 0.03);
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #98a2b3;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.os-table {
|
||||
width: 100%;
|
||||
min-width: 1380px;
|
||||
border-collapse: collapse;
|
||||
|
||||
th.mat-header-cell {
|
||||
background: #f8fafc;
|
||||
color: #475467;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.45px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td.mat-cell,
|
||||
th.mat-header-cell {
|
||||
padding: 12px 14px;
|
||||
border-bottom-color: #edf0f3;
|
||||
}
|
||||
|
||||
tr.mat-row {
|
||||
transition: background 120ms ease;
|
||||
}
|
||||
|
||||
tr.mat-row:hover {
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
tr.selected-row {
|
||||
background: #eef6ff;
|
||||
}
|
||||
}
|
||||
|
||||
.os-number {
|
||||
color: #1769aa;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.equipment-cell,
|
||||
.date-cell,
|
||||
.hours-cell,
|
||||
.money-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
|
||||
strong {
|
||||
color: #344054;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #98a2b3;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.problem-text {
|
||||
display: block;
|
||||
max-width: 340px;
|
||||
color: #475467;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 2px 9px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-sucesso {
|
||||
background: #ecfdf3;
|
||||
color: #027a48;
|
||||
}
|
||||
|
||||
.badge-processando {
|
||||
background: #eff8ff;
|
||||
color: #175cd3;
|
||||
}
|
||||
|
||||
.badge-alerta {
|
||||
background: #fffaeb;
|
||||
color: #b54708;
|
||||
}
|
||||
|
||||
.badge-erro {
|
||||
background: #fef3f2;
|
||||
color: #b42318;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: #f4f3ff;
|
||||
color: #5925dc;
|
||||
}
|
||||
|
||||
.badge-neutro {
|
||||
background: #f2f4f7;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.error-state,
|
||||
.empty-state {
|
||||
min-height: 220px;
|
||||
padding: 36px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
strong {
|
||||
color: #344054;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p,
|
||||
span {
|
||||
color: #667085;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
width: 160px;
|
||||
height: 4px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
#d0d5dd 0%,
|
||||
#1769aa 45%,
|
||||
#d0d5dd 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
from {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-view {
|
||||
margin-top: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.quick-view-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
|
||||
h2 {
|
||||
margin: 4px 0 0;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-view-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(160px, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 18px;
|
||||
|
||||
> div {
|
||||
padding: 14px;
|
||||
background: #f8fafc;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
span,
|
||||
small {
|
||||
display: block;
|
||||
color: #667085;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
color: #344054;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-view-problem {
|
||||
margin-top: 14px;
|
||||
padding: 14px;
|
||||
border: 1px solid #eaecf0;
|
||||
border-radius: 9px;
|
||||
|
||||
span {
|
||||
color: #667085;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 7px 0 0;
|
||||
color: #344054;
|
||||
line-height: 1.55;
|
||||
}
|
||||
}
|
||||
|
||||
mat-paginator {
|
||||
border-top: 1px solid #edf0f3;
|
||||
}
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.filters-grid {
|
||||
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.summary-grid,
|
||||
.quick-view-grid {
|
||||
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.os-page {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.filters-grid,
|
||||
.summary-grid,
|
||||
.quick-view-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filters-actions {
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ISOLAMENTO CONTRA ESTILOS GLOBAIS E TEMA ESCURO DO MATERIAL
|
||||
// ============================================================
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
min-height: 100vh;
|
||||
background: #f4f6f8;
|
||||
color: #1f2933;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
CAMPOS DOS FILTROS
|
||||
------------------------------------------------------------ */
|
||||
|
||||
:host ::ng-deep .filters-card .mat-form-field {
|
||||
color: #101828 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-input-element {
|
||||
color: #101828 !important;
|
||||
caret-color: #1769aa !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-select-value,
|
||||
:host ::ng-deep .filters-card .mat-select-value-text {
|
||||
color: #101828 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-form-field-label {
|
||||
color: #667085 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-form-field.mat-focused .mat-form-field-label {
|
||||
color: #1769aa !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-form-field-outline {
|
||||
color: #d0d5dd !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-form-field-outline-thick {
|
||||
color: #1769aa !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-select-arrow {
|
||||
color: #667085 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card .mat-datepicker-toggle {
|
||||
color: #667085 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-card input::placeholder {
|
||||
color: #98a2b3 !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Remove qualquer fundo escuro herdado */
|
||||
:host ::ng-deep .filters-card .mat-form-field-flex {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
TABELA
|
||||
------------------------------------------------------------ */
|
||||
|
||||
:host ::ng-deep .os-table,
|
||||
:host ::ng-deep table.os-table {
|
||||
background: #ffffff !important;
|
||||
color: #344054 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table tr.mat-header-row {
|
||||
background: #f8fafc !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table tr.mat-row {
|
||||
background: #ffffff !important;
|
||||
color: #344054 !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sobrescreve o tr:nth-child(even) global.
|
||||
*/
|
||||
:host ::ng-deep .os-table tr.mat-row:nth-child(even) {
|
||||
background: #fbfcfd !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table tr.mat-row:hover {
|
||||
background: #f2f7fc !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table tr.mat-row.selected-row {
|
||||
background: #eaf4ff !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table th.mat-header-cell {
|
||||
background: #f8fafc !important;
|
||||
color: #475467 !important;
|
||||
border-color: #eaecf0 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table td.mat-cell {
|
||||
background: transparent !important;
|
||||
color: #344054 !important;
|
||||
border-color: #eaecf0 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table td,
|
||||
:host ::ng-deep .os-table th {
|
||||
color: #344054 !important;
|
||||
border-left: 0 !important;
|
||||
border-right: 0 !important;
|
||||
}
|
||||
|
||||
/* Textos internos das células */
|
||||
:host ::ng-deep .os-table .equipment-cell strong,
|
||||
:host ::ng-deep .os-table .date-cell strong,
|
||||
:host ::ng-deep .os-table .hours-cell strong,
|
||||
:host ::ng-deep .os-table .money-cell strong {
|
||||
color: #344054 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table .equipment-cell span,
|
||||
:host ::ng-deep .os-table .date-cell span,
|
||||
:host ::ng-deep .os-table .hours-cell span,
|
||||
:host ::ng-deep .os-table .money-cell span {
|
||||
color: #667085 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table .problem-text {
|
||||
color: #475467 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .os-table .os-number {
|
||||
color: #1769aa !important;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
BOTÕES
|
||||
------------------------------------------------------------ */
|
||||
|
||||
:host ::ng-deep .header-actions .mat-stroked-button,
|
||||
:host ::ng-deep .filters-actions .mat-button,
|
||||
:host ::ng-deep .os-table .mat-stroked-button,
|
||||
:host ::ng-deep .quick-view .mat-button {
|
||||
color: #344054 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .header-actions .mat-stroked-button,
|
||||
:host ::ng-deep .os-table .mat-stroked-button {
|
||||
border-color: #d0d5dd !important;
|
||||
background: #ffffff !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .header-actions .mat-raised-button[disabled] {
|
||||
background: #e4e7ec !important;
|
||||
color: #98a2b3 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .filters-actions .mat-raised-button {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
PAGINADOR
|
||||
------------------------------------------------------------ */
|
||||
|
||||
:host ::ng-deep .mat-paginator {
|
||||
background: #ffffff !important;
|
||||
color: #475467 !important;
|
||||
border-top: 1px solid #eaecf0;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-paginator .mat-select-value,
|
||||
:host ::ng-deep .mat-paginator .mat-select-value-text {
|
||||
color: #344054 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-paginator .mat-select-arrow {
|
||||
color: #667085 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-paginator .mat-icon-button {
|
||||
color: #475467 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-paginator .mat-icon-button[disabled] {
|
||||
color: #d0d5dd !important;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
BADGES
|
||||
------------------------------------------------------------ */
|
||||
|
||||
:host ::ng-deep .status-badge.badge-sucesso {
|
||||
background: #ecfdf3 !important;
|
||||
color: #027a48 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .status-badge.badge-processando {
|
||||
background: #eff8ff !important;
|
||||
color: #175cd3 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .status-badge.badge-alerta {
|
||||
background: #fffaeb !important;
|
||||
color: #b54708 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .status-badge.badge-erro {
|
||||
background: #fef3f2 !important;
|
||||
color: #b42318 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .status-badge.badge-info {
|
||||
background: #f4f3ff !important;
|
||||
color: #5925dc !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .status-badge.badge-neutro {
|
||||
background: #f2f4f7 !important;
|
||||
color: #475467 !important;
|
||||
}
|
||||
|
||||
.row-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OrionOrdensServicoComponent } from './orion-ordens-servico.component';
|
||||
|
||||
describe('OrionOrdensServicoComponent', () => {
|
||||
let component: OrionOrdensServicoComponent;
|
||||
let fixture: ComponentFixture<OrionOrdensServicoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ OrionOrdensServicoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OrionOrdensServicoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,429 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import {
|
||||
OrdemServicoFiltrosModel,
|
||||
OrdemServicoListaModel,
|
||||
OrdemServicoStatus,
|
||||
OrdemServicoStatusPagamento,
|
||||
OrdemServicoTipo
|
||||
} from '../../models/orionModel';
|
||||
|
||||
import { OrionService } from '../../services/orionService';
|
||||
|
||||
@Component({
|
||||
selector: 'app-orion-ordens-servico',
|
||||
templateUrl: './orion-ordens-servico.component.html',
|
||||
styleUrls: ['./orion-ordens-servico.component.scss']
|
||||
})
|
||||
export class OrionOrdensServicoComponent implements OnInit {
|
||||
|
||||
displayedColumns: string[] = [
|
||||
'id',
|
||||
'equipamento',
|
||||
'data_entrada',
|
||||
'problema_relatado',
|
||||
'tipo',
|
||||
'status',
|
||||
'status_pagamento',
|
||||
'horas',
|
||||
'valor_total',
|
||||
'acoes'
|
||||
];
|
||||
|
||||
dataSource = new MatTableDataSource<OrdemServicoListaModel>([]);
|
||||
ordemSelecionada: OrdemServicoListaModel = null;
|
||||
|
||||
carregando: boolean = false;
|
||||
erro: string = '';
|
||||
|
||||
paginaAtual: number = 1;
|
||||
tamanhoPagina: number = 25;
|
||||
totalRegistros: number = 0;
|
||||
totalPaginas: number = 0;
|
||||
opcoesTamanhoPagina: number[] = [10, 25, 50, 100];
|
||||
|
||||
busca: string = '';
|
||||
statusSelecionado: OrdemServicoStatus | '' = '';
|
||||
pagamentoSelecionado: OrdemServicoStatusPagamento | '' = '';
|
||||
tipoSelecionado: OrdemServicoTipo | '' = '';
|
||||
dataInicio: Date = null;
|
||||
dataFim: Date = null;
|
||||
|
||||
statusOpcoes = [
|
||||
{ valor: '', texto: 'Todos os status' },
|
||||
{ valor: 'RASCUNHO', texto: 'Rascunho' },
|
||||
{ valor: 'ABERTA', texto: 'Aberta' },
|
||||
{ valor: 'EM_DIAGNOSTICO', texto: 'Em diagnóstico' },
|
||||
{ valor: 'AGUARDANDO_APROVACAO', texto: 'Aguardando aprovação' },
|
||||
{ valor: 'AGUARDANDO_PECA', texto: 'Aguardando peça' },
|
||||
{ valor: 'EM_EXECUCAO', texto: 'Em execução' },
|
||||
{ valor: 'EM_TESTES', texto: 'Em testes' },
|
||||
{ valor: 'FINALIZADA', texto: 'Finalizada' },
|
||||
{ valor: 'RETIRADA', texto: 'Retirada' },
|
||||
{ valor: 'CANCELADA', texto: 'Cancelada' }
|
||||
];
|
||||
|
||||
pagamentoOpcoes = [
|
||||
{ valor: '', texto: 'Todos os pagamentos' },
|
||||
{ valor: 'NAO_APLICAVEL', texto: 'Não aplicável' },
|
||||
{ valor: 'PENDENTE', texto: 'Pendente' },
|
||||
{ valor: 'PARCIAL', texto: 'Parcial' },
|
||||
{ valor: 'RECEBIDO', texto: 'Recebido' },
|
||||
{ valor: 'CORTESIA', texto: 'Cortesia' }
|
||||
];
|
||||
|
||||
tipoOpcoes = [
|
||||
{ valor: '', texto: 'Todos os tipos' },
|
||||
{ valor: 'CORRETIVA', texto: 'Corretiva' },
|
||||
{ valor: 'PREVENTIVA', texto: 'Preventiva' },
|
||||
{ valor: 'REFORMA', texto: 'Reforma' },
|
||||
{ valor: 'DESENVOLVIMENTO', texto: 'Desenvolvimento' },
|
||||
{ valor: 'FABRICACAO', texto: 'Fabricação' },
|
||||
{ valor: 'GARANTIA', texto: 'Garantia' },
|
||||
{ valor: 'CORTESIA', texto: 'Cortesia' },
|
||||
{ valor: 'INTERNA', texto: 'Interna' },
|
||||
{ valor: 'OUTRO', texto: 'Outro' }
|
||||
];
|
||||
|
||||
baixandoPdfId: number = null;
|
||||
baixandoRelatorio: boolean = false;
|
||||
|
||||
constructor(
|
||||
private orionService: OrionService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.carregarOrdens();
|
||||
}
|
||||
|
||||
carregarOrdens() {
|
||||
this.carregando = true;
|
||||
this.erro = '';
|
||||
|
||||
var filtros: OrdemServicoFiltrosModel = {
|
||||
page: this.paginaAtual,
|
||||
pageSize: this.tamanhoPagina,
|
||||
busca: this.normalizarTexto(this.busca),
|
||||
status: this.statusSelecionado,
|
||||
status_pagamento: this.pagamentoSelecionado,
|
||||
tipo: this.tipoSelecionado,
|
||||
data_inicio: this.formatarDataApi(this.dataInicio),
|
||||
data_fim: this.formatarDataApi(this.dataFim)
|
||||
};
|
||||
|
||||
this.orionService
|
||||
.getOrdensServico(filtros)
|
||||
.then(resultado => {
|
||||
this.dataSource.data = resultado.items || [];
|
||||
|
||||
this.paginaAtual = resultado.pagination.page || 1;
|
||||
this.tamanhoPagina = resultado.pagination.pageSize || 25;
|
||||
this.totalRegistros = resultado.pagination.total || 0;
|
||||
this.totalPaginas = resultado.pagination.totalPages || 0;
|
||||
|
||||
if (
|
||||
this.ordemSelecionada &&
|
||||
!this.dataSource.data.some(
|
||||
ordem => ordem.id === this.ordemSelecionada.id
|
||||
)
|
||||
) {
|
||||
this.ordemSelecionada = null;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[OS] Erro ao carregar ordens:', error);
|
||||
|
||||
this.dataSource.data = [];
|
||||
this.totalRegistros = 0;
|
||||
this.totalPaginas = 0;
|
||||
|
||||
this.erro = error && error.message
|
||||
? error.message
|
||||
: 'Não foi possível carregar as ordens de serviço.';
|
||||
})
|
||||
.then(() => {
|
||||
this.carregando = false;
|
||||
});
|
||||
}
|
||||
|
||||
novaOrdem() {
|
||||
this.router.navigate([
|
||||
'/orion-ordens-servico',
|
||||
'nova'
|
||||
]);
|
||||
}
|
||||
|
||||
abrirOrdem(ordem: OrdemServicoListaModel) {
|
||||
this.router.navigate([
|
||||
'/orion-ordens-servico',
|
||||
ordem.id,
|
||||
'editar'
|
||||
]);
|
||||
}
|
||||
|
||||
aplicarFiltros() {
|
||||
this.paginaAtual = 1;
|
||||
this.ordemSelecionada = null;
|
||||
this.carregarOrdens();
|
||||
}
|
||||
|
||||
limparFiltros() {
|
||||
this.busca = '';
|
||||
this.statusSelecionado = '';
|
||||
this.pagamentoSelecionado = '';
|
||||
this.tipoSelecionado = '';
|
||||
this.dataInicio = null;
|
||||
this.dataFim = null;
|
||||
this.paginaAtual = 1;
|
||||
this.ordemSelecionada = null;
|
||||
|
||||
this.carregarOrdens();
|
||||
}
|
||||
|
||||
atualizar() {
|
||||
this.carregarOrdens();
|
||||
}
|
||||
|
||||
onPageChange(event: PageEvent) {
|
||||
this.paginaAtual = event.pageIndex + 1;
|
||||
this.tamanhoPagina = event.pageSize;
|
||||
this.ordemSelecionada = null;
|
||||
|
||||
this.carregarOrdens();
|
||||
}
|
||||
|
||||
selecionarOrdem(ordem: OrdemServicoListaModel) {
|
||||
this.ordemSelecionada = ordem;
|
||||
}
|
||||
|
||||
trackById(index: number, ordem: OrdemServicoListaModel): number {
|
||||
return ordem.id;
|
||||
}
|
||||
|
||||
get totalHorasPagina(): number {
|
||||
return this.dataSource.data.reduce(
|
||||
(total, ordem) =>
|
||||
total +
|
||||
this.numero(ordem.total_horas_expediente) +
|
||||
this.numero(ordem.total_horas_extras),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
get valorTotalPagina(): number {
|
||||
return this.dataSource.data.reduce(
|
||||
(total, ordem) => total + this.numero(ordem.valor_total),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
get pendentesNaPagina(): number {
|
||||
return this.dataSource.data.filter(
|
||||
ordem =>
|
||||
ordem.status_pagamento === 'PENDENTE' ||
|
||||
ordem.status_pagamento === 'PARCIAL'
|
||||
).length;
|
||||
}
|
||||
|
||||
totalHoras(ordem: OrdemServicoListaModel): number {
|
||||
return (
|
||||
this.numero(ordem.total_horas_expediente) +
|
||||
this.numero(ordem.total_horas_extras)
|
||||
);
|
||||
}
|
||||
|
||||
nomeEquipamento(ordem: OrdemServicoListaModel): string {
|
||||
return (
|
||||
ordem.equipamento_nome_snapshot ||
|
||||
ordem.modelo_snapshot ||
|
||||
'Equipamento não informado'
|
||||
);
|
||||
}
|
||||
|
||||
labelStatus(status: OrdemServicoStatus): string {
|
||||
return this.buscarTextoOpcao(this.statusOpcoes, status);
|
||||
}
|
||||
|
||||
labelPagamento(status: OrdemServicoStatusPagamento): string {
|
||||
return this.buscarTextoOpcao(this.pagamentoOpcoes, status);
|
||||
}
|
||||
|
||||
labelTipo(tipo: OrdemServicoTipo): string {
|
||||
return this.buscarTextoOpcao(this.tipoOpcoes, tipo);
|
||||
}
|
||||
|
||||
classeStatus(status: OrdemServicoStatus): string {
|
||||
switch (status) {
|
||||
case 'RETIRADA':
|
||||
case 'FINALIZADA':
|
||||
return 'badge-sucesso';
|
||||
|
||||
case 'EM_EXECUCAO':
|
||||
case 'EM_DIAGNOSTICO':
|
||||
case 'EM_TESTES':
|
||||
return 'badge-processando';
|
||||
|
||||
case 'AGUARDANDO_APROVACAO':
|
||||
case 'AGUARDANDO_PECA':
|
||||
return 'badge-alerta';
|
||||
|
||||
case 'CANCELADA':
|
||||
return 'badge-erro';
|
||||
|
||||
case 'RASCUNHO':
|
||||
return 'badge-neutro';
|
||||
|
||||
default:
|
||||
return 'badge-info';
|
||||
}
|
||||
}
|
||||
|
||||
classePagamento(status: OrdemServicoStatusPagamento): string {
|
||||
switch (status) {
|
||||
case 'RECEBIDO':
|
||||
return 'badge-sucesso';
|
||||
|
||||
case 'PARCIAL':
|
||||
return 'badge-alerta';
|
||||
|
||||
case 'PENDENTE':
|
||||
return 'badge-erro';
|
||||
|
||||
case 'CORTESIA':
|
||||
return 'badge-info';
|
||||
|
||||
default:
|
||||
return 'badge-neutro';
|
||||
}
|
||||
}
|
||||
|
||||
private buscarTextoOpcao(opcoes: any[], valor: string): string {
|
||||
for (var i = 0; i < opcoes.length; i++) {
|
||||
if (opcoes[i].valor === valor) {
|
||||
return opcoes[i].texto;
|
||||
}
|
||||
}
|
||||
|
||||
return valor;
|
||||
}
|
||||
|
||||
private normalizarTexto(valor: string): string {
|
||||
return valor ? valor.trim() : '';
|
||||
}
|
||||
|
||||
private formatarDataApi(data: Date): string {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var ano = data.getFullYear();
|
||||
var mes = data.getMonth() + 1;
|
||||
var dia = data.getDate();
|
||||
|
||||
return (
|
||||
ano +
|
||||
'-' +
|
||||
(mes < 10 ? '0' : '') + mes +
|
||||
'-' +
|
||||
(dia < 10 ? '0' : '') + dia
|
||||
);
|
||||
}
|
||||
|
||||
private numero(valor: any): number {
|
||||
var convertido = Number(valor);
|
||||
return isNaN(convertido) ? 0 : convertido;
|
||||
}
|
||||
|
||||
|
||||
baixarPdf(ordem: OrdemServicoListaModel) {
|
||||
if (!ordem || this.baixandoPdfId !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.baixandoPdfId = ordem.id;
|
||||
|
||||
this.orionService
|
||||
.downloadOrdemServicoPdf(ordem.id, false)
|
||||
.then(blob => {
|
||||
this.salvarBlob(
|
||||
blob,
|
||||
'OS_' +
|
||||
ordem.id +
|
||||
'_' +
|
||||
this.nomeEquipamento(ordem).replace(/[^a-zA-Z0-9]+/g, '_') +
|
||||
'.pdf'
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[OS] Erro ao baixar PDF:', error);
|
||||
this.erro =
|
||||
'Não foi possível gerar o PDF da ordem de serviço.';
|
||||
})
|
||||
.then(() => {
|
||||
this.baixandoPdfId = null;
|
||||
});
|
||||
}
|
||||
|
||||
baixarRelatorioPdf() {
|
||||
if (this.baixandoRelatorio) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.baixandoRelatorio = true;
|
||||
this.erro = '';
|
||||
|
||||
var filtros: any = {
|
||||
busca: this.normalizarTexto(this.busca),
|
||||
status: this.statusSelecionado,
|
||||
status_pagamento: this.pagamentoSelecionado,
|
||||
tipo: this.tipoSelecionado,
|
||||
data_inicio: this.formatarDataApi(this.dataInicio),
|
||||
data_fim: this.formatarDataApi(this.dataFim),
|
||||
|
||||
// Para fechamento mensal, a data de finalização é o critério mais seguro.
|
||||
criterio: 'data_finalizacao'
|
||||
};
|
||||
|
||||
this.orionService
|
||||
.downloadRelatorioOrdensServicoPdf(filtros)
|
||||
.then(blob => {
|
||||
var inicio = filtros.data_inicio || 'inicio';
|
||||
var fim = filtros.data_fim || 'fim';
|
||||
|
||||
this.salvarBlob(
|
||||
blob,
|
||||
'Relatorio_OS_' + inicio + '_' + fim + '.pdf'
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[OS] Erro ao baixar relatório:', error);
|
||||
this.erro =
|
||||
'Não foi possível gerar o relatório de serviços.';
|
||||
})
|
||||
.then(() => {
|
||||
this.baixandoRelatorio = false;
|
||||
});
|
||||
}
|
||||
|
||||
private salvarBlob(blob: Blob, nomeArquivo: string) {
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
|
||||
link.href = url;
|
||||
link.download = nomeArquivo;
|
||||
link.style.display = 'none';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
window.setTimeout(() => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,31 +1,489 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { GeoCoordenadasResponseModel, OrionLogsModel } from '../models/orionModel';
|
||||
|
||||
import {
|
||||
ClienteManutencaoModel,
|
||||
EquipamentoManutencaoModel,
|
||||
GeoCoordenadasResponseModel,
|
||||
MaterialManutencaoModel,
|
||||
OrionApiResponseModel,
|
||||
OrionLogInsertModel,
|
||||
OrionLogsModel,
|
||||
OrdemServicoAtividadeSalvarModel,
|
||||
OrdemServicoDetalhesModel,
|
||||
OrdemServicoFiltrosModel,
|
||||
OrdemServicoListaModel,
|
||||
OrdemServicoListaResponseModel,
|
||||
OrdemServicoOdometroModel,
|
||||
OrdemServicoOperacaoResultModel,
|
||||
OrdemServicoPagamentoModel,
|
||||
OrdemServicoSalvarModel,
|
||||
TecnicoManutencaoModel
|
||||
} from '../models/orionModel';
|
||||
|
||||
@Injectable()
|
||||
export class OrionService {
|
||||
|
||||
Rota: String = environment.ApiUrl + "/otp";
|
||||
private readonly Rota: string = environment.ApiUrl + '/otp';
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getAllLogs() {
|
||||
return this.http.get<OrionLogsModel[]>(`${this.Rota}/getAllLogs`)
|
||||
.toPromise()
|
||||
.then(response => <OrionLogsModel[]>response)
|
||||
.then(data => {
|
||||
return <OrionLogsModel[]>data['response'];
|
||||
})
|
||||
// ==========================================================
|
||||
// RESPOSTA PADRÃO
|
||||
// ==========================================================
|
||||
|
||||
private executar<T>(
|
||||
requisicao: Promise<OrionApiResponseModel<T>>
|
||||
): Promise<T> {
|
||||
return requisicao.then(resultado => {
|
||||
if (!resultado) {
|
||||
throw new Error('A API não retornou uma resposta.');
|
||||
}
|
||||
|
||||
if (resultado.status >= 400 || resultado.error) {
|
||||
const mensagem = resultado.error && resultado.error.message
|
||||
? resultado.error.message
|
||||
: 'Não foi possível concluir a operação.';
|
||||
|
||||
throw new Error(mensagem);
|
||||
}
|
||||
|
||||
return resultado.response;
|
||||
});
|
||||
}
|
||||
|
||||
coordenadaToEndereco(Coordenadas: string) {
|
||||
return this.http.get<GeoCoordenadasResponseModel>(`http://api.positionstack.com/v1/reverse?access_key=${environment.GeoAPI}&query=${Coordenadas}`)
|
||||
.toPromise()
|
||||
.then(response => <GeoCoordenadasResponseModel>response)
|
||||
.then(data => {
|
||||
return data;
|
||||
private montarParametros(filtros?: any): HttpParams {
|
||||
let params = new HttpParams();
|
||||
|
||||
if (!filtros) {
|
||||
return params;
|
||||
}
|
||||
|
||||
Object.keys(filtros).forEach(chave => {
|
||||
const valor = filtros[chave];
|
||||
|
||||
if (valor !== undefined && valor !== null && valor !== '') {
|
||||
params = params.set(chave, String(valor));
|
||||
}
|
||||
});
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
private numero(valor: any): number {
|
||||
const convertido = Number(valor);
|
||||
return Number.isFinite(convertido) ? convertido : 0;
|
||||
}
|
||||
|
||||
private normalizarOrdemLista(
|
||||
ordem: OrdemServicoListaModel
|
||||
): OrdemServicoListaModel {
|
||||
return {
|
||||
...ordem,
|
||||
id: this.numero(ordem.id),
|
||||
cliente_id: ordem.cliente_id ? this.numero(ordem.cliente_id) : null,
|
||||
equipamento_id: ordem.equipamento_id ? this.numero(ordem.equipamento_id) : null,
|
||||
tecnico_responsavel_id: ordem.tecnico_responsavel_id
|
||||
? this.numero(ordem.tecnico_responsavel_id)
|
||||
: null,
|
||||
|
||||
total_horas_expediente: this.numero(ordem.total_horas_expediente),
|
||||
total_horas_extras: this.numero(ordem.total_horas_extras),
|
||||
|
||||
valor_mao_obra: this.numero(ordem.valor_mao_obra),
|
||||
valor_insumos: this.numero(ordem.valor_insumos),
|
||||
valor_desconto: this.numero(ordem.valor_desconto),
|
||||
valor_acrescimo: this.numero(ordem.valor_acrescimo),
|
||||
valor_total: this.numero(ordem.valor_total),
|
||||
valor_pago: this.numero(ordem.valor_pago),
|
||||
valor_pendente: this.numero(ordem.valor_pendente)
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// LOGS
|
||||
// ==========================================================
|
||||
|
||||
getAllLogs(): Promise<OrionLogsModel[]> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<OrionLogsModel[]>>(
|
||||
`${this.Rota}/getAllLogs`
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
insertLog(log: OrionLogInsertModel): Promise<{ id: number }> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.post<OrionApiResponseModel<{ id: number }>>(
|
||||
`${this.Rota}/insertLog`,
|
||||
log
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
coordenadaToEndereco(
|
||||
coordenadas: string
|
||||
): Promise<GeoCoordenadasResponseModel> {
|
||||
return this.http
|
||||
.get<GeoCoordenadasResponseModel>(
|
||||
`http://api.positionstack.com/v1/reverse` +
|
||||
`?access_key=${environment.GeoAPI}` +
|
||||
`&query=${encodeURIComponent(coordenadas)}`
|
||||
)
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// CADASTROS AUXILIARES
|
||||
// ==========================================================
|
||||
|
||||
getClientesManutencao(): Promise<ClienteManutencaoModel[]> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<ClienteManutencaoModel[]>>(
|
||||
`${this.Rota}/getClientesManutencao`
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
getEquipamentosManutencao(): Promise<EquipamentoManutencaoModel[]> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<EquipamentoManutencaoModel[]>>(
|
||||
`${this.Rota}/getEquipamentosManutencao`
|
||||
)
|
||||
.toPromise()
|
||||
).then(equipamentos =>
|
||||
equipamentos.map(equipamento => ({
|
||||
...equipamento,
|
||||
id: this.numero(equipamento.id),
|
||||
cliente_id: equipamento.cliente_id
|
||||
? this.numero(equipamento.cliente_id)
|
||||
: null,
|
||||
modelo_id: equipamento.modelo_id
|
||||
? this.numero(equipamento.modelo_id)
|
||||
: null,
|
||||
odometro_total_segundos: this.numero(
|
||||
equipamento.odometro_total_segundos
|
||||
),
|
||||
odometro_conectado_segundos: this.numero(
|
||||
equipamento.odometro_conectado_segundos
|
||||
),
|
||||
odometro_movimento_segundos: this.numero(
|
||||
equipamento.odometro_movimento_segundos
|
||||
),
|
||||
odometro_parcial_segundos: this.numero(
|
||||
equipamento.odometro_parcial_segundos
|
||||
)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
getTecnicosManutencao(): Promise<TecnicoManutencaoModel[]> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<TecnicoManutencaoModel[]>>(
|
||||
`${this.Rota}/getTecnicosManutencao`
|
||||
)
|
||||
.toPromise()
|
||||
).then(tecnicos =>
|
||||
tecnicos.map(tecnico => ({
|
||||
...tecnico,
|
||||
id: this.numero(tecnico.id),
|
||||
usuario_sistema_id: tecnico.usuario_sistema_id
|
||||
? this.numero(tecnico.usuario_sistema_id)
|
||||
: null,
|
||||
valor_hora_expediente_padrao: this.numero(
|
||||
tecnico.valor_hora_expediente_padrao
|
||||
),
|
||||
valor_hora_extra_padrao: this.numero(
|
||||
tecnico.valor_hora_extra_padrao
|
||||
)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
getMateriaisManutencao(): Promise<MaterialManutencaoModel[]> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<MaterialManutencaoModel[]>>(
|
||||
`${this.Rota}/getMateriaisManutencao`
|
||||
)
|
||||
.toPromise()
|
||||
).then(materiais =>
|
||||
materiais.map(material => ({
|
||||
...material,
|
||||
id: this.numero(material.id),
|
||||
custo_padrao: this.numero(material.custo_padrao),
|
||||
preco_venda_padrao: this.numero(material.preco_venda_padrao)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// ORDENS DE SERVIÇO
|
||||
// ==========================================================
|
||||
|
||||
getOrdensServico(
|
||||
filtros: OrdemServicoFiltrosModel = {}
|
||||
): Promise<OrdemServicoListaResponseModel> {
|
||||
const params = this.montarParametros(filtros);
|
||||
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<OrdemServicoListaResponseModel>>(
|
||||
`${this.Rota}/getOrdensServico`,
|
||||
{ params }
|
||||
)
|
||||
.toPromise()
|
||||
).then(resultado => ({
|
||||
items: resultado.items.map(item => this.normalizarOrdemLista(item)),
|
||||
pagination: {
|
||||
page: this.numero(resultado.pagination.page),
|
||||
pageSize: this.numero(resultado.pagination.pageSize),
|
||||
total: this.numero(resultado.pagination.total),
|
||||
totalPages: this.numero(resultado.pagination.totalPages)
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
getOrdemServico(id: number): Promise<OrdemServicoDetalhesModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.get<OrionApiResponseModel<OrdemServicoDetalhesModel>>(
|
||||
`${this.Rota}/getOrdemServico/${id}`
|
||||
)
|
||||
.toPromise()
|
||||
).then(ordem => {
|
||||
const normalizada = this.normalizarOrdemLista(ordem);
|
||||
|
||||
return {
|
||||
...ordem,
|
||||
...normalizada,
|
||||
custo_insumos: this.numero(ordem.custo_insumos),
|
||||
atividades: (ordem.atividades || []).map(atividade => ({
|
||||
...atividade,
|
||||
id: this.numero(atividade.id),
|
||||
ordem_servico_id: this.numero(atividade.ordem_servico_id),
|
||||
ordem_exibicao: this.numero(atividade.ordem_exibicao),
|
||||
horas_expediente: this.numero(atividade.horas_expediente),
|
||||
horas_extras: this.numero(atividade.horas_extras),
|
||||
valor_hora_expediente: this.numero(
|
||||
atividade.valor_hora_expediente
|
||||
),
|
||||
valor_hora_extra: this.numero(atividade.valor_hora_extra),
|
||||
valor_mao_obra: this.numero(atividade.valor_mao_obra),
|
||||
tecnicos: atividade.tecnicos || [],
|
||||
materiais: (atividade.materiais || []).map(material => ({
|
||||
...material,
|
||||
quantidade: this.numero(material.quantidade),
|
||||
valor_custo_unitario: this.numero(
|
||||
material.valor_custo_unitario
|
||||
),
|
||||
valor_cobrado_unitario: this.numero(
|
||||
material.valor_cobrado_unitario
|
||||
),
|
||||
valor_custo_total: this.numero(material.valor_custo_total),
|
||||
valor_cobrado_total: this.numero(
|
||||
material.valor_cobrado_total
|
||||
)
|
||||
}))
|
||||
})),
|
||||
odometros_da_os: ordem.odometros_da_os || [],
|
||||
pagamentos: (ordem.pagamentos || []).map(pagamento => ({
|
||||
...pagamento,
|
||||
valor: this.numero(pagamento.valor)
|
||||
})),
|
||||
historico_status: ordem.historico_status || []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
insertOrdemServico(
|
||||
ordem: OrdemServicoSalvarModel
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.post<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/insertOrdemServico`,
|
||||
ordem
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
updateOrdemServico(
|
||||
id: number,
|
||||
ordem: OrdemServicoSalvarModel
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.put<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/updateOrdemServico/${id}`,
|
||||
ordem
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
deleteOrdemServico(
|
||||
id: number,
|
||||
usuarioId?: number
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.request<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
'DELETE',
|
||||
`${this.Rota}/deleteOrdemServico/${id}`,
|
||||
{
|
||||
body: {
|
||||
usuario_id: usuarioId || null
|
||||
}
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// ATIVIDADES
|
||||
// ==========================================================
|
||||
|
||||
insertAtividadeOrdemServico(
|
||||
ordemServicoId: number,
|
||||
atividade: OrdemServicoAtividadeSalvarModel
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.post<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/insertAtividadeOrdemServico/${ordemServicoId}`,
|
||||
atividade
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
updateAtividadeOrdemServico(
|
||||
atividadeId: number,
|
||||
atividade: OrdemServicoAtividadeSalvarModel
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.put<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/updateAtividadeOrdemServico/${atividadeId}`,
|
||||
atividade
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
deleteAtividadeOrdemServico(
|
||||
atividadeId: number
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.delete<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/deleteAtividadeOrdemServico/${atividadeId}`
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// ODÔMETROS
|
||||
// ==========================================================
|
||||
|
||||
insertOdometroOrdemServico(
|
||||
ordemServicoId: number,
|
||||
odometro: OrdemServicoOdometroModel
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.post<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/insertOdometroOrdemServico/${ordemServicoId}`,
|
||||
odometro
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// PAGAMENTOS
|
||||
// ==========================================================
|
||||
|
||||
insertPagamentoOrdemServico(
|
||||
ordemServicoId: number,
|
||||
pagamento: OrdemServicoPagamentoModel
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.post<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
`${this.Rota}/insertPagamentoOrdemServico/${ordemServicoId}`,
|
||||
pagamento
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
cancelPagamentoOrdemServico(
|
||||
pagamentoId: number,
|
||||
usuarioId?: number
|
||||
): Promise<OrdemServicoOperacaoResultModel> {
|
||||
return this.executar(
|
||||
this.http
|
||||
.request<OrionApiResponseModel<OrdemServicoOperacaoResultModel>>(
|
||||
'DELETE',
|
||||
`${this.Rota}/cancelPagamentoOrdemServico/${pagamentoId}`,
|
||||
{
|
||||
body: {
|
||||
usuario_id: usuarioId || null
|
||||
}
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
downloadOrdemServicoPdf(
|
||||
id: number,
|
||||
interno: boolean = false
|
||||
): Promise<Blob> {
|
||||
var url =
|
||||
this.Rota +
|
||||
'/downloadOrdemServicoPdf/' +
|
||||
id +
|
||||
'?interno=' +
|
||||
(interno ? '1' : '0');
|
||||
|
||||
return this.http
|
||||
.get(url, {
|
||||
responseType: 'blob' as 'json'
|
||||
})
|
||||
.toPromise()
|
||||
.then(response => response as Blob);
|
||||
}
|
||||
|
||||
downloadRelatorioOrdensServicoPdf(
|
||||
filtros: any = {}
|
||||
): Promise<Blob> {
|
||||
var params = this.montarParametros(filtros);
|
||||
|
||||
return this.http
|
||||
.get(
|
||||
this.Rota + '/downloadRelatorioOrdensServicoPdf',
|
||||
{
|
||||
params: params,
|
||||
responseType: 'blob' as 'json'
|
||||
}
|
||||
)
|
||||
.toPromise()
|
||||
.then(response => response as Blob);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue