This commit is contained in:
Andrey Cunha 2026-05-11 00:18:03 -03:00
parent 2946fc4f6e
commit dbb3061cac
4 changed files with 1117 additions and 63 deletions

View File

@ -2,13 +2,13 @@
"version": "1.0.0", "version": "1.0.0",
"configurations": [ "configurations": [
{ {
"id": "o9cixezp329movgapv2risawf8wvym", "id": "qhvjkjcxlzmp0m9vw6iehyg3gadm",
"name": "Ginseng Produção", "name": "Ginseng Produção",
"host": "comerciode188006.fluig.cloudtotvs.com.br", "host": "comerciode188006.fluig.cloudtotvs.com.br",
"ssl": true, "ssl": true,
"port": 443, "port": 443,
"username": "andrey.cunha", "username": "andrey.cunha",
"password": "eyJpdiI6IjM5MGNhMzMwMWIyNWI2ZDMyYmVmM2FiNDg5NzQ3NzZhIiwic2FsdCI6ImU1ZWE3MDZhOWE0NGMyMjExNmE3NjIyYmRmZTdmMjNhIiwidGV4dCI6ImZkMTY4MTAwNjM0ZmQ3NmI5M2MwZjQ1YmE4NDJmMjRhIn0=", "password": "eyJpdiI6IjI2NmJlZWRhYTE1NTk0M2ZkOTM4MWJlMDBlODlhMjFkIiwic2FsdCI6IjJlNzAwM2FiMzM3NTMzODEwMTk4Mzk0ODAxOGM0N2YwIiwidGV4dCI6IjliOWI2OGQ5MWFkNDNjOWU5ZDE4NjdjNWEyYWU5YzRlIn0=",
"userCode": "andrey.cunha", "userCode": "andrey.cunha",
"confirmExporting": false, "confirmExporting": false,
"hasBrowser": false, "hasBrowser": false,

View File

@ -1,39 +1,51 @@
<div id="MyWidget_${instanceId}" <div id="MyWidget_${instanceId}"
class="super-widget wcm-widget-class fluig-style-guide" class="super-widget wcm-widget-class fluig-style-guide compras-tracker-widget"
data-params="MyWidget.instance()"> data-params="MyWidget.instance()">
<div class="panel panel-default"> <section class="tracker-shell">
<div class="panel-heading"><strong>Consultar Solicitações</strong></div> <header class="tracker-header">
<div class="panel-body"> <div>
<h2>Tracker da Solicitacao de Compra</h2>
<p>Acompanhe SC, cotacao, pedido, aprovacao e entrega em uma unica tela.</p>
</div>
<button type="button" class="btn btn-default tracker-refresh" data-limpar>
<span class="fluigicon fluigicon-refresh"></span>
Limpar
</button>
</header>
<div class="tracker-search">
<div class="row"> <div class="row">
<div class="col-md-3"> <div class="col-md-3 col-sm-6">
<label>Número SC</label> <label for="filtroNumero_${instanceId}">Numero SC</label>
<input type="text" id="filtroNumero_${instanceId}" class="form-control"> <input type="text" id="filtroNumero_${instanceId}" class="form-control" placeholder="Ex.: 000211">
</div> </div>
<div class="col-md-3"> <div class="col-md-3 col-sm-6">
<label>Solicitante</label> <label for="filtroSolicitante_${instanceId}">Solicitante</label>
<input type="text" id="filtroSolicitante_${instanceId}" class="form-control"> <input type="text" id="filtroSolicitante_${instanceId}" class="form-control" placeholder="Opcional">
</div> </div>
<div class="col-md-3"> <div class="col-md-3 col-sm-6">
<label>Status</label> <label for="filtroStatus_${instanceId}">Status</label>
<select id="filtroStatus_${instanceId}" class="form-control"> <select id="filtroStatus_${instanceId}" class="form-control">
<option value="">Todos</option> <option value="">Todos</option>
<option value="Aguardando Aprovação">Aguardando Aprovação</option> <option value="Aguardando Aprovacao">Aguardando Aprovacao</option>
<option value="Em Cotação">Em Cotação</option> <option value="Em Cotacao">Em Cotacao</option>
<option value="Convertida em Pedido">Convertida em Pedido</option> <option value="Convertida em Pedido">Convertida em Pedido</option>
<option value="Encerrada">Encerrada</option> <option value="Encerrada">Encerrada</option>
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-3 col-sm-6 tracker-search-actions">
<label>&nbsp;</label><br> <label>&nbsp;</label>
<button class="btn btn-primary" data-pesquisar>Pesquisar</button> <button type="button" class="btn btn-primary btn-block" data-pesquisar>
<span class="fluigicon fluigicon-search"></span>
Pesquisar
</button>
</div> </div>
</div> </div>
<hr>
<div id="listaSolicitacoes_${instanceId}"></div>
</div> </div>
</div>
<div id="mensagemSolicitacoes_${instanceId}" class="tracker-message"></div>
<div id="listaSolicitacoes_${instanceId}" class="tracker-results"></div>
<div id="detalheSolicitacao_${instanceId}" class="tracker-detail"></div>
</section>
</div> </div>

View File

@ -1 +1,462 @@
/* Coloque aqui seu codigo CSS */ .compras-tracker-widget {
color: #172033;
}
.tracker-shell {
background: #f7f9fc;
border: 1px solid #dfe6ef;
border-radius: 8px;
padding: 18px;
}
.tracker-header {
align-items: center;
display: flex;
gap: 16px;
justify-content: space-between;
margin-bottom: 16px;
}
.tracker-header h2 {
color: #101828;
font-size: 22px;
font-weight: 700;
line-height: 1.2;
margin: 0 0 4px;
}
.tracker-header p {
color: #667085;
font-size: 13px;
margin: 0;
}
.tracker-search,
.tracker-card,
.tracker-table-wrap {
background: #fff;
border: 1px solid #d9e2ec;
border-radius: 8px;
box-shadow: 0 8px 22px rgba(16, 24, 40, .05);
}
.tracker-search {
margin-bottom: 14px;
padding: 16px;
}
.tracker-search label {
color: #344054;
font-size: 12px;
font-weight: 700;
}
.tracker-search-actions .btn {
margin-top: 0;
}
.tracker-refresh {
align-items: center;
display: inline-flex;
gap: 6px;
white-space: nowrap;
}
.tracker-message:empty {
display: none;
}
.tracker-results {
margin-bottom: 14px;
}
.tracker-table-wrap {
overflow-x: auto;
}
.tracker-table {
margin: 0;
}
.tracker-table > thead > tr > th {
background: #f8fafc;
border-bottom: 1px solid #d9e2ec;
color: #344054;
font-size: 12px;
white-space: nowrap;
}
.tracker-table > tbody > tr > td {
color: #344054;
font-size: 13px;
vertical-align: middle;
}
.tracker-detail {
margin-top: 14px;
}
.tracker-card {
margin-bottom: 14px;
padding: 16px;
}
.tracker-card-main {
padding: 18px;
}
.tracker-title-row,
.tracker-section-head {
align-items: center;
display: flex;
gap: 12px;
justify-content: space-between;
}
.tracker-title-row {
margin-bottom: 16px;
}
.tracker-title-row h3 {
color: #101828;
font-size: 20px;
font-weight: 700;
margin: 0 0 3px;
}
.tracker-title-row span {
color: #667085;
font-size: 12px;
}
.tracker-summary-grid {
display: grid;
gap: 12px;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.tracker-info {
background: #fbfcfe;
border: 1px solid #e6edf5;
border-radius: 6px;
min-height: 68px;
padding: 10px 12px;
}
.tracker-info span {
color: #667085;
display: block;
font-size: 11px;
font-weight: 700;
margin-bottom: 7px;
text-transform: uppercase;
}
.tracker-info strong {
color: #172033;
display: block;
font-size: 14px;
line-height: 1.25;
overflow-wrap: anywhere;
}
.tracker-steps {
align-items: flex-start;
display: grid;
grid-template-columns: repeat(5, minmax(96px, 1fr));
position: relative;
}
.tracker-steps:before {
background: #cfd8e3;
content: "";
height: 2px;
left: 10%;
position: absolute;
right: 10%;
top: 22px;
}
.tracker-step {
align-items: center;
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
position: relative;
text-align: center;
z-index: 1;
}
.tracker-step-icon {
align-items: center;
background: #d0d5dd;
border-radius: 999px;
color: #475467;
display: flex;
height: 44px;
justify-content: center;
margin-bottom: 4px;
width: 44px;
}
.tracker-step.is-done .tracker-step-icon {
background: #2563eb;
color: #fff;
}
.tracker-step strong {
color: #172033;
font-size: 12px;
line-height: 1.2;
}
.tracker-step span {
color: #667085;
font-size: 11px;
line-height: 1.2;
max-width: 120px;
overflow-wrap: anywhere;
}
.tracker-grid {
display: grid;
gap: 14px;
grid-template-columns: minmax(0, 1.2fr) minmax(320px, .8fr);
}
.tracker-section-head {
border-bottom: 1px solid #e6edf5;
margin: -2px -16px 12px;
padding: 0 16px 12px;
}
.tracker-section-head h4 {
color: #172033;
font-size: 16px;
font-weight: 700;
margin: 0;
}
.tracker-section-head > span {
align-items: center;
background: #eef2f7;
border-radius: 999px;
color: #344054;
display: inline-flex;
font-size: 12px;
font-weight: 700;
height: 24px;
justify-content: center;
min-width: 24px;
padding: 0 8px;
}
.tracker-mini-table {
overflow-x: auto;
}
.tracker-mini-table .table {
margin: 0;
}
.tracker-mini-table th {
color: #344054;
font-size: 12px;
white-space: nowrap;
}
.tracker-mini-table td {
color: #344054;
font-size: 12px;
vertical-align: middle;
}
.tracker-badge {
border-radius: 999px;
display: inline-flex;
font-size: 12px;
font-weight: 700;
line-height: 1;
padding: 7px 10px;
white-space: nowrap;
}
.tracker-badge.is-success {
background: #dcfce7;
color: #15803d;
}
.tracker-badge.is-warning {
background: #fef3c7;
color: #b45309;
}
.tracker-badge.is-danger {
background: #fee2e2;
color: #b42318;
}
.tracker-badge.is-info {
background: #dbeafe;
color: #1d4ed8;
}
.tracker-order-line {
align-items: center;
border: 1px solid #e6edf5;
border-radius: 6px;
display: grid;
gap: 10px;
grid-template-columns: 120px minmax(0, 1fr) auto;
padding: 10px 12px;
}
.tracker-order-line span {
color: #344054;
overflow-wrap: anywhere;
}
.tracker-order-line em {
color: #172033;
font-style: normal;
font-weight: 700;
}
.tracker-muted,
.tracker-updated {
color: #667085;
font-size: 12px;
margin-top: 10px;
}
.tracker-approval-list {
list-style: none;
margin: 0;
padding: 0;
}
.tracker-approval-list li {
align-items: flex-start;
display: flex;
gap: 10px;
padding: 9px 0;
}
.tracker-approval-list li + li {
border-top: 1px solid #eef2f7;
}
.tracker-approval-dot {
align-items: center;
border-radius: 999px;
display: inline-flex;
flex: 0 0 24px;
font-size: 12px;
font-weight: 700;
height: 24px;
justify-content: center;
width: 24px;
}
.tracker-approval-list .is-approved .tracker-approval-dot {
background: #16a34a;
color: #fff;
}
.tracker-approval-list .is-waiting .tracker-approval-dot {
background: #fef3c7;
color: #b45309;
}
.tracker-approval-list strong {
color: #172033;
display: block;
font-size: 13px;
}
.tracker-approval-list small {
color: #667085;
display: block;
font-size: 12px;
margin-top: 3px;
}
.tracker-resume {
display: grid;
gap: 10px 16px;
grid-template-columns: 150px minmax(0, 1fr);
margin: 0;
}
.tracker-resume dt {
color: #667085;
font-weight: 600;
}
.tracker-resume dd {
color: #172033;
font-weight: 600;
margin: 0;
overflow-wrap: anywhere;
}
.tracker-state,
.tracker-empty-inline {
align-items: center;
background: #fff;
border: 1px dashed #cfd8e3;
border-radius: 8px;
color: #667085;
display: flex;
gap: 8px;
justify-content: center;
min-height: 80px;
padding: 16px;
text-align: center;
}
.tracker-empty-inline {
background: #fbfcfe;
min-height: 74px;
}
@media (max-width: 992px) {
.tracker-summary-grid,
.tracker-grid {
grid-template-columns: 1fr;
}
.tracker-steps {
gap: 12px;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.tracker-steps:before {
display: none;
}
}
@media (max-width: 640px) {
.tracker-shell {
padding: 12px;
}
.tracker-header {
align-items: flex-start;
flex-direction: column;
}
.tracker-refresh {
width: 100%;
}
.tracker-steps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.tracker-order-line,
.tracker-resume {
grid-template-columns: 1fr;
}
}

View File

@ -1,55 +1,636 @@
var MyWidget = SuperWidget.extend({ var MyWidget = SuperWidget.extend({
// Variáveis do widget (se precisar salvar estado entre chamadas)
instanceId: null, instanceId: null,
// Método iniciado quando o widget é carregado
init: function() { init: function() {
this.instanceId = this.instanceId || new Date().getTime(); this.clearMessage();
console.log("Widget Solicitações carregado, instância:", this.instanceId); $("#detalheSolicitacao_" + this.instanceId).hide();
}, },
// BIND de eventos
bindings: { bindings: {
local: { local: {
'pesquisar': ['click_consultarSolicitacoes'] "pesquisar": ["click_consultarSolicitacoes"],
"acompanhar": ["click_acompanharSolicitacao"],
"limpar": ["click_limparConsulta"]
}, },
global: {} global: {}
}, },
// Função chamada ao clicar em "Pesquisar"
consultarSolicitacoes: function(htmlElement, event) { consultarSolicitacoes: function(htmlElement, event) {
var numero = $("#filtroNumero_" + this.instanceId).val(); if (event) event.preventDefault();
var solicitante = $("#filtroSolicitante_" + this.instanceId).val();
var status = $("#filtroStatus_" + this.instanceId).val();
var constraints = []; var numero = this.normalizarTexto($("#filtroNumero_" + this.instanceId).val());
if (numero) constraints.push(DatasetFactory.createConstraint("C1_NUM", numero, numero, ConstraintType.MUST)); var solicitante = this.normalizarTexto($("#filtroSolicitante_" + this.instanceId).val());
if (solicitante) constraints.push(DatasetFactory.createConstraint("C1_SOLICIT", solicitante, solicitante, ConstraintType.MUST)); var status = this.normalizarTexto($("#filtroStatus_" + this.instanceId).val());
if (status) constraints.push(DatasetFactory.createConstraint("STATUS", status, status, ConstraintType.MUST));
var ds = DatasetFactory.getDataset("dsSolicitacoesCompra", null, constraints, null); this.clearMessage();
$("#detalheSolicitacao_" + this.instanceId).hide().empty();
let html = `<table class="table table-striped"> if (numero) {
<thead><tr> this.renderizarConsultaPorNumero(numero);
<th>Número</th><th>Solicitante</th><th>Data</th><th>Status</th><th>Pedido</th> return;
</tr></thead><tbody>`;
if (ds && ds.values.length) {
ds.values.forEach(sc => {
html += `<tr>
<td>${sc.C1_NUM}</td>
<td>${sc.C1_SOLICIT}</td>
<td>${sc.C1_EMISSAO}</td>
<td>${sc.STATUS}</td>
<td>${sc.C1_PEDIDO || '-'}</td>
</tr>`;
});
} else {
html += `<tr><td colspan="5" class="text-center">Nenhuma solicitação encontrada</td></tr>`;
} }
html += `</tbody></table>`; this.renderizarListaGenerica(solicitante, status);
$("#listaSolicitacoes_" + this.instanceId).html(html); },
acompanharSolicitacao: function(htmlElement, event) {
if (event) event.preventDefault();
var numero = $(htmlElement).attr("data-numero-sc");
if (!numero) return;
$("#filtroNumero_" + this.instanceId).val(numero);
this.renderizarTracker(numero);
},
limparConsulta: function(htmlElement, event) {
if (event) event.preventDefault();
$("#filtroNumero_" + this.instanceId).val("");
$("#filtroSolicitante_" + this.instanceId).val("");
$("#filtroStatus_" + this.instanceId).val("");
$("#listaSolicitacoes_" + this.instanceId).empty();
$("#detalheSolicitacao_" + this.instanceId).hide().empty();
this.clearMessage();
},
bindDynamicActions: function() {
var self = this;
$("#listaSolicitacoes_" + this.instanceId)
.find("[data-acompanhar]")
.off("click.tracker")
.on("click.tracker", function(event) {
self.acompanharSolicitacao(this, event);
});
},
renderizarConsultaPorNumero: function(numero) {
var dados = this.consultarSC(numero);
if (!dados || !dados.rows.length || !this.temSCValida(dados.rows[0])) {
$("#listaSolicitacoes_" + this.instanceId).html(this.emptyState("Nenhuma solicitacao encontrada para a SC informada."));
return;
}
$("#listaSolicitacoes_" + this.instanceId).html(this.montarTabelaSolicitacoes(dados.rows));
this.bindDynamicActions();
this.renderizarTracker(numero, dados);
},
renderizarListaGenerica: function(solicitante, status) {
var ds = null;
var constraints = [];
try {
if (solicitante) constraints.push(this.criarConstraint("C1_SOLICIT", solicitante, solicitante));
if (status) constraints.push(this.criarConstraint("STATUS", status, status));
ds = this.buscarDataset("dsSolicitacoesCompra", null, constraints, null);
} catch (e) {
ds = null;
}
if (!ds || !ds.values || !ds.values.length) {
this.showMessage("Informe o numero da SC para abrir o tracker. A listagem geral depende de um dataset de consulta de solicitacoes no ambiente Fluig.", "info");
$("#listaSolicitacoes_" + this.instanceId).html(this.emptyState("Digite uma SC e clique em Pesquisar."));
return;
}
$("#listaSolicitacoes_" + this.instanceId).html(this.montarTabelaSolicitacoes(ds.values));
this.bindDynamicActions();
},
consultarSC: function(numero) {
var constraints = [
this.criarConstraint("numeroSCProtheus", numero, numero)
];
var ds = this.buscarDataset("ds_consultaSC", null, constraints, null);
return {
dataset: ds,
rows: ds && ds.values ? ds.values : []
};
},
criarConstraint: function(campo, valorInicial, valorFinal) {
if (typeof DatasetFactory !== "undefined" && typeof ConstraintType !== "undefined") {
return DatasetFactory.createConstraint(campo, valorInicial, valorFinal, ConstraintType.MUST);
}
return {
_field: campo,
_initialValue: valorInicial,
_finalValue: valorFinal,
_type: 1,
fieldName: campo,
initialValue: valorInicial,
finalValue: valorFinal,
constraintType: 1
};
},
buscarDataset: function(nome, fields, constraints, sortFields) {
if (typeof DatasetFactory !== "undefined") {
return DatasetFactory.getDataset(nome, fields, constraints, sortFields);
}
var retorno = { values: [] };
$.ajax({
url: "/api/public/ecm/dataset/datasets",
type: "POST",
async: false,
contentType: "application/json",
dataType: "json",
data: JSON.stringify({
name: nome,
fields: fields || null,
constraints: constraints || [],
order: sortFields || null
}),
success: function(response) {
var content = response && response.content ? response.content : response;
retorno = {
values: content && content.values ? content.values : []
};
},
error: function(xhr) {
var mensagem = xhr && xhr.responseText ? xhr.responseText : "Falha ao consultar dataset " + nome;
throw new Error(mensagem);
}
});
return retorno;
},
renderizarTracker: function(numero, dadosConsulta) {
var detalhe = $("#detalheSolicitacao_" + this.instanceId);
detalhe.show().html(this.loadingState("Consultando dados da SC " + this.escapeHTML(numero) + "..."));
try {
var dados = dadosConsulta || this.consultarSC(numero);
if (!dados || !dados.rows.length || !this.temSCValida(dados.rows[0])) {
detalhe.html(this.emptyState("Nao foi possivel carregar o tracker desta SC."));
return;
}
var resumo = this.montarResumoSC(dados.rows);
detalhe.html(this.montarTracker(resumo));
} catch (e) {
detalhe.html(this.emptyState("Erro ao carregar o tracker: " + this.escapeHTML(String(e))));
}
},
montarTabelaSolicitacoes: function(rows) {
var exibidos = {};
var html = [
'<div class="tracker-table-wrap">',
'<table class="table tracker-table">',
'<thead><tr>',
'<th>Solicitacao</th>',
'<th>Solicitante</th>',
'<th>Data</th>',
'<th>Status</th>',
'<th>Pedido</th>',
'<th class="text-right">Acao</th>',
'</tr></thead><tbody>'
];
for (var i = 0; i < rows.length; i++) {
var row = rows[i] || {};
var numero = this.primeiroValor(row.C1_NUM, row.numero, row.NUMERO);
if (!numero) continue;
if (exibidos[numero]) continue;
exibidos[numero] = true;
html.push(
'<tr>',
'<td><strong>SC ' + this.escapeHTML(numero) + '</strong></td>',
'<td>' + this.escapeHTML(this.primeiroValor(row.C1_SOLICIT, row.solicitante, "-")) + '</td>',
'<td>' + this.escapeHTML(this.formatarData(this.primeiroValor(row.C1_EMISSAO, row.data, ""))) + '</td>',
'<td>' + this.montarBadge(this.primeiroValor(row.STATUS, row.status, "-")) + '</td>',
'<td>' + this.escapeHTML(this.formatarDocumento(this.primeiroValor(row.C1_PEDIDO, row.pedido, "-"))) + '</td>',
'<td class="text-right">',
'<button type="button" class="btn btn-default btn-sm" data-acompanhar data-numero-sc="' + this.escapeHTML(numero) + '">',
'<span class="fluigicon fluigicon-eye-open"></span> Acompanhar',
'</button>',
'</td>',
'</tr>'
);
}
html.push('</tbody></table></div>');
return html.join("");
},
montarResumoSC: function(rows) {
var base = rows[0] || {};
var cotacoes = this.parseJsonArray(base.COTACOES_JSON);
var pedidos = this.parseJsonArray(base.PEDIDO_JSON);
var totalSC = this.somarCampo(rows, "C1_TOTAL");
var menorCotacao = this.encontrarMenorCotacao(cotacoes);
var pedido = this.formatarDocumento(this.primeiroValor(base.C1_PEDIDO, menorCotacao ? menorCotacao.C8_NUMPED : ""));
var cotacao = this.formatarDocumento(this.primeiroValor(base.C1_COTACAO, menorCotacao ? menorCotacao.C8_NUM : ""));
var status = this.definirStatus(base, cotacao, pedido);
var assinaturas = this.extrairAssinaturasPedido(pedidos);
return {
numero: this.primeiroValor(base.C1_NUM, ""),
solicitante: this.primeiroValor(base.C1_SOLICIT, "-"),
data: this.formatarData(base.C1_EMISSAO),
valor: totalSC > 0 ? this.formatarMoeda(totalSC) : this.formatarMoeda(this.numeroBR(base.C1_TOTAL)),
status: status,
cotacao: cotacao,
pedido: pedido,
fornecedor: menorCotacao ? this.primeiroValor(menorCotacao.A2_NOME, menorCotacao.C8_FORNOME, menorCotacao.C8_FORNECE, "-") : "-",
menorValor: menorCotacao ? this.formatarMoeda(this.numeroBR(menorCotacao.C8_TOTAL)) : "-",
itens: rows,
cotacoes: cotacoes,
pedidos: pedidos,
assinaturas: assinaturas,
cotacaoErro: base.COTACAO_ERRO || "",
pedidoErro: base.PEDIDO_ERRO || "",
atualizadoEm: this.montarDataHora(base.data_consulta, base.hora_consulta)
};
},
montarTracker: function(resumo) {
var etapas = this.montarEtapas(resumo);
return [
'<article class="tracker-card tracker-card-main">',
'<div class="tracker-title-row">',
'<div>',
'<h3>SC ' + this.escapeHTML(resumo.numero || "-") + '</h3>',
'<span>Acompanhamento completo da solicitacao</span>',
'</div>',
this.montarBadge(resumo.status),
'</div>',
'<div class="tracker-summary-grid">',
this.montarInfo("Solicitante", resumo.solicitante),
this.montarInfo("Data da solicitacao", resumo.data || "-"),
this.montarInfo("Valor estimado", resumo.valor || "-"),
this.montarInfo("Cotacao", resumo.cotacao || "-"),
this.montarInfo("Pedido", resumo.pedido || "-"),
this.montarInfo("Fornecedor sugerido", resumo.fornecedor || "-"),
'</div>',
'</article>',
'<article class="tracker-card">',
'<div class="tracker-steps">' + etapas.join("") + '</div>',
'</article>',
'<div class="tracker-grid">',
'<div>',
this.montarCotacoes(resumo),
this.montarPedidos(resumo),
this.montarItens(resumo),
'</div>',
'<div>',
this.montarAprovacoes(resumo),
this.montarResumoFinal(resumo),
'</div>',
'</div>'
].join("");
},
montarEtapas: function(resumo) {
var temCotacao = this.documentoValido(resumo.cotacao);
var temPedido = this.documentoValido(resumo.pedido);
var aprovado = resumo.assinaturas.length > 0 && this.todasAssinaturasAprovadas(resumo.assinaturas);
return [
this.montarEtapa("Solicitacao", "Criada", true, "fluigicon-shopping-cart"),
this.montarEtapa("Cotacao", temCotacao ? resumo.cotacao : "Pendente", temCotacao, "fluigicon-link"),
this.montarEtapa("Pedido", temPedido ? resumo.pedido : "Aguardando", temPedido, "fluigicon-file"),
this.montarEtapa("Aprovacao", aprovado ? "Aprovado" : "Pendente", aprovado, "fluigicon-ok-circle"),
this.montarEtapa("Entrega", "Aguardando", false, "fluigicon-truck")
];
},
montarEtapa: function(titulo, detalhe, concluida, icone) {
return [
'<div class="tracker-step ' + (concluida ? "is-done" : "is-pending") + '">',
'<div class="tracker-step-icon"><span class="fluigicon ' + icone + '"></span></div>',
'<strong>' + this.escapeHTML(titulo) + '</strong>',
'<span>' + this.escapeHTML(detalhe || "-") + '</span>',
'</div>'
].join("");
},
montarCotacoes: function(resumo) {
var cotacoes = resumo.cotacoes || [];
var html = [
'<section class="tracker-card">',
'<div class="tracker-section-head"><h4>Cotacoes</h4><span>' + cotacoes.length + '</span></div>'
];
if (!cotacoes.length) {
html.push(this.emptyInline(resumo.cotacaoErro || "Nenhuma cotacao vinculada ate o momento."));
} else {
html.push('<div class="tracker-mini-table"><table class="table"><thead><tr><th>Fornecedor</th><th>Item</th><th>Valor</th><th>Pedido</th></tr></thead><tbody>');
for (var i = 0; i < Math.min(cotacoes.length, 5); i++) {
var c = cotacoes[i] || {};
html.push(
'<tr>',
'<td>' + this.escapeHTML(this.primeiroValor(c.A2_NOME, c.C8_FORNOME, c.C8_FORNECE, "-")) + '</td>',
'<td>' + this.escapeHTML(this.primeiroValor(c.C8_ITEM, c.C8_ITEMSC, "-")) + '</td>',
'<td><strong>' + this.escapeHTML(this.formatarMoeda(this.numeroBR(c.C8_TOTAL))) + '</strong></td>',
'<td>' + this.escapeHTML(this.formatarDocumento(c.C8_NUMPED || "-")) + '</td>',
'</tr>'
);
}
html.push('</tbody></table></div>');
}
html.push('</section>');
return html.join("");
},
montarPedidos: function(resumo) {
var pedidos = resumo.pedidos || [];
var html = [
'<section class="tracker-card">',
'<div class="tracker-section-head"><h4>Pedidos</h4><span>' + (this.documentoValido(resumo.pedido) ? "1" : "0") + '</span></div>'
];
if (!this.documentoValido(resumo.pedido)) {
html.push(this.emptyInline(resumo.pedidoErro || "Pedido ainda nao gerado."));
} else {
html.push('<div class="tracker-order-line"><strong>PED ' + this.escapeHTML(resumo.pedido) + '</strong><span>' + this.escapeHTML(resumo.fornecedor) + '</span><em>' + this.escapeHTML(resumo.menorValor) + '</em></div>');
if (pedidos.length) {
html.push('<div class="tracker-muted">' + this.escapeHTML(pedidos.length + " registro(s) retornado(s) pela consulta do pedido.") + '</div>');
}
}
html.push('</section>');
return html.join("");
},
montarAprovacoes: function(resumo) {
var assinaturas = resumo.assinaturas || [];
var html = [
'<section class="tracker-card tracker-approval-card">',
'<div class="tracker-section-head"><h4>Aprovacoes</h4><span>' + assinaturas.length + '</span></div>'
];
if (!assinaturas.length) {
html.push(this.emptyInline("Sem alcadas retornadas para o pedido."));
} else {
html.push('<ol class="tracker-approval-list">');
for (var i = 0; i < assinaturas.length; i++) {
var a = assinaturas[i] || {};
var aprovado = this.statusAprovado(a.status);
html.push(
'<li class="' + (aprovado ? "is-approved" : "is-waiting") + '">',
'<span class="tracker-approval-dot">' + (aprovado ? "&#10003;" : "!") + '</span>',
'<div><strong>' + this.escapeHTML(this.primeiroValor(a.nivel, "Nivel " + (i + 1))) + ' - ' + this.escapeHTML(this.primeiroValor(a.usuario, a.nome, "-")) + '</strong>',
'<small>' + this.escapeHTML(this.primeiroValor(a.status, a.descricao, "Pendente")) + '</small></div>',
'</li>'
);
}
html.push('</ol>');
}
html.push('</section>');
return html.join("");
},
montarItens: function(resumo) {
var itens = resumo.itens || [];
var html = [
'<section class="tracker-card">',
'<div class="tracker-section-head"><h4>Itens da solicitacao</h4><span>' + itens.length + '</span></div>',
'<div class="tracker-mini-table"><table class="table"><thead><tr><th>Item</th><th>Descricao</th><th>Qtd.</th><th>Total</th></tr></thead><tbody>'
];
for (var i = 0; i < itens.length; i++) {
var item = itens[i] || {};
html.push(
'<tr>',
'<td>' + this.escapeHTML(this.primeiroValor(item.C1_ITEM, "-")) + '</td>',
'<td>' + this.escapeHTML(this.primeiroValor(item.C1_DESCRI, item.C1_PRODUTO, "-")) + '</td>',
'<td>' + this.escapeHTML(this.primeiroValor(item.C1_QUANT, "-")) + '</td>',
'<td><strong>' + this.escapeHTML(this.formatarMoeda(this.numeroBR(item.C1_TOTAL))) + '</strong></td>',
'</tr>'
);
}
html.push('</tbody></table></div></section>');
return html.join("");
},
montarResumoFinal: function(resumo) {
return [
'<section class="tracker-card">',
'<div class="tracker-section-head"><h4>Resumo</h4></div>',
'<dl class="tracker-resume">',
'<dt>Status da SC</dt><dd>' + this.montarBadge(resumo.status) + '</dd>',
'<dt>Menor valor encontrado</dt><dd>' + this.escapeHTML(resumo.menorValor || "-") + '</dd>',
'<dt>Fornecedor sugerido</dt><dd>' + this.escapeHTML(resumo.fornecedor || "-") + '</dd>',
'<dt>Pedido gerado</dt><dd>' + this.escapeHTML(resumo.pedido || "-") + '</dd>',
'<dt>Previsao de entrega</dt><dd>A definir</dd>',
'</dl>',
resumo.atualizadoEm ? '<div class="tracker-updated">Ultima atualizacao: ' + this.escapeHTML(resumo.atualizadoEm) + '</div>' : '',
'</section>'
].join("");
},
montarInfo: function(label, valor) {
return '<div class="tracker-info"><span>' + this.escapeHTML(label) + '</span><strong>' + this.escapeHTML(valor || "-") + '</strong></div>';
},
montarBadge: function(texto) {
var t = this.primeiroValor(texto, "-");
var c = "tracker-badge";
var s = String(t).toLowerCase();
if (s.indexOf("aprov") >= 0 || s.indexOf("gerad") >= 0 || s.indexOf("liberad") >= 0 || s.indexOf("sucesso") >= 0) c += " is-success";
else if (s.indexOf("aguard") >= 0 || s.indexOf("pend") >= 0 || s.indexOf("cotacao") >= 0) c += " is-warning";
else if (s.indexOf("reprov") >= 0 || s.indexOf("erro") >= 0) c += " is-danger";
else c += " is-info";
return '<span class="' + c + '">' + this.escapeHTML(t) + '</span>';
},
definirStatus: function(row, cotacao, pedido) {
if (this.documentoValido(pedido)) return "Pedido gerado";
if (this.documentoValido(cotacao)) return "Cotacao gerada";
var aprov = String(row.C1_APROV || "").toUpperCase();
if (aprov === "L") return "Liberada";
if (aprov === "R") return "Reprovada";
if (aprov === "B") return "Aguardando cotacao";
return this.primeiroValor(row.STATUS, "Aguardando acompanhamento");
},
encontrarMenorCotacao: function(cotacoes) {
var menor = null;
var menorValor = null;
for (var i = 0; i < cotacoes.length; i++) {
var atual = cotacoes[i] || {};
var valor = this.numeroBR(atual.C8_TOTAL);
if (!valor && valor !== 0) continue;
if (menor === null || valor < menorValor) {
menor = atual;
menorValor = valor;
}
}
return menor;
},
extrairAssinaturasPedido: function(pedidos) {
var assinaturas = [];
for (var i = 0; i < pedidos.length; i++) {
var p = pedidos[i] || {};
var candidatos = [p.ALCADAS, p.alcadas, p.aprovacoes, p.APROVACOES, p.assinaturas, p.ASSINATURAS];
for (var j = 0; j < candidatos.length; j++) {
var lista = candidatos[j];
if (typeof lista === "string") lista = this.parseJsonArray(lista);
if (lista && lista.length) {
for (var k = 0; k < lista.length; k++) {
var a = lista[k] || {};
assinaturas.push({
nivel: this.primeiroValor(a.NIVEL, a.nivel, a.SEQUENCIA, a.sequencia),
usuario: this.primeiroValor(a.USUARIO, a.usuario, a.NOME, a.nome, a.APROVADOR, a.aprovador),
status: this.primeiroValor(a.STATUS, a.status, a.SITUACAO, a.situacao, a.DESCRICAO, a.descricao)
});
}
}
}
}
return assinaturas;
},
todasAssinaturasAprovadas: function(assinaturas) {
for (var i = 0; i < assinaturas.length; i++) {
if (!this.statusAprovado(assinaturas[i].status)) return false;
}
return assinaturas.length > 0;
},
statusAprovado: function(status) {
var s = String(status || "").toLowerCase();
return s.indexOf("aprov") >= 0 || s === "03" || s.indexOf("liberad") >= 0;
},
temSCValida: function(row) {
return !!(row && this.primeiroValor(row.C1_NUM, row.numero, row.NUMERO));
},
documentoValido: function(valor) {
var v = String(valor || "").replace(/\s/g, "");
if (!v || v === "-") return false;
if (/^0+$/.test(v)) return false;
if (/^x+$/i.test(v)) return false;
return true;
},
parseJsonArray: function(valor) {
if (!valor) return [];
if (Object.prototype.toString.call(valor) === "[object Array]") return valor;
try {
var parsed = JSON.parse(valor);
if (Object.prototype.toString.call(parsed) === "[object Array]") return parsed;
if (parsed && parsed.pedidos) return parsed.pedidos;
if (parsed && parsed.cotacoes) return parsed.cotacoes;
if (parsed && parsed.values) return parsed.values;
} catch (e) {
return [];
}
return [];
},
somarCampo: function(rows, campo) {
var total = 0;
for (var i = 0; i < rows.length; i++) {
total += this.numeroBR((rows[i] || {})[campo]);
}
return total;
},
numeroBR: function(valor) {
if (typeof valor === "number") return valor;
var v = String(valor || "").trim();
if (!v) return 0;
v = v.replace(/[R$\s]/g, "");
if (v.indexOf(",") >= 0) v = v.replace(/\./g, "").replace(",", ".");
var n = parseFloat(v);
return isNaN(n) ? 0 : n;
},
formatarMoeda: function(valor) {
var n = typeof valor === "number" ? valor : this.numeroBR(valor);
if (!n && n !== 0) return "-";
return "R$ " + n.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
},
formatarData: function(valor) {
var v = String(valor || "").trim();
if (!v) return "-";
if (/^\d{8}$/.test(v)) return v.substr(6, 2) + "/" + v.substr(4, 2) + "/" + v.substr(0, 4);
return v;
},
formatarDocumento: function(valor) {
var v = String(valor || "").trim();
if (!this.documentoValido(v)) return "-";
return v;
},
montarDataHora: function(data, hora) {
var d = this.formatarData(data);
var h = String(hora || "").trim();
if (d === "-" && !h) return "";
return h ? d + " " + h : d;
},
primeiroValor: function() {
for (var i = 0; i < arguments.length; i++) {
var v = arguments[i];
if (v !== null && v !== undefined && String(v).trim() !== "") return String(v).trim();
}
return "";
},
normalizarTexto: function(valor) {
return String(valor || "").trim();
},
showMessage: function(texto, tipo) {
var classe = tipo === "danger" ? "alert-danger" : tipo === "success" ? "alert-success" : "alert-info";
$("#mensagemSolicitacoes_" + this.instanceId).html('<div class="alert ' + classe + '">' + this.escapeHTML(texto) + '</div>');
},
clearMessage: function() {
$("#mensagemSolicitacoes_" + this.instanceId).empty();
},
loadingState: function(texto) {
return '<div class="tracker-state"><span class="fluigicon fluigicon-refresh"></span>' + this.escapeHTML(texto) + '</div>';
},
emptyState: function(texto) {
return '<div class="tracker-state">' + this.escapeHTML(texto) + '</div>';
},
emptyInline: function(texto) {
return '<div class="tracker-empty-inline">' + this.escapeHTML(texto) + '</div>';
},
escapeHTML: function(valor) {
return String(valor === null || valor === undefined ? "" : valor)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
} }
}); });