'+i.descricao+' '+i.unidade+' '+i.quantidade+' ';}).join('');
}
function cancelWizard() { switchPage('list'); }
async function submitRequest() {
wizardFormData.tipo = document.getElementById('wizardTipo').value;
wizardFormData.urgencia = document.getElementById('wizardUrgencia').value;
wizardFormData.vencimento = document.getElementById('wizardVencimento').value;
wizardFormData.observacoes = document.getElementById('wizardObservacoes').value;
var groups=document.getElementById('wizardItemsContainer').querySelectorAll('.items-input-group');
wizardFormData.itens=[];
groups.forEach(function(g){var desc=g.querySelector('input[type="text"]');var un=g.querySelector('select');var qtd=g.querySelector('input[type="number"]');wizardFormData.itens.push({descricao:desc.value,unidade:un.value||'UN',quantidade:parseFloat(qtd.value)||1,preco_estimado:0});});
if (typeof validarAcesso === 'function' && !validarAcesso('compras_solicitacoes', EMPRESA_ID)) return;
try {
var numRes=await sb.from('compras_solicitacoes').select('numero').eq('empresa_id',EMPRESA_ID).order('numero',{ascending:false}).limit(1);
var numero='001';
if(numRes.data&&numRes.data.length>0) numero=String(parseInt(numRes.data[0].numero)+1).padStart(3,'0');
var valorTotal=wizardFormData.itens.reduce(function(s,i){return s+i.preco_estimado*i.quantidade},0);
var solRes=await sb.from('compras_solicitacoes').insert({numero:numero,empresa_id:EMPRESA_ID,data_solicitacao:new Date().toISOString(),solicitante_id:wizardFormData.solicitante_id,solicitante_nome:wizardFormData.solicitante_nome,obra_nome:wizardFormData.obra_nome,tipo:wizardFormData.tipo,urgencia:wizardFormData.urgencia,data_vencimento:wizardFormData.vencimento,observacoes:wizardFormData.observacoes,valor_total:valorTotal,status:'aguardando_analise'}).select().limit(200);
if(solRes.error) throw solRes.error;
var solId=solRes.data[0].id;
var itensData=wizardFormData.itens.map(function(item,idx){return {solicitacao_id:solId,item_numero:idx+1,descricao:item.descricao,unidade:item.unidade,quantidade:item.quantidade,preco_estimado:item.preco_estimado,observacao:''};});
var itRes=await sb.from('compras_solicitacao_itens').insert(itensData);
if(itRes.error)throw itRes.error;
showToast('Solicitação enviada com sucesso!');
await loadRequests(); switchPage('list');
} catch(err){showToast('Erro ao criar pedido: '+err.message,'error');}
}
// ===== COTACOES =====
// ===== PARCELAS =====
var quotationParcelas = [];
function gerarParcelas() {
var num = parseInt(document.getElementById('quotationNumParcelas').value) || 1;
var primVenc = document.getElementById('quotationPrimVenc').value;
var totalVal = calcQuotationTotal();
if (num < 1) num = 1;
if (num > 60) num = 60;
quotationParcelas = [];
var valorParcela = Math.floor((totalVal / num) * 100) / 100;
var resto = Math.round((totalVal - valorParcela * num) * 100) / 100;
for (var i = 0; i < num; i++) {
var dt = '';
if (primVenc) {
var d = new Date(primVenc + 'T12:00:00');
d.setMonth(d.getMonth() + i);
dt = d.toISOString().split('T')[0];
}
var val = valorParcela;
if (i === 0) val = Math.round((valorParcela + resto) * 100) / 100;
quotationParcelas.push({ numero: i + 1, data: dt, valor: val });
}
renderParcelas();
}
function addParcela() {
var totalVal = calcQuotationTotal();
var somaExistente = quotationParcelas.reduce(function(s, p) { return s + p.valor; }, 0);
var restante = Math.round((totalVal - somaExistente) * 100) / 100;
if (restante < 0) restante = 0;
quotationParcelas.push({ numero: quotationParcelas.length + 1, data: '', valor: restante });
renderParcelas();
}
function removeParcela(idx) {
quotationParcelas.splice(idx, 1);
for (var i = 0; i < quotationParcelas.length; i++) quotationParcelas[i].numero = i + 1;
renderParcelas();
}
function updateParcelaData(idx, val) { quotationParcelas[idx].data = val; }
function updateParcelaValor(idx, val) {
quotationParcelas[idx].valor = parseFloat(val) || 0;
renderParcelasTotal();
}
function calcQuotationTotal() {
var vItens = parseFloat(document.getElementById('quotationValueItems').value) || 0;
var frete = parseFloat(document.getElementById('quotationFrete').value) || 0;
var desconto = parseFloat(document.getElementById('quotationDesconto').value) || 0;
var seguro = parseFloat(document.getElementById('quotationSeguro').value) || 0;
return vItens + frete - desconto + seguro;
}
function renderParcelas() {
var container = document.getElementById('parcelasContainer');
if (quotationParcelas.length === 0) {
container.innerHTML = 'À vista (sem parcelas)
';
document.getElementById('parcelasTotalDisplay').style.display = 'none';
return;
}
container.innerHTML = quotationParcelas.map(function(p, i) {
return ''
+ '' + p.numero + '. '
+ ' '
+ ' '
+ 'close '
+ '
';
}).join('');
renderParcelasTotal();
}
function renderParcelasTotal() {
var totalParcelas = quotationParcelas.reduce(function(s, p) { return s + p.valor; }, 0);
var totalCotacao = calcQuotationTotal();
var diff = Math.round((totalCotacao - totalParcelas) * 100) / 100;
var el = document.getElementById('parcelasTotalDisplay');
el.style.display = 'block';
var diffColor = Math.abs(diff) < 0.01 ? 'var(--bv-green)' : '#EF4444';
var diffText = Math.abs(diff) < 0.01 ? 'OK' : (diff > 0 ? 'Falta R$ ' + diff.toFixed(2) : 'Excede R$ ' + Math.abs(diff).toFixed(2));
el.innerHTML = 'Total parcelas: ' + formatCurrency(totalParcelas) + ' / Cotação: ' + formatCurrency(totalCotacao) + ' — ' + diffText + ' ';
}
function openQuotationModal() {
if(!currentRequest)return;
currentQuotationSolicitacaoId=currentRequest.id;
var fid = document.getElementById('quotationFornecedorId');
if (fid) fid.value = '';
var ac = document.getElementById('supplierAutocomplete');
if (ac) { ac.innerHTML = ''; ac.style.display = 'none'; }
document.getElementById('quotationSupplier').value = '';
document.getElementById('quotationCNPJ').value = '';
document.getElementById('quotationSeller').value = '';
document.getElementById('quotationContact').value = '';
document.getElementById('quotationValueItems').value = '';
document.getElementById('quotationFrete').value = '0';
document.getElementById('quotationDesconto').value = '0';
document.getElementById('quotationSeguro').value = '0';
document.getElementById('quotationPaymentCondition').value = '';
document.getElementById('quotationDeliveryTime').value = '';
document.getElementById('quotationBankingData').value = '';
document.getElementById('quotationObservations').value = '';
document.getElementById('quotationNumParcelas').value = '1';
document.getElementById('quotationPrimVenc').value = '';
quotationParcelas = [];
renderParcelas();
document.getElementById('quotationModal').classList.add('active');
}
function closeQuotationModal() { document.getElementById('quotationModal').classList.remove('active'); }
async function submitQuotation(e) {
e.preventDefault();
var fornecedor=document.getElementById('quotationSupplier').value;
var cnpj=document.getElementById('quotationCNPJ').value;
var vendedor=document.getElementById('quotationSeller').value;
var contato=document.getElementById('quotationContact').value;
var vItens=parseFloat(document.getElementById('quotationValueItems').value);
var frete=parseFloat(document.getElementById('quotationFrete').value)||0;
var desconto=parseFloat(document.getElementById('quotationDesconto').value)||0;
var seguro=parseFloat(document.getElementById('quotationSeguro').value)||0;
var condicao=document.getElementById('quotationPaymentCondition').value;
var prazo=document.getElementById('quotationDeliveryTime').value;
var bancarios=document.getElementById('quotationBankingData').value;
var obs=document.getElementById('quotationObservations').value;
var total=vItens+frete-desconto+seguro;
var fornecedorId=document.getElementById('quotationFornecedorId').value||null;
try {
// Auto-criar fornecedor se não selecionou do autocomplete
if (!fornecedorId && fornecedor) {
var newForn = await sb.from('fornecedores').insert({
empresa_id: EMPRESA_ID,
razao_social: fornecedor,
nome_fantasia: fornecedor,
cnpj: cnpj || null,
contato_nome: vendedor || null,
telefone: contato || null,
ativo: true
}).select().single();
if (!newForn.error && newForn.data) {
fornecedorId = newForn.data.id;
fornecedoresCache = [];
}
}
var cotRes=await sb.from('compras_cotacoes').insert({solicitacao_id:currentQuotationSolicitacaoId,fornecedor_id:fornecedorId,fornecedor_nome:fornecedor,fornecedor_cnpj:cnpj,vendedor_nome:vendedor,vendedor_contato:contato,data_cotacao:new Date().toISOString(),numero_orcamento:'',valor_itens:vItens,valor_frete:frete,valor_desconto:desconto,valor_seguro:seguro,valor_total:total,condicao_pagamento:condicao,prazo_entrega:prazo,dados_bancarios:bancarios,observacoes:obs,selecionada:false}).select().single();
if(cotRes.error)throw cotRes.error;
// Salvar parcelas
if (quotationParcelas.length > 0 && cotRes.data) {
var parcelasInsert = quotationParcelas.filter(function(p){return p.data}).map(function(p){
return { cotacao_id: cotRes.data.id, parcela_numero: p.numero, data_vencimento: p.data, valor: p.valor };
});
if (parcelasInsert.length > 0) {
var pRes = await sb.from('compras_cotacao_parcelas').insert(parcelasInsert);
if (pRes.error) console.warn('Erro ao salvar parcelas:', pRes.error.message);
}
}
var stRes=await sb.from('compras_solicitacoes').select('status').eq('id',currentQuotationSolicitacaoId).single();
if(stRes.data.status==='aprovado') await sb.from('compras_solicitacoes').update({status:'em_cotacao'}).eq('id',currentQuotationSolicitacaoId);
showToast('Cotação adicionada com sucesso!');
closeQuotationModal();
await viewRequest(currentQuotationSolicitacaoId);
} catch(err){showToast('Erro ao adicionar cotação: '+err.message,'error');}
}
// ===== FORNECEDORES SUB-MODULE =====
var allFornecedores = [];
var currentFornecedor = null;
var fornecedoresCache = [];
var supplierSearchTimer = null;
function goToFornecedores() {
if (['diretor','admin','financeiro','compras'].indexOf(currentUserRole) === -1) { showToast('Acesso restrito a Diretor, Financeiro e Compras', 'error'); return; }
switchPage('fornecedores');
loadFornecedores();
}
function backToFornecedoresList() {
document.getElementById('fornecedorDetailView').style.display = 'none';
document.getElementById('fornecedoresListView').style.display = '';
}
async function loadFornecedores() {
try {
var res = await sb.from('fornecedores').select('*').eq('empresa_id', EMPRESA_ID).order('razao_social').limit(200);
if (res.error) throw res.error;
allFornecedores = res.data || [];
fornecedoresCache = allFornecedores.filter(function(f){return f.ativo});
// Count cotacoes per fornecedor (filtered by empresa solicitacoes)
var solIds = allRequests.map(function(r){ return r.id; });
var cotRes = solIds.length > 0
? await sb.from('compras_cotacoes').select('fornecedor_id,fornecedor_nome').in('solicitacao_id', solIds)
: { data: [] };
var cotMap = {};
if (cotRes.data) cotRes.data.forEach(function(c) {
var key = c.fornecedor_id || c.fornecedor_nome;
cotMap[key] = (cotMap[key] || 0) + 1;
});
allFornecedores.forEach(function(f) {
f._cotCount = (cotMap[f.id] || 0) + (cotMap[f.razao_social] || 0);
});
renderFornecedoresStats();
filterFornecedoresUI();
} catch(err) { showToast('Erro ao carregar fornecedores: '+err.message,'error'); }
}
function renderFornecedoresStats() {
var total = allFornecedores.length;
var ativos = allFornecedores.filter(function(f){return f.ativo}).length;
var comCot = allFornecedores.filter(function(f){return f._cotCount > 0}).length;
var avgRating = 0;
var rated = allFornecedores.filter(function(f){return f.avaliacao > 0});
if (rated.length > 0) avgRating = (rated.reduce(function(s,f){return s + parseFloat(f.avaliacao)},0) / rated.length).toFixed(1);
document.getElementById('fornStats').innerHTML =
''
+ ''
+ ''
+ 'Média Avaliação
'+avgRating+' ★
';
}
function filterFornecedoresUI() {
var search = (document.getElementById('fornSearch').value || '').toLowerCase();
var status = document.getElementById('fornStatusFilter').value;
var filtered = allFornecedores.filter(function(f) {
var matchSearch = !search || (f.razao_social||'').toLowerCase().includes(search) || (f.nome_fantasia||'').toLowerCase().includes(search) || (f.cnpj||'').includes(search);
var matchStatus = !status || (status === 'ativo' && f.ativo) || (status === 'inativo' && !f.ativo);
return matchSearch && matchStatus;
});
var tbody = document.getElementById('fornTableBody');
if (filtered.length === 0) {
tbody.innerHTML = 'Nenhum fornecedor encontrado ';
return;
}
tbody.innerHTML = filtered.map(function(f) {
var cats = f.categorias ? f.categorias.split(',').map(function(c){return ''+c.trim()+' '}).join('') : '—';
var statusDot = ' '+(f.ativo?'Ativo':'Inativo');
var cotBadge = f._cotCount > 0 ? ''+f._cotCount+' ' : '0 ';
var objetoHtml = f.objeto ? ''+f.objeto+'
' : '';
objetoHtml += f.especialidade ? ''+f.especialidade+'
' : '';
if (!objetoHtml) objetoHtml = '— ';
return ''
+ ''+(f.nome_fantasia || f.razao_social)+'
'+(f.nome_fantasia && f.nome_fantasia !== f.razao_social ? ''+f.razao_social+'
' : '')+' '
+ ''+(f.cnpj||'—')+' '
+ ''+objetoHtml+' '
+ ''+(f.contato_nome||'—')+' '
+ ''+(f.telefone||'—')+' '
+ ''+cotBadge+' '
+ ''+statusDot+' ';
}).join('');
}
async function viewFornecedor(id) {
try {
var f = allFornecedores.find(function(x){return x.id===id});
if (!f) return;
currentFornecedor = f;
document.getElementById('fornecedoresListView').style.display = 'none';
document.getElementById('fornecedorDetailView').style.display = '';
document.getElementById('fornDetailTitle').textContent = f.nome_fantasia || f.razao_social;
var stars = '';
for (var i=1;i<=5;i++) stars += i <= (f.avaliacao||0) ? '★' : '☆';
document.getElementById('fornDetailGrid').innerHTML =
'Razão Social
'+(f.razao_social||'—')+'
'
+ 'Nome Fantasia
'+(f.nome_fantasia||'—')+'
'
+ ''
+ 'Objeto
'+(f.objeto||'—')+'
'
+ 'Especialidade
'+(f.especialidade||'—')+'
'
+ 'Tipo
'+(f.tipo_pessoa||'—')+'
'
+ 'Contato
'+(f.contato_nome||'—')+'
'
+ 'Telefone
'+(f.telefone||'—')+'
'
+ ''
+ 'Endereço
'+(f.endereco ? f.endereco+', '+(f.cidade||'')+'/'+(f.estado||'') : '—')+'
'
+ 'Categorias
'+(f.categorias ? f.categorias.split(',').map(function(c){return ''+c.trim()+' '}).join(' ') : '—')+'
'
+ 'Avaliação
'+stars+' '+(f.avaliacao||0)+'
'
+ 'Status
'+(f.ativo?'Ativo':'Inativo')+'
'
+ 'Observações
'+(f.observacao||'—')+'
';
// Load cotacoes history
var cotRes = await sb.from('compras_cotacoes').select('*,compras_solicitacoes(numero,obra_nome,status)').or('fornecedor_id.eq.'+id+',fornecedor_nome.eq.'+encodeURIComponent(f.razao_social)).order('created_at',{ascending:false}).limit(200);
var histEl = document.getElementById('fornCotacoesHist');
if (!cotRes.data || cotRes.data.length === 0) {
histEl.innerHTML = 'Nenhuma cotação registrada para este fornecedor
';
} else {
histEl.innerHTML = cotRes.data.map(function(c) {
var sol = c.compras_solicitacoes || {};
return ''
+ '
'
+ '
Pedido #'+(sol.numero||'—')+' '
+ ''+(sol.obra_nome||'')+'
'
+ '
'+formatCurrency(c.valor_total)+' '
+ '
'
+ ''+formatDate(c.data_cotacao)+' '
+ 'Cond: '+(c.condicao_pagamento||'—')+' '
+ 'Prazo: '+(c.prazo_entrega||'—')+' '
+ (c.selecionada ? '✓ Selecionada ' : '')
+ '
';
}).join('');
}
} catch(err) { showToast('Erro: '+err.message,'error'); }
}
// Modal CRUD
function openFornecedorModal(id) {
var modal = document.getElementById('fornecedorModal');
document.getElementById('fornEditId').value = '';
document.getElementById('fornRazao').value = '';
document.getElementById('fornFantasia').value = '';
document.getElementById('fornCNPJ').value = '';
document.getElementById('fornTipo').value = 'PJ';
document.getElementById('fornContato').value = '';
document.getElementById('fornTelefone').value = '';
document.getElementById('fornEmail').value = '';
document.getElementById('fornEndereco').value = '';
document.getElementById('fornCidade').value = '';
document.getElementById('fornUF').value = '';
document.getElementById('fornCEP').value = '';
document.getElementById('fornObjeto').value = '';
document.getElementById('fornEspecialidade').value = '';
document.getElementById('fornCategorias').value = '';
document.getElementById('fornAvaliacao').value = '0';
document.getElementById('fornAtivo').value = 'true';
document.getElementById('fornObs').value = '';
if (id) {
var f = allFornecedores.find(function(x){return x.id===id});
if (f) {
document.getElementById('fornModalTitle').textContent = 'Editar Fornecedor';
document.getElementById('fornEditId').value = f.id;
document.getElementById('fornRazao').value = f.razao_social || '';
document.getElementById('fornFantasia').value = f.nome_fantasia || '';
document.getElementById('fornCNPJ').value = f.cnpj || '';
document.getElementById('fornTipo').value = f.tipo_pessoa || 'PJ';
document.getElementById('fornContato').value = f.contato_nome || '';
document.getElementById('fornTelefone').value = f.telefone || '';
document.getElementById('fornEmail').value = f.email || '';
document.getElementById('fornEndereco').value = f.endereco || '';
document.getElementById('fornCidade').value = f.cidade || '';
document.getElementById('fornUF').value = f.estado || '';
document.getElementById('fornCEP').value = f.cep || '';
document.getElementById('fornObjeto').value = f.objeto || '';
document.getElementById('fornEspecialidade').value = f.especialidade || '';
document.getElementById('fornCategorias').value = f.categorias || '';
document.getElementById('fornAvaliacao').value = f.avaliacao || '0';
document.getElementById('fornAtivo').value = f.ativo ? 'true' : 'false';
document.getElementById('fornObs').value = f.observacao || '';
}
} else {
document.getElementById('fornModalTitle').textContent = 'Novo Fornecedor';
}
modal.classList.add('active');
}
function closeFornecedorModal() { document.getElementById('fornecedorModal').classList.remove('active'); }
async function saveFornecedor(e) {
e.preventDefault();
var id = document.getElementById('fornEditId').value;
var data = {
razao_social: document.getElementById('fornRazao').value,
nome_fantasia: document.getElementById('fornFantasia').value || null,
cnpj: document.getElementById('fornCNPJ').value || null,
tipo_pessoa: document.getElementById('fornTipo').value,
contato_nome: document.getElementById('fornContato').value || null,
telefone: document.getElementById('fornTelefone').value || null,
email: document.getElementById('fornEmail').value || null,
endereco: document.getElementById('fornEndereco').value || null,
cidade: document.getElementById('fornCidade').value || null,
estado: document.getElementById('fornUF').value || null,
cep: document.getElementById('fornCEP').value || null,
objeto: document.getElementById('fornObjeto').value || null,
especialidade: document.getElementById('fornEspecialidade').value || null,
categorias: document.getElementById('fornCategorias').value || null,
avaliacao: parseFloat(document.getElementById('fornAvaliacao').value) || null,
ativo: document.getElementById('fornAtivo').value === 'true',
observacao: document.getElementById('fornObs').value || null,
data_atualizacao: new Date().toISOString(),
empresa_id: EMPRESA_ID
};
try {
if (id) {
var r = await sb.from('fornecedores').update(data).eq('id', id);
if (r.error) throw r.error;
showToast('Fornecedor atualizado!');
} else {
var r = await sb.from('fornecedores').insert(data).select().limit(200);
if (r.error) throw r.error;
showToast('Fornecedor cadastrado!');
}
closeFornecedorModal();
fornecedoresCache = [];
await loadFornecedores();
if (id && currentFornecedor && currentFornecedor.id === id) await viewFornecedor(id);
} catch(err) { showToast('Erro ao salvar: '+err.message,'error'); }
}
// Autocomplete no modal de cotação
function searchSupplierAutocomplete(query) {
clearTimeout(supplierSearchTimer);
var dropdown = document.getElementById('supplierAutocomplete');
if (!query || query.length < 2) { dropdown.style.display = 'none'; return; }
supplierSearchTimer = setTimeout(async function() {
if (fornecedoresCache.length === 0) {
var res = await sb.from('fornecedores').select('id,razao_social,nome_fantasia,cnpj,contato_nome,telefone,contato_telefone').eq('empresa_id',EMPRESA_ID).eq('ativo',true).order('razao_social').limit(200);
fornecedoresCache = res.data || [];
}
var q = query.toLowerCase();
var matches = fornecedoresCache.filter(function(f) {
return (f.razao_social||'').toLowerCase().includes(q) || (f.nome_fantasia||'').toLowerCase().includes(q) || (f.cnpj||'').includes(q);
}).slice(0, 8);
if (matches.length === 0) {
dropdown.innerHTML = 'Fornecedor não encontrado — será criado automaticamente
';
} else {
dropdown.innerHTML = matches.map(function(f) {
return ''
+ '
'+(f.nome_fantasia || f.razao_social)+'
'
+ '
'+(f.cnpj||'Sem CNPJ')+' · '+(f.contato_nome||'Sem contato')+'
';
}).join('');
}
dropdown.style.display = 'block';
}, 250);
}
function selectSupplierAutocomplete(id) {
var f = fornecedoresCache.find(function(x){return x.id===id});
if (!f) return;
document.getElementById('quotationSupplier').value = f.nome_fantasia || f.razao_social;
document.getElementById('quotationFornecedorId').value = f.id;
document.getElementById('quotationCNPJ').value = f.cnpj || '';
document.getElementById('quotationSeller').value = f.contato_nome || '';
document.getElementById('quotationContact').value = f.contato_telefone || f.telefone || '';
document.getElementById('supplierAutocomplete').style.display = 'none';
}
function clearSupplierAutocomplete() {
document.getElementById('quotationFornecedorId').value = '';
document.getElementById('supplierAutocomplete').style.display = 'none';
}
document.addEventListener('click', function(e) {
var ac = document.getElementById('supplierAutocomplete');
if (ac && !e.target.closest('#quotationSupplier') && !e.target.closest('#supplierAutocomplete')) ac.style.display = 'none';
});
// ═══ HEARTBEAT — registrar presença ═══
function startHeartbeat() {
async function beat() {
sb.rpc('upsert_user_session', {
p_user_id: currentUser.id,
p_nome: currentUser.nome_completo || currentUser.email || 'Usuario',
p_email: currentUser.email || '',
p_empresa_id: empresaData.id || null,
p_empresa_nome: empresaData.nome_fantasia || empresaData.nome || '',
p_modulo: 'index'
}).then(function(){}).catch(function(e){ console.error(e); });
}
beat();
setInterval(beat, 30000);
}
// ===== INIT =====
window.addEventListener('load', async function() {
startHeartbeat();
document.title = 'Compras — ' + (empresaData.nome_fantasia || 'Nexoin ERP');
if (!sb) return;
try {
var result = await sb.auth.getUser();
if (result.data && result.data.user) {
currentUser = result.data.user;
await loadUserProfile();
await loadRequests();
var params = new URLSearchParams(window.location.search);
var pedidoId = params.get('pedido');
if (pedidoId) {
await viewRequest(pedidoId);
} else {
switchPage('list');
}
} else {
// Sessão Supabase expirada — tentar refresh
var ref = await sb.auth.refreshSession();
if (ref.data && ref.data.session) {
currentUser = ref.data.session.user;
await loadUserProfile();
await loadRequests();
switchPage('list');
} else {
// Sessão não pode ser renovada — limpar e redirecionar
sessionStorage.clear();
window.location.replace('nexoin-portal.html');
}
}
} catch (err) {
console.error('Erro no auto-login:', err);
sessionStorage.clear();
window.location.replace('nexoin-portal.html');
}
});
// ===== ENVIAR AO FINANCEIRO =====
async function sendToFinanceiro() {
if (!currentRequest) return;
// Verificar se tem pelo menos 1 doc fiscal
var fiscais = currentAnexos.filter(function(a) { return a.categoria === 'fiscal'; });
if (fiscais.length === 0) {
showToast('Anexe pelo menos a NF ou boleto antes de enviar ao financeiro', 'error');
return;
}
if (!(await nxConfirm('Enviar ao Financeiro para pagamento?'))) return;
try {
var obsFiscal = (document.getElementById('obsFiscalInput').value || '').trim();
// 1. Atualiza status da solicitação
var r = await sb.from('compras_solicitacoes').update({status:'em_pagamento', obs_fiscal: obsFiscal || null}).eq('id', currentRequest.id);
if (r.error) throw r.error;
// 2. Registra etapa na timeline
await sb.from('compras_aprovacoes').insert({
solicitacao_id: currentRequest.id,
etapa: 'liberacao_financeiro',
aprovador_id: currentUser.id,
aprovador_nome: currentUser.email,
decisao: 'encaminhado',
data_decisao: new Date().toISOString()
});
// 3. Cria lançamento financeiro pendente em movimentacoes_financeiras
if (typeof validarAcesso === 'function' && !validarAcesso('movimentacoes_financeiras', EMPRESA_ID)) return;
var descLancamento = 'Compra #' + (currentRequest.numero || '—')
+ ' — ' + (currentRequest.obra_nome || 'Sem centro de custo')
+ (currentRequest.tipo ? ' [' + currentRequest.tipo + ']' : '');
var movRes = await sb.from('movimentacoes_financeiras').insert({
empresa_id: EMPRESA_ID,
tipo: 'saida',
categoria: 'compras',
descricao: descLancamento,
valor: parseFloat(currentRequest.valor_total) || 0,
obra_nome: currentRequest.obra_nome || null,
centro_custo: currentRequest.obra_nome || null,
solicitacao_id: currentRequest.id,
numero_documento: String(currentRequest.numero || ''),
observacoes: obsFiscal || null,
status: 'pendente',
created_by: currentUser.id
});
if (movRes.error) {
// Não bloqueia o fluxo principal — avisa no console
console.warn('Aviso: lançamento financeiro não criado —', movRes.error.message);
}
showToast('Enviado ao Financeiro!');
await loadRequests();
backToList();
} catch (err) { showToast('Erro: ' + err.message, 'error'); }
}
// ===== ANEXOS =====
async function loadAnexos(solicitacaoId) {
try {
var result = await sb.from('compras_anexos').select('*').eq('solicitacao_id', solicitacaoId).order('created_at', { ascending: false }).limit(200);
if (result.error) throw result.error;
currentAnexos = result.data || [];
return currentAnexos;
} catch (err) { console.error('Erro ao carregar anexos:', err); currentAnexos = []; return []; }
}
function getFileIcon(tipo) {
if (!tipo) return 'insert_drive_file';
if (tipo.includes('pdf')) return 'picture_as_pdf';
if (tipo.includes('image')) return 'image';
if (tipo.includes('word') || tipo.includes('document')) return 'description';
if (tipo.includes('sheet') || tipo.includes('excel')) return 'table_chart';
if (tipo.includes('zip') || tipo.includes('rar')) return 'folder_zip';
return 'insert_drive_file';
}
function formatFileSize(bytes) {
if (!bytes) return '';
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / 1048576).toFixed(1) + ' MB';
}
function renderAnexos(anexos, canManage) {
var section = document.getElementById('anexosSection');
var list = document.getElementById('anexosList');
var btnContainer = document.getElementById('anexosBtnContainer');
var titleEl = document.getElementById('anexosTitleText');
section.style.display = 'block';
// Título dinâmico baseado no status
if (titleEl && currentRequest) {
if (['aguardando_analise','aguardando_aprovacao'].includes(currentRequest.status)) {
titleEl.textContent = 'Anexos / Documentação';
} else {
titleEl.textContent = 'Anexos para Fornecedores';
}
}
// Botao de anexar (so para compras/diretor em status de cotacao)
if (canManage) {
btnContainer.innerHTML = 'upload_file Anexar Arquivo ';
} else {
btnContainer.innerHTML = '';
}
if (!anexos || anexos.length === 0) {
list.innerHTML = 'attachment Nenhum anexo adicionado
';
return;
}
list.innerHTML = anexos.map(function(a) {
var icon = getFileIcon(a.tipo);
var size = formatFileSize(a.tamanho);
var date = new Date(a.created_at).toLocaleDateString('pt-BR');
var safePath = (a.storage_path || '').replace(/'/g, "\\'");
var safeName = (a.nome_arquivo || '').replace(/'/g, "\\'");
var deleteBtn = canManage ? 'delete ' : '';
return ''
+ '
' + icon + ' '
+ '
' + a.nome_arquivo + '
'
+ '
' + size + (size ? ' • ' : '') + date + '
'
+ '
download '
+ deleteBtn
+ '
';
}).join('');
}
async function renderDocsFiscais(anexos, canManage) {
var section = document.getElementById('docsFiscaisSection');
var list = document.getElementById('docsFiscaisList');
var btnContainer = document.getElementById('docsFiscaisBtnContainer');
section.style.display = 'block';
if (canManage) {
btnContainer.innerHTML = 'upload_file Anexar NF/Boleto ';
} else {
btnContainer.innerHTML = '';
}
if (!anexos || anexos.length === 0) {
list.innerHTML = 'receipt_long ' + (canManage ? 'Anexe a NF, boleto e outros documentos fiscais' : 'Nenhum documento fiscal anexado') + '
';
} else {
list.innerHTML = anexos.map(function(a) {
var icon = getFileIcon(a.tipo);
var size = formatFileSize(a.tamanho);
var date = new Date(a.created_at).toLocaleDateString('pt-BR');
var safePath = (a.storage_path || '').replace(/'/g, "\\'");
var safeName = (a.nome_arquivo || '').replace(/'/g, "\\'");
var deleteBtn = canManage ? 'delete ' : '';
return ''
+ '
' + icon + ' '
+ '
' + a.nome_arquivo + '
'
+ '
' + size + (size ? ' • ' : '') + date + '
'
+ '
download '
+ deleteBtn
+ '
';
}).join('');
}
// Observação fiscal
var obsContainer = document.getElementById('obsFiscalContainer');
var obsInput = document.getElementById('obsFiscalInput');
var obsSaved = document.getElementById('obsFiscalSaved');
obsContainer.style.display = 'block';
if (canManage) {
obsInput.style.display = 'block';
obsInput.value = currentRequest.obs_fiscal || '';
obsSaved.style.display = 'none';
} else {
obsInput.style.display = 'none';
if (currentRequest.obs_fiscal) {
obsSaved.style.display = 'block';
obsSaved.textContent = currentRequest.obs_fiscal;
} else {
obsSaved.style.display = 'block';
obsSaved.style.background = '#222222';
obsSaved.style.borderColor = '#2d333b';
obsSaved.style.color = '#A0A0A0';
obsSaved.textContent = 'Nenhuma observação adicionada';
}
}
}
async function handleDocFiscalUpload(input) {
if (!input.files || !input.files.length || !currentRequest) return;
var files = Array.from(input.files);
var solId = currentRequest.id;
var uploaded = 0;
var errors = 0;
showToast('Enviando ' + files.length + ' documento(s) fiscal(is)...');
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!checkFileSize(file)) { errors++; continue; }
var ext = file.name.split('.').pop();
var path = solId + '/fiscal/' + Date.now() + '_' + Math.random().toString(36).substr(2, 6) + '.' + ext;
try {
var upload = await sb.storage.from('compras-anexos').upload(path, file, { cacheControl: '3600', upsert: false });
if (upload.error) throw upload.error;
var insert = await sb.from('compras_anexos').insert({
solicitacao_id: solId,
nome_arquivo: file.name,
tamanho: file.size,
tipo: file.type,
storage_path: path,
uploaded_by: currentUser.id,
categoria: 'fiscal'
});
if (insert.error) throw insert.error;
uploaded++;
} catch (err) {
console.error('Erro ao enviar ' + file.name + ':', err);
errors++;
}
}
input.value = '';
if (uploaded > 0) showToast(uploaded + ' documento(s) fiscal(is) enviado(s)!');
if (errors > 0) showToast(errors + ' documento(s) falharam', 'error');
var anexos = await loadAnexos(solId);
var fiscais = anexos.filter(function(a) { return a.categoria === 'fiscal'; });
var canManage = (currentUserRole === 'compras' || currentUserRole === 'diretor' || currentUserRole === 'admin');
renderDocsFiscais(fiscais, canManage);
}
async function handleAnexoUpload(input) {
if (!input.files || !input.files.length || !currentRequest) return;
var files = Array.from(input.files);
var solId = currentRequest.id;
var uploaded = 0;
var errors = 0;
var cat = ['aguardando_analise','aguardando_aprovacao'].includes(currentRequest.status) ? 'documentacao' : 'cotacao';
showToast('Enviando ' + files.length + ' arquivo(s)...');
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!checkFileSize(file)) { errors++; continue; }
var ext = file.name.split('.').pop();
var path = solId + '/' + cat + '/' + Date.now() + '_' + Math.random().toString(36).substr(2, 6) + '.' + ext;
try {
var upload = await sb.storage.from('compras-anexos').upload(path, file, { cacheControl: '3600', upsert: false });
if (upload.error) throw upload.error;
var insert = await sb.from('compras_anexos').insert({
solicitacao_id: solId,
nome_arquivo: file.name,
tamanho: file.size,
tipo: file.type,
storage_path: path,
uploaded_by: currentUser.id,
categoria: cat
});
if (insert.error) throw insert.error;
uploaded++;
} catch (err) {
console.error('Erro ao enviar ' + file.name + ':', err);
errors++;
}
}
input.value = '';
if (uploaded > 0) showToast(uploaded + ' arquivo(s) enviado(s) com sucesso!');
if (errors > 0) showToast(errors + ' arquivo(s) falharam no envio', 'error');
// Recarregar lista — filtrar pela categoria correta
var anexos = await loadAnexos(solId);
var canManage = (currentUserRole === 'compras' || currentUserRole === 'diretor' || currentUserRole === 'admin');
var anexosToShow = ['aguardando_analise','aguardando_aprovacao'].includes(currentRequest.status)
? anexos.filter(function(a){return a.categoria==='documentacao' || a.categoria==='cotacao'})
: anexos.filter(function(a){return a.categoria==='cotacao'});
renderAnexos(anexosToShow, canManage);
}
async function deleteAnexo(id, path) {
if (!(await nxConfirm('Excluir este anexo?'))) return;
try {
var del = await sb.storage.from('compras-anexos').remove([path]);
if (del.error) throw new Error('Erro ao remover do storage: ' + del.error.message);
var result = await sb.from('compras_anexos').delete().eq('id', id);
if (result.error) throw result.error;
showToast('Anexo excluído');
var anexos = await loadAnexos(currentRequest.id);
var cotacao = anexos.filter(function(a){return a.categoria==='cotacao'});
var fiscal = anexos.filter(function(a){return a.categoria==='fiscal'});
var canManageCot = (currentUserRole === 'compras' || currentUserRole === 'diretor' || currentUserRole === 'admin');
var canManageFiscal = (currentUserRole === 'compras' || currentUserRole === 'diretor' || currentUserRole === 'admin');
renderAnexos(cotacao, canManageCot);
if (['aguardando_nf','em_pagamento','concluido'].includes(currentRequest.status)) {
renderDocsFiscais(fiscal, canManageFiscal);
var recibos = anexos.filter(function(a){return a.categoria==='recibo'});
var canManageRecibo = (currentUserRole === 'compras' || currentUserRole === 'diretor' || currentUserRole === 'admin');
renderRecibo(recibos, canManageRecibo, false, currentRequest);
}
} catch (err) { showToast('Erro ao excluir: ' + err.message, 'error'); }
}
async function downloadAnexo(path, nome) {
try {
var result = await sb.storage.from('compras-anexos').createSignedUrl(path, 300);
if (result.error) throw result.error;
var a = document.createElement('a');
a.href = result.data.signedUrl;
a.download = nome;
a.target = '_blank';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} catch (err) { showToast('Erro ao baixar arquivo: ' + err.message, 'error'); }
}
// ===== RECIBO =====
function clickReciboUpload() {
var input = document.getElementById('reciboFileInput');
if (input) { input.value = ''; input.click(); }
}
function renderRecibo(anexos, canManage, canUploadPagamento, sol) {
console.log('[renderRecibo] canManage:', canManage, 'canUploadPagamento:', canUploadPagamento, 'status:', sol.status, 'pendente:', sol.recibo_pendente_assinatura);
var section = document.getElementById('reciboSection');
var list = document.getElementById('reciboList');
var btnContainer = document.getElementById('reciboBtnContainer');
var pendenteContainer = document.getElementById('reciboPendenteContainer');
var pendenteReadonly = document.getElementById('reciboPendenteReadonly');
section.style.display = 'block';
// Determinar se pode fazer upload e excluir
var canUpload = canManage || canUploadPagamento;
var canDelete = canUpload;
// Botão de anexar recibo
if (canUpload) {
var btnLabel = canUploadPagamento ? 'Anexar Recibo Assinado' : 'Anexar Recibo';
btnContainer.innerHTML = 'upload_file ' + btnLabel + ' ';
} else {
btnContainer.innerHTML = '';
}
// Lista de recibos
if (!anexos || anexos.length === 0) {
var emptyMsg = canUpload ? 'Clique em "' + (canUploadPagamento ? 'Anexar Recibo Assinado' : 'Anexar Recibo') + '" acima para adicionar' : 'Nenhum recibo anexado';
list.innerHTML = 'receipt ' + emptyMsg + '
';
} else {
list.innerHTML = anexos.map(function(a) {
var icon = getFileIcon(a.tipo);
var size = formatFileSize(a.tamanho);
var date = new Date(a.created_at).toLocaleDateString('pt-BR');
var safePath = (a.storage_path || '').replace(/'/g, "\\'");
var safeName = (a.nome_arquivo || '').replace(/'/g, "\\'");
var deleteBtn = canDelete ? 'delete ' : '';
return ''
+ '
' + icon + ' '
+ '
' + a.nome_arquivo + '
'
+ '
' + size + (size ? ' • ' : '') + date + '
'
+ '
download '
+ deleteBtn
+ '
';
}).join('');
}
// Checkbox pendente de assinatura
if (canManage) {
pendenteContainer.style.display = 'block';
pendenteReadonly.style.display = 'none';
var radios = document.getElementsByName('reciboPendente');
if (sol.recibo_pendente_assinatura === true) { radios[0].checked = true; radios[1].checked = false; }
else if (sol.recibo_pendente_assinatura === false) { radios[0].checked = false; radios[1].checked = true; }
else { radios[0].checked = false; radios[1].checked = false; }
} else {
pendenteContainer.style.display = 'none';
if (sol.recibo_pendente_assinatura === true) {
var jaAnexou = anexos && anexos.length > 0 && sol.status !== 'aguardando_nf';
pendenteReadonly.style.display = 'block';
if (canUploadPagamento && !jaAnexou) {
pendenteReadonly.innerHTML = 'warning Recibo pendente de assinatura — Anexe o recibo assinado acima
';
} else if (jaAnexou) {
pendenteReadonly.innerHTML = 'check_circle Recibo assinado anexado
';
} else {
pendenteReadonly.innerHTML = 'pending_actions Recibo pendente de assinatura
';
}
} else {
pendenteReadonly.style.display = 'none';
}
}
}
async function toggleReciboPendente(pendente) {
if (!currentRequest) return;
try {
var r = await sb.from('compras_solicitacoes').update({recibo_pendente_assinatura: pendente}).eq('id', currentRequest.id);
if (r.error) throw r.error;
currentRequest.recibo_pendente_assinatura = pendente;
} catch (err) { showToast('Erro ao salvar: ' + err.message, 'error'); }
}
async function handleReciboUpload(input) {
if (!input.files || !input.files.length || !currentRequest) return;
var files = Array.from(input.files);
var solId = currentRequest.id;
var uploaded = 0;
var errors = 0;
showToast('Enviando recibo...');
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!checkFileSize(file)) { errors++; continue; }
var ext = file.name.split('.').pop();
var path = solId + '/recibo/' + Date.now() + '_' + Math.random().toString(36).substr(2, 6) + '.' + ext;
try {
var upload = await sb.storage.from('compras-anexos').upload(path, file, { cacheControl: '3600', upsert: false });
if (upload.error) throw upload.error;
var insert = await sb.from('compras_anexos').insert({
solicitacao_id: solId,
nome_arquivo: file.name,
tamanho: file.size,
tipo: file.type,
storage_path: path,
uploaded_by: currentUser.id,
categoria: 'recibo'
});
if (insert.error) throw insert.error;
uploaded++;
} catch (err) {
console.error('Erro ao enviar ' + file.name + ':', err);
errors++;
}
}
input.value = '';
if (uploaded > 0) showToast('Recibo enviado!');
if (errors > 0) showToast(errors + ' arquivo(s) falharam', 'error');
// Recarregar
var anexos = await loadAnexos(solId);
var recibos = anexos.filter(function(a) { return a.categoria === 'recibo'; });
var canManage = (currentUserRole === 'compras' || currentUserRole === 'diretor' || currentUserRole === 'admin');
renderRecibo(recibos, canManage, false, currentRequest);
}
// ===== EDIÇÃO DE PREÇOS DOS ITENS =====
function updateItemPrice(input) {
var itemId = input.getAttribute('data-item-id');
var preco = parseFloat(input.value) || 0;
// Encontrar quantidade do item para atualizar total
if (currentRequest && currentRequest._itens) {
var item = currentRequest._itens.find(function(i){return i.id === itemId});
if (item) {
var totalEl = document.getElementById('itemTotal_' + itemId);
if (totalEl) totalEl.textContent = formatCurrency(preco * item.quantidade);
}
}
}
async function saveAllItemPrices() {
var inputs = document.querySelectorAll('.item-price-input');
if (!inputs.length) return;
var errors = 0;
var saved = 0;
var valorTotal = 0;
for (var idx = 0; idx < inputs.length; idx++) {
var inp = inputs[idx];
var itemId = inp.getAttribute('data-item-id');
var preco = parseFloat(inp.value) || 0;
try {
var r = await sb.from('compras_solicitacao_itens').update({preco_estimado: preco}).eq('id', itemId);
if (r.error) throw r.error;
saved++;
// Atualizar no cache local
if (currentRequest && currentRequest._itens) {
var item = currentRequest._itens.find(function(i){return i.id === itemId});
if (item) { item.preco_estimado = preco; valorTotal += preco * item.quantidade; }
}
} catch (err) {
console.error('Erro ao salvar preço:', err);
errors++;
}
}
// Atualizar valor_total da solicitação
if (valorTotal > 0) {
try {
await sb.from('compras_solicitacoes').update({valor_total: valorTotal}).eq('id', currentRequest.id);
currentRequest.valor_total = valorTotal;
document.getElementById('detailValor').textContent = formatCurrency(valorTotal);
} catch (err) { console.error('Erro ao atualizar valor total:', err); }
}
if (saved > 0) showToast('Preços salvos com sucesso!');
if (errors > 0) showToast(errors + ' erro(s) ao salvar', 'error');
}
// ===== SEÇÃO PAGAMENTO INFO (CONCLUÍDO) =====
function renderPagamentoInfo(sol, anexosPag) {
var section = document.getElementById('pagamentoInfoSection');
var content = document.getElementById('pagamentoInfoContent');
section.style.display = 'block';
var html = '';
html += '
Data do Pagamento
' + formatDate(sol.data_pagamento) + '
';
html += '
Valor do Pedido
' + formatCurrency(sol.valor_total) + '
';
html += '
';
// Valor adicional por atraso
if (sol.valor_adicional_atraso && sol.valor_adicional_atraso > 0) {
html += '';
html += '
warning Valor Adicional por Atraso: ' + formatCurrency(sol.valor_adicional_atraso) + '
';
html += '
Motivo: ' + (sol.motivo_atraso||'—') + '
';
html += '
';
}
// Comprovante de pagamento
if (anexosPag && anexosPag.length > 0) {
html += 'Comprovante de Pagamento
';
anexosPag.forEach(function(a) {
html += '
'
+ '
' + getFileIcon(a.tipo) + ' '
+ '
' + a.nome_arquivo + '
'
+ '
' + formatFileSize(a.tamanho) + ' • ' + new Date(a.created_at).toLocaleDateString('pt-BR') + '
'
+ '
download '
+ '
';
});
html += '
';
}
content.innerHTML = html;
}
// ===== RELATÓRIO COMPLETO (CONCLUÍDO) =====
function renderRelatorioCompleto(sol) {
var section = document.getElementById('relatorioSection');
var content = document.getElementById('relatorioContent');
section.style.display = 'block';
content.innerHTML = _buildFullReportHTML(sol, 'reportPrint2');
}
// ===== KANBAN BOARD =====
var listViewMode = 'table'; // 'table' or 'kanban'
function toggleListView() {
if (listViewMode === 'table') {
listViewMode = 'kanban';
document.getElementById('viewToggleBtn').innerHTML = 'table_rows Tabela';
renderKanban();
} else {
listViewMode = 'table';
document.getElementById('viewToggleBtn').innerHTML = 'view_kanban Kanban';
}
updateListViewVisibility();
}
function updateListViewVisibility() {
var tableContainer = document.querySelector('.page-list .table-container');
var kanbanContainer = document.getElementById('kanbanContainer');
var filtersRow = document.getElementById('filtersRow');
var emptyList = document.getElementById('emptyList');
if (listViewMode === 'kanban') {
if (tableContainer) tableContainer.style.display = 'none';
if (kanbanContainer) kanbanContainer.style.display = 'block';
if (filtersRow) filtersRow.style.display = 'none';
if (emptyList) emptyList.classList.add('hide');
} else {
if (tableContainer) tableContainer.style.display = '';
if (kanbanContainer) kanbanContainer.style.display = 'none';
if (filtersRow) filtersRow.style.display = '';
}
}
function getAgingDays(sol) {
var ref = sol.updated_at || sol.created_at;
if (!ref) return 0;
return Math.floor((Date.now() - new Date(ref).getTime()) / 86400000);
}
function getAgingClass(days) {
if (days <= 1) return 'aging-green';
if (days <= 4) return 'aging-amber';
return 'aging-red';
}
function getStatusBorderColor(status) {
var map = {
'aguardando_analise': '#F59E0B',
'aguardando_aprovacao': '#F59E0B',
'aprovado': '#22C55E',
'em_cotacao': '#3B82F6',
'cotacao_enviada': '#3B82F6',
'aguardando_nf': '#F59E0B',
'em_pagamento': '#f59e0b',
'concluido': '#22C55E',
'cancelado': '#606060',
'rejeitado': '#EF4444'
};
return map[status] || '#606060';
}
function renderKanban() {
var container = document.getElementById('kanbanContainer');
if (!container) return;
var html = '';
PROGRESS_STAGES.forEach(function(stage) {
var cards = allRequests.filter(function(r) { return r.status === stage.status; });
html += '
';
html += '
' + stage.label + ' ' + cards.length + '
';
html += '
';
if (cards.length === 0) {
html += '
Nenhum pedido
';
} else {
cards.forEach(function(r) {
var days = getAgingDays(r);
var agingCls = getAgingClass(days);
var borderColor = getStatusBorderColor(r.status);
var redBorderCls = days >= 5 ? ' aging-border-red' : '';
var rhTag = (r.tipo === 'admissao_demissao') ? '
RH ' : '';
html += '
';
html += '
#' + r.numero + ' ' + rhTag + '' + days + 'd
';
html += '
' + getCcHtml(r.obra_nome) + '
';
html += '
' + formatCurrency(r.valor_total) + ' ' + (r.solicitante_nome || '—') + '
';
html += '
';
});
}
html += '
';
});
html += '
';
container.innerHTML = html;
}
// ===== COMPARISON MATRIX ENHANCED =====
var _origRenderQuotations = renderQuotations;
renderQuotations = function(cotacoes) {
// Call original card rendering
var el = document.getElementById('quotationsContainer');
el.innerHTML = cotacoes.map(function(c) {
var btn = '';
var isSelected = currentRequest.cotacao_selecionada_id === c.id;
if (isSelected)
btn = 'check_circle Fornecedor Selecionado
';
return 'Condição: ' + (c.condicao_pagamento||'—') + '
Prazo: ' + (c.prazo_entrega||'—') + '
' + formatCurrency(c.valor_total) + '
' + btn + '
';
}).join('');
if (cotacoes.length > 1) {
document.getElementById('comparisonTableContainer').style.display = 'block';
// Parse delivery days for scoring
function parseDeliveryDays(prazo) {
if (!prazo) return 999;
var m = prazo.match(/(\d+)/);
return m ? parseInt(m[1]) : 999;
}
// Calculate scores
var minPrice = Infinity, minDelivery = Infinity;
cotacoes.forEach(function(c) {
if (c.valor_total < minPrice) minPrice = c.valor_total;
var dd = parseDeliveryDays(c.prazo_entrega);
if (dd < minDelivery) minDelivery = dd;
});
var scores = cotacoes.map(function(c) {
var priceScore = minPrice > 0 ? (minPrice / c.valor_total) * 100 : 100;
var dd = parseDeliveryDays(c.prazo_entrega);
var deliveryScore = minDelivery > 0 ? (minDelivery / dd) * 100 : 100;
// Conditions: simple heuristic - shorter text = simpler = better
var condLen = (c.condicao_pagamento || '').length;
var condScore = condLen > 0 ? Math.max(0, 100 - condLen) : 50;
var weighted = priceScore * 0.60 + deliveryScore * 0.25 + condScore * 0.15;
return { id: c.id, score: Math.round(weighted), priceScore: priceScore, deliveryScore: deliveryScore };
});
var bestScoreId = scores.reduce(function(best, s) { return s.score > best.score ? s : best; }, scores[0]).id;
// Find best values per column
var bestTotal = Math.min.apply(null, cotacoes.map(function(c){return c.valor_total||Infinity}));
var bestFrete = Math.min.apply(null, cotacoes.map(function(c){return c.valor_frete||Infinity}));
var bestDeliveryDays = Math.min.apply(null, cotacoes.map(function(c){return parseDeliveryDays(c.prazo_entrega)}));
var thead = 'Fornecedor Valor Itens Frete Desconto Seguro TOTAL Prazo SCORE ';
var tbody = '' + cotacoes.map(function(c) {
var sc = scores.find(function(s){return s.id === c.id});
var isWinner = c.id === bestScoreId;
var rowCls = isWinner ? ' class="winner-row"' : '';
var totalCls = c.valor_total <= bestTotal ? ' class="best-value"' : '';
var dd = parseDeliveryDays(c.prazo_entrega);
var deliveryCls = dd <= bestDeliveryDays ? ' class="best-value"' : '';
var badge = isWinner ? ' ⭐ Recomendado ' : '';
return '' + c.fornecedor_nome + ' ' + formatCurrency(c.valor_itens) + ' ' + formatCurrency(c.valor_frete) + ' -' + formatCurrency(c.valor_desconto) + ' ' + formatCurrency(c.valor_seguro) + ' ' + formatCurrency(c.valor_total) + ' ' + (c.prazo_entrega||'—') + ' ' + (sc ? sc.score : '—') + ' ' + badge + ' ';
}).join('') + ' ';
document.getElementById('comparisonTable').innerHTML = thead + tbody;
} else {
document.getElementById('comparisonTableContainer').style.display = 'none';
}
};
// ===== PULSE FEED =====
function goToPulse() {
document.querySelectorAll('.nav-item,.nav-sub-item').forEach(function(n){n.classList.remove('active')});
var subs = document.querySelectorAll('.nav-sub-item');
if (subs[0]) subs[0].classList.add('active');
switchPage('pulse');
loadPulseFeed();
}
var PULSE_ICONS = {
'encaminhado': { icon: 'arrow_forward', cls: 'pi-encaminhado' },
'aprovado': { icon: 'check_circle', cls: 'pi-aprovado' },
'rejeitado': { icon: 'cancel', cls: 'pi-rejeitado' },
'cotacao_selecionada': { icon: 'shopping_cart', cls: 'pi-cotacao_selecionada' },
'docs_fiscais_anexados': { icon: 'receipt_long', cls: 'pi-docs_fiscais_anexados' },
'pagamento_aprovado': { icon: 'paid', cls: 'pi-pagamento_aprovado' },
'cancelado': { icon: 'block', cls: 'pi-cancelado' },
'devolvido_financeiro': { icon: 'undo', cls: 'pi-devolvido_financeiro' }
};
var PULSE_MESSAGES = {
'encaminhado': function(a){ return '' + a.aprovador_nome + ' encaminhou pedido #' + (a._numero||'') + ' para aprovação'; },
'aprovado': function(a){ return '' + a.aprovador_nome + ' aprovou pedido #' + (a._numero||'') + ' '; },
'rejeitado': function(a){ return '' + a.aprovador_nome + ' rejeitou pedido #' + (a._numero||'') + ' '; },
'cotacao_selecionada': function(a){ return '' + a.aprovador_nome + ' selecionou cotação do pedido #' + (a._numero||'') + ' '; },
'docs_fiscais_anexados': function(a){ return '' + a.aprovador_nome + ' enviou pedido #' + (a._numero||'') + ' ao financeiro'; },
'pagamento_aprovado': function(a){ return '' + a.aprovador_nome + ' confirmou pagamento do pedido #' + (a._numero||'') + ' '; },
'cancelado': function(a){ return '' + a.aprovador_nome + ' cancelou pedido #' + (a._numero||'') + ' '; },
'devolvido_financeiro': function(a){ return '' + a.aprovador_nome + ' devolveu pedido #' + (a._numero||'') + ' '; }
};
function timeAgo(dateStr) {
if (!dateStr) return '';
var diff = Date.now() - new Date(dateStr).getTime();
var mins = Math.floor(diff / 60000);
if (mins < 1) return 'agora';
if (mins < 60) return mins + ' min atrás';
var hours = Math.floor(mins / 60);
if (hours < 24) return hours + 'h atrás';
var days = Math.floor(hours / 24);
if (days < 7) return days + 'd atrás';
return formatDate(dateStr);
}
async function loadPulseFeed() {
var feed = document.getElementById('pulseFeed');
try {
// Filter aprovacoes by empresa solicitacao_ids (table has no empresa_id)
var empresaSolIds = allRequests.map(function(r){ return r.id; });
var res;
if (empresaSolIds.length > 0) {
res = await sb.from('compras_aprovacoes').select('*').in('solicitacao_id', empresaSolIds).order('data_decisao', { ascending: false }).limit(50);
} else {
res = { data: [], error: null };
}
if (res.error) throw res.error;
var items = res.data || [];
if (items.length === 0) {
feed.innerHTML = 'electric_bolt Nenhuma atividade registrada
';
return;
}
// Enrich with solicitacao numbers
var solIds = [];
items.forEach(function(a) { if (a.solicitacao_id && solIds.indexOf(a.solicitacao_id) === -1) solIds.push(a.solicitacao_id); });
var solMap = {};
if (solIds.length > 0) {
var solRes = await sb.from('compras_solicitacoes').select('id,numero').in('id', solIds).limit(200);
if (solRes.data) solRes.data.forEach(function(s) { solMap[s.id] = s.numero; });
}
items.forEach(function(a) { a._numero = solMap[a.solicitacao_id] || '?'; });
var dot = document.getElementById('pulseDot');
if (dot) dot.style.display = 'inline-block';
feed.innerHTML = items.map(function(a) {
var iconInfo = PULSE_ICONS[a.decisao] || { icon: 'info', cls: 'pi-encaminhado' };
var msgFn = PULSE_MESSAGES[a.decisao];
var msg = msgFn ? msgFn(a) : a.aprovador_nome + ' — ' + a.decisao;
return ''
+ '
' + iconInfo.icon + '
'
+ '
' + msg + '
' + timeAgo(a.data_decisao) + '
'
+ '
';
}).join('');
} catch (err) {
feed.innerHTML = 'error Erro ao carregar atividades
';
console.error('Pulse error:', err);
}
}
// ===== SLA SEMAFORO =====
function renderGargalos() {
var section = document.getElementById('gargalosSection');
if (!section || allRequests.length === 0) { if (section) section.style.display = 'none'; return; }
var activeRequests = allRequests.filter(function(r) { return !['concluido','cancelado','rejeitado'].includes(r.status); });
if (activeRequests.length === 0) { section.style.display = 'none'; return; }
// Calculate days in current status for each
var stageStats = {};
var atRisk = 0;
var slowestStage = '';
var slowestAvg = 0;
var totalDays = 0;
activeRequests.forEach(function(r) {
var days = getAgingDays(r);
if (days >= 5) atRisk++;
totalDays += days;
if (!stageStats[r.status]) stageStats[r.status] = { count: 0, totalDays: 0 };
stageStats[r.status].count++;
stageStats[r.status].totalDays += days;
});
var avgOverall = activeRequests.length > 0 ? (totalDays / activeRequests.length).toFixed(1) : '0';
// Find bottleneck (most items stuck)
var bottleneckStatus = '';
var bottleneckCount = 0;
for (var st in stageStats) {
if (stageStats[st].count > bottleneckCount) {
bottleneckCount = stageStats[st].count;
bottleneckStatus = st;
}
var avg = stageStats[st].totalDays / stageStats[st].count;
if (avg > slowestAvg) {
slowestAvg = avg;
slowestStage = st;
}
}
var bottleneckLabel = STATUS_LABELS[bottleneckStatus] ? STATUS_LABELS[bottleneckStatus].split(' ')[0] + ' (' + bottleneckCount + ')' : '—';
var slowestLabel = STATUS_LABELS[slowestStage] ? STATUS_LABELS[slowestStage].split(' ')[0] : '—';
section.style.display = 'grid';
section.innerHTML = 'Média Dias/Etapa
' + avgOverall + '
dias em média por pedido ativo
'
+ 'Gargalo Acumulado
' + bottleneckLabel + '
etapa com mais pedidos
'
+ 'Em Risco (5+ dias)
' + atRisk + '
pedidos com SLA vermelho
';
}
// ===== MAPA DE CORES POR CENTRO DE CUSTO =====
var CC_COLORS = {
'010/011 — Tapa-Buraco': { cls: 'cc-010', color: '', label: '010/011 TB' },
'010 — Tapa-Buraco São Sebastião': { cls: 'cc-010', color: '', label: '010 TB S.Seb.' },
'011 — Tapa-Buraco Ceilândia': { cls: 'cc-011', color: '', label: '011 TB Ceil.' },
'013/019 — Recapeamento': { cls: 'cc-013', color: '', label: '013/019 Recap.' },
'013 — Recapeamento Lago Sul': { cls: 'cc-013', color: '', label: '013 Recap. LS' },
'019 — Recapeamento Park Way': { cls: 'cc-019', color: '#06B6D4', label: '019 Recap. PW' },
'015 — DER Viaduto': { cls: 'cc-015', color: '', label: '015 Viaduto' },
'001 — Administrativo': { cls: 'cc-001', color: '', label: '001 Admin.' },
'Outros': { cls: 'cc-outros', color: '', label: 'Outros' }
};
function getCcHtml(obraNome) {
var cc = CC_COLORS[obraNome];
if (!cc) cc = CC_COLORS['Outros'];
return ' ' + (obraNome || '—') + ' ';
}
// ===== POPULAR FILTROS DINÂMICOS =====
function populateColumnFilters() {
var solicitantes = {};
var centros = {};
var tipos = {};
allRequests.forEach(function(r) {
if (r.solicitante_nome) solicitantes[r.solicitante_nome] = true;
if (r.obra_nome) centros[r.obra_nome] = true;
if (r.tipo) tipos[r.tipo] = true;
});
var fSol = document.getElementById('filterSolicitante');
var fCC = document.getElementById('filterCentroCusto');
var fTipo = document.getElementById('filterTipo');
if (fSol) { var v = fSol.value; fSol.innerHTML = 'Todos ' + Object.keys(solicitantes).sort().map(function(s){return ''+s+' '}).join(''); fSol.value = v; }
if (fCC) { var v = fCC.value; fCC.innerHTML = 'Todos ' + Object.keys(centros).sort().map(function(s){return ''+s+' '}).join(''); fCC.value = v; }
if (fTipo) { var v = fTipo.value; fTipo.innerHTML = 'Todos ' + Object.keys(tipos).sort().map(function(s){var lb={'material':'Material','servico':'Prestação de Serviços','combustivel':'Combustível','uniforme':'Uniforme/EPI','frete':'Frete','gratificacao':'Gratificações','admissao_demissao':'Admissão e Demissão'}; return ''+(lb[s]||s)+' '}).join(''); fTipo.value = v; }
}
function clearColumnFilters() {
var ids = ['filterData','filterSolicitante','filterCentroCusto','filterTipo','filterValor'];
ids.forEach(function(id){ var el = document.getElementById(id); if (el) el.value = ''; });
filterRequests();
}
function matchDateFilter(dateStr, filter) {
if (!filter || !dateStr) return true;
var d = new Date(dateStr);
var now = new Date();
var diff = Math.floor((now - d) / 86400000);
if (filter === 'hoje') return diff < 1;
if (filter === '7d') return diff <= 7;
if (filter === '30d') return diff <= 30;
if (filter === '90d') return diff <= 90;
return true;
}
function matchValorFilter(valor, filter) {
if (!filter) return true;
var v = parseFloat(valor) || 0;
if (filter === '0-1000') return v <= 1000;
if (filter === '1000-5000') return v > 1000 && v <= 5000;
if (filter === '5000-20000') return v > 5000 && v <= 20000;
if (filter === '20000-100000') return v > 20000 && v <= 100000;
if (filter === '100000+') return v > 100000;
return true;
}
// Override renderRequests to add SLA dots + CC colors
var _origRenderRequests = renderRequests;
renderRequests = async function() {
populateColumnFilters();
var tbody = document.getElementById('listTable');
var emptyEl = document.getElementById('emptyList');
if (allRequests.length === 0) { tbody.innerHTML = ''; emptyEl.classList.remove('hide'); }
else {
emptyEl.classList.add('hide');
tbody.innerHTML = allRequests.map(function(r) {
var actions = 'visibility Ver ';
var delBtn = 'delete_outline ';
// SLA dot
var slaDot = '';
if (!['concluido','cancelado','rejeitado'].includes(r.status)) {
var days = getAgingDays(r);
var slaCls = days <= 1 ? 'sla-green' : (days <= 4 ? 'sla-amber' : 'sla-red');
slaDot = ' ';
}
return '' + r.numero + ' ' + formatDate(r.data_solicitacao) + ' ' + r.solicitante_nome + ' ' + getCcHtml(r.obra_nome) + ' ' + r.tipo + ' ' + formatCurrency(r.valor_total) + ' ' + STATUS_LABELS[r.status] + ' ' + slaDot + delBtn + '
' + actions + ' ';
}).join('');
}
renderGargalos();
if (listViewMode === 'kanban') renderKanban();
};
// Override filterRequests to include SLA dots + column filters
var _origFilterRequests = filterRequests;
filterRequests = function() {
var search = document.getElementById('searchInput').value.toLowerCase();
var sf = document.getElementById('statusFilter').value;
var fData = document.getElementById('filterData') ? document.getElementById('filterData').value : '';
var fSol = document.getElementById('filterSolicitante') ? document.getElementById('filterSolicitante').value : '';
var fCC = document.getElementById('filterCentroCusto') ? document.getElementById('filterCentroCusto').value : '';
var fTipo = document.getElementById('filterTipo') ? document.getElementById('filterTipo').value : '';
var fValor = document.getElementById('filterValor') ? document.getElementById('filterValor').value : '';
var filtered = allRequests.filter(function(r) {
if (search && !(String(r.numero||'').toLowerCase().includes(search) || String(r.obra_nome||'').toLowerCase().includes(search) || String(r.solicitante_nome||'').toLowerCase().includes(search))) return false;
if (sf && r.status !== sf) return false;
if (fData && !matchDateFilter(r.data_solicitacao, fData)) return false;
if (fSol && r.solicitante_nome !== fSol) return false;
if (fCC && r.obra_nome !== fCC) return false;
if (fTipo && r.tipo !== fTipo) return false;
if (fValor && !matchValorFilter(r.valor_total, fValor)) return false;
return true;
});
document.getElementById('listTable').innerHTML = filtered.length === 0 ? 'Nenhum pedido encontrado ' :
filtered.map(function(r) {
var actions = 'visibility Ver ';
var delBtn = 'delete_outline ';
var slaDot = '';
if (!['concluido','cancelado','rejeitado'].includes(r.status)) {
var days = getAgingDays(r);
var slaCls = days <= 1 ? 'sla-green' : (days <= 4 ? 'sla-amber' : 'sla-red');
slaDot = ' ';
}
return '' + r.numero + ' ' + formatDate(r.data_solicitacao) + ' ' + r.solicitante_nome + ' ' + getCcHtml(r.obra_nome) + ' ' + r.tipo + ' ' + formatCurrency(r.valor_total) + ' ' + STATUS_LABELS[r.status] + ' ' + slaDot + delBtn + '
' + actions + ' ';
}).join('');
};
// ===== RELATÓRIOS SUB-MODULE =====
var allRelatorios = [];
function goToRelatorios() {
if (['diretor','admin','financeiro','compras'].indexOf(currentUserRole) === -1) { showToast('Acesso restrito a Diretor, Financeiro e Compras', 'error'); return; }
switchPage('relatorios');
loadRelatorios();
}
async function loadRelatorios() {
try {
var res = await sb.from('compras_relatorios').select('*').eq('empresa_id', EMPRESA_ID).order('created_at', {ascending: false}).limit(200);
if (res.error) throw res.error;
allRelatorios = res.data || [];
renderRelatoriosStats();
populateRelCentroFilter();
filterRelatoriosUI();
} catch(err) { showToast('Erro ao carregar relatórios: ' + err.message, 'error'); }
}
function renderRelatoriosStats() {
var total = allRelatorios.length;
var valorTotal = allRelatorios.reduce(function(s, r) { return s + (parseFloat(r.valor_total) || 0); }, 0);
var mesAtual = new Date().toISOString().slice(0, 7);
var doMes = allRelatorios.filter(function(r) { return (r.created_at || '').slice(0, 7) === mesAtual; }).length;
var fornecedores = {};
allRelatorios.forEach(function(r) { if (r.fornecedor_nome) fornecedores[r.fornecedor_nome] = true; });
var el = document.getElementById('relatoriosStats');
if (!el) return;
el.innerHTML =
''
+ ''
+ 'Fornecedores
' + Object.keys(fornecedores).length + '
'
+ 'Valor Total
' + formatCurrency(valorTotal) + '
';
}
function populateRelCentroFilter() {
var centros = {};
allRelatorios.forEach(function(r) { if (r.centro_custo) centros[r.centro_custo] = true; });
var sel = document.getElementById('relCentroFilter');
if (!sel) return;
var opts = 'Todos os Centros de Custo ';
Object.keys(centros).sort().forEach(function(c) { opts += '' + c + ' '; });
sel.innerHTML = opts;
}
function filterRelatoriosUI() {
var search = (document.getElementById('relSearch').value || '').toLowerCase();
var centro = document.getElementById('relCentroFilter').value;
var filtered = allRelatorios.filter(function(r) {
var matchSearch = !search || (r.numero_pedido || '').toLowerCase().includes(search)
|| (r.fornecedor_nome || '').toLowerCase().includes(search)
|| (r.titulo || '').toLowerCase().includes(search);
var matchCentro = !centro || r.centro_custo === centro;
return matchSearch && matchCentro;
});
var tbody = document.getElementById('relTableBody');
if (!tbody) return;
if (filtered.length === 0) {
tbody.innerHTML = 'Nenhum relatório encontrado ';
return;
}
tbody.innerHTML = filtered.map(function(r) {
return ''
+ '#' + (r.numero_pedido || '—') + ' '
+ '' + formatDate(r.created_at) + ' '
+ '' + (r.fornecedor_nome || '—') + ' '
+ '' + (r.centro_custo || '—') + ' '
+ '' + formatCurrency(r.valor_total) + ' '
+ '' + (r.gerado_por_nome || '—') + ' '
+ ''
+ 'visibility Ver '
+ 'picture_as_pdf PDF '
+ '
'
+ ' ';
}).join('');
}
async function viewRelatorio(id) {
var r = allRelatorios.find(function(x) { return x.id === id; });
if (!r) return;
showToast('Carregando relatório completo...', 'info');
// Carregar dados completos do pedido em tempo real
try {
var solRes = await sb.from('compras_solicitacoes').select('*').eq('id', r.solicitacao_id).single();
var sol = solRes.data;
if (!sol) throw new Error('Pedido não encontrado');
var itensRes = await sb.from('compras_solicitacao_itens').select('*').eq('solicitacao_id', sol.id).order('item_numero');
sol._itens = itensRes.data || [];
var cotRes = await sb.from('compras_cotacoes').select('*').eq('solicitacao_id', sol.id).order('valor_total');
sol._cotacoes = cotRes.data || [];
var anexosRes = await sb.from('compras_anexos').select('*').eq('solicitacao_id', sol.id);
var savedAnexos = currentAnexos;
currentAnexos = anexosRes.data || [];
var obsRes = await sb.from('compras_observacoes').select('*').eq('solicitacao_id', sol.id).order('created_at');
var savedObs = currentObservacoes;
currentObservacoes = obsRes.data || [];
var apvRes = await sb.from('compras_aprovacoes').select('*').eq('solicitacao_id', sol.id).order('data_decisao');
var aprovacoes = apvRes.data || [];
// Criar timeline temporário para o relatório capturar
var tempTimeline = document.createElement('div');
tempTimeline.id = 'tempTimelineForReport';
tempTimeline.style.display = 'none';
if (aprovacoes.length > 0) {
var etapaLabels = {'analise_administrativa':'Análise','aprovacao_diretor':'Aprovação Diretor','aprovacao_cotacao':'Cotação','liberacao_financeiro':'Financeiro'};
tempTimeline.innerHTML = aprovacoes.map(function(a) {
var etapaLabel = etapaLabels[a.etapa] || a.etapa;
var decisaoLabel = (a.decisao || '').replace(/_/g,' '); decisaoLabel = decisaoLabel.charAt(0).toUpperCase() + decisaoLabel.slice(1);
var just = a.justificativa ? '"' + a.justificativa + '"
' : '';
return '' + formatDate(a.data_decisao) + '
' + etapaLabel + ': ' + decisaoLabel + '
Por ' + a.aprovador_nome + '
' + just + '
';
}).join('');
}
document.body.appendChild(tempTimeline);
// Salvar ref do timeline original e usar o temporário
var origTimeline = document.getElementById('timeline');
if (origTimeline) origTimeline.id = 'timeline_backup';
tempTimeline.id = 'timeline';
tempTimeline.style.display = 'block';
var reportHtml = _buildFullReportHTML(sol, 'reportPrintOverlay');
// Restaurar estado
tempTimeline.remove();
if (origTimeline) origTimeline.id = 'timeline';
currentAnexos = savedAnexos;
currentObservacoes = savedObs;
// Mostrar overlay com relatório completo
var overlay = document.createElement('div');
overlay.id = 'relatorioDetailOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px';
overlay.innerHTML = ''
+ '
'
+ '
' + (r.titulo || 'Relatório') + ' '
+ '
'
+ 'visibility Abrir '
+ 'picture_as_pdf PDF '
+ '× '
+ '
'
+ reportHtml
+ '
';
overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); });
document.body.appendChild(overlay);
} catch(err) {
showToast('Erro ao carregar relatório: ' + err.message, 'error');
}
}
function viewRelatorioNewTab(id) {
var r = allRelatorios.find(function(x) { return x.id === id; });
var overlay = document.getElementById('relatorioDetailOverlay');
var reportEl = overlay ? overlay.querySelector('[id^="reportPrint"]') : null;
if (!reportEl) { showToast('Relatório não encontrado', 'error'); return; }
var w = window.open('', '_blank');
w.document.write('Relatório Pedido #' + (r ? r.numero_pedido : '') + ' — Nexoin ERP ' + reportEl.innerHTML.replace(/var\(--bv-dark-2\)/g,'#fff').replace(/var\(--bv-dark-3\)/g,'#f5f5f5').replace(/var\(--bv-dark-4\)/g,'#ddd').replace(/var\(--bv-dark-1\)/g,'#f0f0f0').replace(/var\(--bv-text-1\)/g,'#1a1a1a').replace(/var\(--bv-text-2\)/g,'#444').replace(/var\(--bv-text-3\)/g,'#666').replace(/var\(--bv-gold\)/g,'#b8860b').replace(/var\(--bv-green\)/g,'#16a34a').replace(/var\(--bv-black\)/g,'#1a1a1a') + '');
w.document.close();
}
async function exportRelatorioPDFCompleto(solicitacaoId) {
showToast('Gerando PDF completo...', 'info');
try {
var solRes = await sb.from('compras_solicitacoes').select('*').eq('id', solicitacaoId).single();
var sol = solRes.data;
if (!sol) throw new Error('Pedido não encontrado');
var itensRes = await sb.from('compras_solicitacao_itens').select('*').eq('solicitacao_id', sol.id).order('item_numero');
sol._itens = itensRes.data || [];
var cotRes = await sb.from('compras_cotacoes').select('*').eq('solicitacao_id', sol.id).order('valor_total');
sol._cotacoes = cotRes.data || [];
var anexosRes = await sb.from('compras_anexos').select('*').eq('solicitacao_id', sol.id);
var savedAnexos = currentAnexos;
currentAnexos = anexosRes.data || [];
var obsRes = await sb.from('compras_observacoes').select('*').eq('solicitacao_id', sol.id).order('created_at');
var savedObs = currentObservacoes;
currentObservacoes = obsRes.data || [];
var reportHtml = _buildFullReportHTML(sol, 'pdfTemp');
currentAnexos = savedAnexos;
currentObservacoes = savedObs;
var tempDiv = document.createElement('div');
tempDiv.style.cssText = 'position:fixed;left:-9999px;top:0;width:800px;padding:40px;background:#fff;color:#333;font-family:Arial,sans-serif';
tempDiv.innerHTML = reportHtml.replace(/var\(--bv-dark-2\)/g,'#fff').replace(/var\(--bv-dark-3\)/g,'#f5f5f5').replace(/var\(--bv-dark-4\)/g,'#ddd').replace(/var\(--bv-dark-1\)/g,'#f0f0f0').replace(/var\(--bv-text-1\)/g,'#1a1a1a').replace(/var\(--bv-text-2\)/g,'#444').replace(/var\(--bv-text-3\)/g,'#666').replace(/var\(--bv-gold\)/g,'#b8860b').replace(/var\(--bv-green\)/g,'#16a34a').replace(/var\(--bv-black\)/g,'#1a1a1a');
document.body.appendChild(tempDiv);
var canvas = await html2canvas(tempDiv, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 800 });
var imgData = canvas.toDataURL('image/jpeg', 0.95);
var pdf = new jspdf.jsPDF('p', 'mm', 'a4');
var pageWidth = pdf.internal.pageSize.getWidth();
var pageHeight = pdf.internal.pageSize.getHeight();
var margin = 10;
var imgWidth = pageWidth - (margin * 2);
var imgHeight = (canvas.height * imgWidth) / canvas.width;
var heightLeft = imgHeight;
var position = margin;
pdf.addImage(imgData, 'JPEG', margin, position, imgWidth, imgHeight);
heightLeft -= (pageHeight - margin * 2);
while (heightLeft > 0) {
position = position - (pageHeight - margin * 2);
pdf.addPage();
pdf.addImage(imgData, 'JPEG', margin, position, imgWidth, imgHeight);
heightLeft -= (pageHeight - margin * 2);
}
var fileName = 'Relatorio_Completo_Pedido_' + sol.numero + '_' + new Date().toISOString().slice(0,10) + '.pdf';
pdf.save(fileName);
showToast('PDF gerado: ' + fileName, 'success');
document.body.removeChild(tempDiv);
} catch(e) {
showToast('Erro ao gerar PDF: ' + e.message, 'error');
}
}
async function exportRelatorioPDF(id) {
var r = allRelatorios.find(function(x) { return x.id === id; });
if (!r) return;
showToast('Gerando PDF...', 'info');
var dados = r.dados || {};
var itensRows = '';
if (dados.itens && dados.itens.length > 0) {
dados.itens.forEach(function(it, i) {
itensRows += '' + (i + 1) + ' ' + (it.descricao || it.nome || '—') + ' ' + (it.quantidade || '') + ' ' + (it.unidade || '') + ' ';
});
}
var htmlContent = '' + (r.titulo || 'Relatório de Pedido #' + (r.numero_pedido || '')) + ' '
+ 'Fornecedor ' + (r.fornecedor_nome || '—') + '
Centro de Custo ' + (r.centro_custo || '—') + '
Valor Total ' + formatCurrency(r.valor_total) + '
Data ' + formatDate(r.created_at) + '
Gerado por ' + (r.gerado_por_nome || '—') + '
'
+ (itensRows ? 'Itens # Item Qtd Un. ' + itensRows + '
' : '');
var tempDiv = document.createElement('div');
tempDiv.style.cssText = 'position:fixed;left:-9999px;top:0;width:800px;padding:40px;background:#fff;color:#333;font-family:Arial,sans-serif';
tempDiv.innerHTML = htmlContent;
document.body.appendChild(tempDiv);
try {
var canvas = await html2canvas(tempDiv, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 800 });
var imgData = canvas.toDataURL('image/jpeg', 0.95);
var pdf = new jspdf.jsPDF('p', 'mm', 'a4');
var pageWidth = pdf.internal.pageSize.getWidth();
var pageHeight = pdf.internal.pageSize.getHeight();
var margin = 10;
var imgWidth = pageWidth - (margin * 2);
var imgHeight = (canvas.height * imgWidth) / canvas.width;
var heightLeft = imgHeight;
var position = margin;
pdf.addImage(imgData, 'JPEG', margin, position, imgWidth, imgHeight);
heightLeft -= (pageHeight - margin * 2);
while (heightLeft > 0) {
position = position - (pageHeight - margin * 2);
pdf.addPage();
pdf.addImage(imgData, 'JPEG', margin, position, imgWidth, imgHeight);
heightLeft -= (pageHeight - margin * 2);
}
var fileName = 'Relatorio_Pedido_' + (r.numero_pedido || 'N') + '_' + new Date().toISOString().slice(0,10) + '.pdf';
pdf.save(fileName);
showToast('PDF gerado: ' + fileName, 'success');
} catch(e) {
console.error('Erro ao gerar PDF:', e);
showToast('Erro ao gerar PDF: ' + e.message, 'error');
} finally {
document.body.removeChild(tempDiv);
}
}
// ===== CALENDAR VIEW =====
var calendarDate = new Date();
function goToCalendar() {
document.querySelectorAll('.nav-item,.nav-sub-item').forEach(function(n){n.classList.remove('active')});
var subs = document.querySelectorAll('.nav-sub-item');
if (subs[1]) subs[1].classList.add('active');
switchPage('calendar');
renderCalendar();
}
function changeCalendarMonth(delta) {
calendarDate.setMonth(calendarDate.getMonth() + delta);
renderCalendar();
}
function renderCalendar() {
var year = calendarDate.getFullYear();
var month = calendarDate.getMonth();
var monthNames = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
var titleEl = document.getElementById('calendarMonthTitle');
if (titleEl) titleEl.textContent = monthNames[month] + ' ' + year;
var firstDay = new Date(year, month, 1);
var lastDay = new Date(year, month + 1, 0);
var startDow = firstDay.getDay(); // 0=Sun
var daysInMonth = lastDay.getDate();
var today = new Date();
var todayStr = today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0') + '-' + String(today.getDate()).padStart(2,'0');
// Build events map: date -> [ { type, label, id } ]
var eventsMap = {};
function addEvent(dateStr, type, label, id) {
if (!dateStr) return;
var d = dateStr.substring(0, 10);
if (!eventsMap[d]) eventsMap[d] = [];
eventsMap[d].push({ type: type, label: label, id: id });
}
allRequests.forEach(function(r) {
if (r.data_vencimento) addEvent(r.data_vencimento, 'deadline', '#' + r.numero + ' entrega', r.id);
if (r.data_pagamento) addEvent(r.data_pagamento, 'payment', '#' + r.numero + ' pago', r.id);
if (!['concluido','cancelado','rejeitado'].includes(r.status)) {
var days = getAgingDays(r);
if (days >= 5) {
var stuckDate = today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0') + '-' + String(today.getDate()).padStart(2,'0');
addEvent(stuckDate, 'stuck', '#' + r.numero + ' atraso', r.id);
}
}
});
// Render grid
var grid = document.getElementById('calendarGrid');
var html = '';
var dayHeaders = ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'];
dayHeaders.forEach(function(dh) {
html += '';
});
// Previous month days
var prevMonthLast = new Date(year, month, 0).getDate();
for (var p = startDow - 1; p >= 0; p--) {
html += '' + (prevMonthLast - p) + '
';
}
// Current month days
for (var d = 1; d <= daysInMonth; d++) {
var dateStr = year + '-' + String(month+1).padStart(2,'0') + '-' + String(d).padStart(2,'0');
var isToday = dateStr === todayStr;
var cls = isToday ? ' today' : '';
var dayEvents = eventsMap[dateStr] || [];
var chipsHtml = '';
var maxChips = 3;
for (var ci = 0; ci < Math.min(dayEvents.length, maxChips); ci++) {
var ev = dayEvents[ci];
var chipCls = ev.type === 'deadline' ? 'chip-deadline' : (ev.type === 'payment' ? 'chip-payment' : 'chip-stuck');
chipsHtml += '' + ev.label + '
';
}
if (dayEvents.length > maxChips) {
chipsHtml += '+' + (dayEvents.length - maxChips) + ' mais
';
}
html += '' + d + '
' + chipsHtml + '
';
}
// Next month days to fill grid
var totalCells = startDow + daysInMonth;
var remaining = totalCells % 7 === 0 ? 0 : 7 - (totalCells % 7);
for (var n = 1; n <= remaining; n++) {
html += '';
}
grid.innerHTML = html;
}
// ===== ATTACHMENT FIXES =====
var MAX_FILE_SIZE = 25 * 1024 * 1024; // 25MB
function checkFileSize(file) {
if (file.size > MAX_FILE_SIZE) {
showToast('Arquivo "' + file.name + '" excede 25MB (' + formatFileSize(file.size) + ')', 'error');
return false;
}
return true;
}
function escapeStoragePath(path) {
return (path || '').replace(/'/g, "\\'");
}