From a28f5a1d212da10f24beae34e941ae482cf1dd14 Mon Sep 17 00:00:00 2001
From: hhhthiti <109553200+hhhthiti@users.noreply.github.com>
Date: Sat, 7 Mar 2026 02:48:20 -0300
Subject: [PATCH 1/5] Revamp consulta navigation and streamline
contagem/ocupacao workflows
---
app.js | 1875 +++++++++++++++++++++++++++++++++++++++++++++++++---
index.html | 310 +++++++--
styles.css | 283 +++++++-
3 files changed, 2286 insertions(+), 182 deletions(-)
diff --git a/app.js b/app.js
index a3d10bc..7eb2bc2 100644
--- a/app.js
+++ b/app.js
@@ -3,10 +3,39 @@ const defaultConfig = {
key: 'sb_publishable_rIcKdaflOvJ0DLTJDcOrxA_bpTGG2hA'
};
+
+function storageGet(key, fallback = null) {
+ try {
+ const value = window.localStorage.getItem(key);
+ return value === null ? fallback : value;
+ } catch (_) {
+ return fallback;
+ }
+}
+
+function storageSet(key, value) {
+ try {
+ window.localStorage.setItem(key, value);
+ return true;
+ } catch (_) {
+ return false;
+ }
+}
+
+function storageGetJSON(key, fallback) {
+ const raw = storageGet(key, null);
+ if (!raw) return fallback;
+ try {
+ return JSON.parse(raw);
+ } catch (_) {
+ return fallback;
+ }
+}
+
+
const el = {
- supabaseUrl: document.getElementById('supabaseUrl'),
- supabaseKey: document.getElementById('supabaseKey'),
- connectBtn: document.getElementById('connectBtn'),
+ paleteIncompletoToggle: document.getElementById('paleteIncompletoToggle'),
+ paleteIncompletoFields: document.getElementById('paleteIncompletoFields'),
connectionStatus: document.getElementById('connectionStatus'),
feedback: document.getElementById('feedback'),
estoqueForm: document.getElementById('estoqueForm'),
@@ -14,9 +43,56 @@ const el = {
expedicaoForm: document.getElementById('expedicaoForm'),
importForm: document.getElementById('importForm'),
importFile: document.getElementById('importFile'),
+ importSyncMode: document.getElementById('importSyncMode'),
+ importFileName: document.getElementById('importFileName'),
+ selectImportBtn: document.getElementById('selectImportBtn'),
+ adminPasteForm: document.getElementById('adminPasteForm'),
+ adminPasteInput: document.getElementById('adminPasteInput'),
+ adminPasteSyncMode: document.getElementById('adminPasteSyncMode'),
estoqueTableBody: document.querySelector('#estoqueTable tbody'),
consultaAreaBody: document.querySelector('#consultaAreaTable tbody'),
+ consultaChaoBody: document.querySelector('#consultaChaoTable tbody'),
+ consultaSkuAreaBody: document.querySelector('#consultaSkuAreaTable tbody'),
+ consultaFilterForm: document.getElementById('consultaFilterForm'),
+ consultaDepositoFilter: document.getElementById('consultaDepositoFilter'),
+ consultaSideFilter: document.getElementById('consultaSideFilter'),
+ consultaMapaBox: document.getElementById('consultaMapaBox'),
+ mapaAreaSelect: document.getElementById('mapaAreaSelect'),
+ mapaPosicaoInput: document.getElementById('mapaPosicaoInput'),
+ exportConsultaResumoPdfBtn: document.getElementById('exportConsultaResumoPdfBtn'),
totaisSkuBody: document.querySelector('#totaisSkuTable tbody'),
+ sobrasB01Body: document.querySelector('#sobrasB01Table tbody'),
+ planejamentoForm: document.getElementById('planejamentoForm'),
+ previsaoEntrada: document.getElementById('previsaoEntrada'),
+ planejamentoFile: document.getElementById('planejamentoFile'),
+ planejamentoResultado: document.getElementById('planejamentoResultado'),
+ planejamentoBody: document.querySelector('#planejamentoTable tbody'),
+ ocupacaoForm: document.getElementById('ocupacaoForm'),
+ ocupacaoStatus: document.getElementById('ocupacaoStatus'),
+ exportOcupacaoPdfBtn: document.getElementById('exportOcupacaoPdfBtn'),
+ ocupacaoBody: document.querySelector('#ocupacaoTable tbody'),
+ g1DetalheBody: document.querySelector('#g1DetalheTable tbody'),
+ g1TotaisStatus: document.getElementById('g1TotaisStatus'),
+ contagemForm: document.getElementById('contagemForm'),
+ contagemScope: document.getElementById('contagemScope'),
+ contagemSideLabel: document.getElementById('contagemSideLabel'),
+ contagemSide: document.getElementById('contagemSide'),
+ contagemLoadBtn: document.getElementById('contagemLoadBtn'),
+ contagemImportBtn: document.getElementById('contagemImportBtn'),
+ contagemEstimateBtn: document.getElementById('contagemEstimateBtn'),
+ contagemApplyBtn: document.getElementById('contagemApplyBtn'),
+ contagemFile: document.getElementById('contagemFile'),
+ contagemExportBtn: document.getElementById('contagemExportBtn'),
+ contagemStatus: document.getElementById('contagemStatus'),
+ contagemBody: document.querySelector('#contagemTable tbody'),
+ contagemResumoBody: document.querySelector('#contagemResumoTable tbody'),
+ turnoForm: document.getElementById('turnoForm'),
+ turnoFile: document.getElementById('turnoFile'),
+ turnoStatus: document.getElementById('turnoStatus'),
+ turnoSkuBody: document.querySelector('#turnoSkuTable tbody'),
+ exportTurnoExcelBtn: document.getElementById('exportTurnoExcelBtn'),
+ exportTurnoPdfBtn: document.getElementById('exportTurnoPdfBtn'),
+ turnoHistoryBody: document.querySelector('#turnoHistoryTable tbody'),
movimentacoesBody: document.querySelector('#movimentacoesTable tbody'),
exportCadastroBtn: document.getElementById('exportCadastroBtn'),
exportConsultaBtn: document.getElementById('exportConsultaBtn'),
@@ -26,20 +102,70 @@ const el = {
visualizarLayoutBtn: document.getElementById('visualizarLayoutBtn'),
layoutContainer: document.getElementById('layoutContainer'),
layoutGrid: document.getElementById('layoutGrid'),
+ estruturasGrid: document.getElementById('estruturasGrid'),
+ semanticLayout: document.getElementById('semanticLayout'),
exportLayoutPdfBtn: document.getElementById('exportLayoutPdfBtn'),
- fecharLayoutBtn: document.getElementById('fecharLayoutBtn')
+ fecharLayoutBtn: document.getElementById('fecharLayoutBtn'),
+ themeToggleBtn: document.getElementById('themeToggleBtn')
};
let supabaseClient;
-let cache = { estoque: [], movimentacoes: [] };
+let cache = { estoque: [], movimentacoes: [], produtos: [] };
+let fracionadoMap = {};
+let turnoSnapshots = storageGetJSON('wmss_turno_snapshots', []);
+let lastTurnoResultado = [];
+let turnoUltimaPlanilhaSku = storageGetJSON('wmss_turno_ultima_planilha_sku', {});
+let contagemMap = storageGetJSON('wmss_contagem_map', {});
+
+const manualOcupados = new Set([
+ 'A14', 'A15', 'A16', 'A17', 'A18', 'A19',
+ 'B21D', 'B20D', 'B22D', 'B20E',
+ 'C15', 'C14', 'C12', 'C11', 'C10', 'C09', 'C08',
+ 'A09', 'A08', 'A07', 'A06', 'A05', 'A04', 'A03'
+]);
+
+const capacidadeGalpoes = {
+ principal: 2520,
+ tissue: 1728,
+ lonil: 1112,
+ ttd: 0
+};
+
+const ocupacaoRules = {
+ larguraPl2: 6,
+ profundidade: { A: 5, BD: 5, BE: 4, C: 6 },
+ posicoesPorRua: 22,
+ interditados: { A: 1, BD: 1, BE: 1, C: 1 }
+};
-el.supabaseUrl.value = defaultConfig.url;
-el.supabaseKey.value = defaultConfig.key;
+const g1Modelo = [
+ { bloco: 'A', posicoes: 22, palletPosicao: 40, bloqueado: 80, terceiros: 400 },
+ { bloco: 'B', posicoes: 22, palletPosicao: 40, bloqueado: 30, terceiros: 0 },
+ { bloco: 'C', posicoes: 22, palletPosicao: 32, bloqueado: 16, terceiros: 0 },
+ { bloco: 'D', posicoes: 22, palletPosicao: 48, bloqueado: 20, terceiros: 412 },
+ { bloco: 'H1', posicoes: 12, palletPosicao: 40, bloqueado: 40, terceiros: 120 },
+ { bloco: 'H2', posicoes: 2, palletPosicao: 32, bloqueado: 16, terceiros: 0 },
+ { bloco: 'H3', posicoes: 4, palletPosicao: 48, bloqueado: 16, terceiros: 0 },
+ { bloco: 'PP', posicoes: 1, palletPosicao: 364, bloqueado: 0, terceiros: 53 }
+];
+
+const capacidadePlanejamento = {
+ A: 80,
+ BD: 40,
+ BE: 32,
+ C: 48
+};
-const autoExportEnabled = localStorage.getItem('wmss_auto_export') === '1';
+const autoExportEnabled = storageGet('wmss_auto_export', '0') === '1';
if (el.autoExportToggle) el.autoExportToggle.checked = autoExportEnabled;
+fracionadoMap = storageGetJSON('wmss_fracionado_map', {});
+
+const darkModeEnabled = storageGet('wmss_theme', 'light') === 'dark';
+document.body.classList.toggle('dark', darkModeEnabled);
+if (el.themeToggleBtn) el.themeToggleBtn.textContent = darkModeEnabled ? '☀️ Modo claro' : '🌙 Modo escuro';
function setStatus(target, message, type = '') {
+ if (!target) return;
target.textContent = message;
target.className = `status ${type}`.trim();
}
@@ -52,21 +178,141 @@ function normalizeText(value) {
return String(value ?? '').trim().toUpperCase();
}
+function normalizeAreaCode(value) {
+ return normalizeText(value).replace(/[-_\s]/g, '');
+}
+
+function estoqueKey(area, sku, tipo) {
+ return `${normalizeAreaCode(area)}|${Number(sku)}|${normalizeText(tipo)}`;
+}
+
+function getFardosPorPalete(sku) {
+ const produto = cache.produtos.find((item) => Number(item.sku) === Number(sku));
+ const valor = Number(produto?.fardos_por_palete);
+ return Number.isFinite(valor) && valor > 0 ? valor : null;
+}
+
function shouldDeleteByAction(actionValue) {
const action = normalizeText(actionValue);
return ['APAGAR', 'EXCLUIR', 'DELETE', 'DEL', 'REMOVER', 'REMOVE'].includes(action);
}
+function isRetrabalhoArea(area) {
+ return normalizeAreaCode(area) === 'C01';
+}
+
+function parseIncomingForecast(text) {
+ return String(text || '')
+ .split('\n')
+ .map((line) => line.trim())
+ .filter(Boolean)
+ .map((line) => {
+ const [skuRaw, paletesRaw] = line.split(',').map((part) => part?.trim());
+ return { sku: Number(skuRaw), paletes: Number(paletesRaw) };
+ })
+ .filter((item) => Number.isFinite(item.sku) && Number.isFinite(item.paletes) && item.paletes > 0);
+}
+
+function parseIncomingForecastRows(rows) {
+ return rows
+ .map((row) => {
+ const normalized = Object.fromEntries(
+ Object.entries(row).map(([k, v]) => [String(k).trim().toLowerCase(), v])
+ );
+ return {
+ sku: Number(normalized.sku),
+ paletes: Number(normalized.paletes)
+ };
+ })
+ .filter((item) => Number.isFinite(item.sku) && Number.isFinite(item.paletes) && item.paletes > 0);
+}
+
+function exportTurnoResultadoExcel() {
+ if (!lastTurnoResultado.length) return showFeedback('Execute a conferência do turno antes de exportar.', 'error');
+ exportWorkbook('resultado_turno.xlsx', [{ name: 'Saida_Turno', data: lastTurnoResultado }]);
+}
+
+function exportTurnoResultadoPDF() {
+ if (!lastTurnoResultado.length) return showFeedback('Execute a conferência do turno antes de exportar.', 'error');
+ if (!window.jspdf) return showFeedback('Biblioteca de PDF não carregada.', 'error');
+
+ const { jsPDF } = window.jspdf;
+ const pdf = new jsPDF('portrait');
+ let y = 15;
+ pdf.text('Resultado da Conferência de Turno', 10, y);
+ y += 8;
+ lastTurnoResultado.forEach((row) => {
+ pdf.text(`SKU ${row.sku} | ${row.tipo} | Paletes: ${row.paletes_sairam} | Fardos: ${row.fardos_estimados}`, 10, y);
+ y += 6;
+ if (y > 280) {
+ pdf.addPage();
+ y = 15;
+ }
+ });
+ pdf.save('resultado_turno.pdf');
+}
+
+function mapAreaToPlanejamentoBloco(area) {
+ const normalized = normalizeAreaCode(area);
+ if (/^A\d+$/.test(normalized)) return 'A';
+ if (/^B\d+D$/.test(normalized) || /^BD\d+$/.test(normalized)) return 'BD';
+ if (/^B\d+E$/.test(normalized) || /^BE\d+$/.test(normalized)) return 'BE';
+ if (/^C\d+$/.test(normalized)) return 'C';
+ return null;
+}
+
+function getPlanejamentoOcupacaoAtual() {
+ const ocupado = { A: 0, BD: 0, BE: 0, C: 0 };
+ cache.estoque.forEach((row) => {
+ const bloco = mapAreaToPlanejamentoBloco(row.area);
+ if (!bloco || isRetrabalhoArea(row.area)) return;
+ ocupado[bloco] += Number(row.paletes || 0);
+ });
+ return ocupado;
+}
+
+function renderPlanejamentoTable(ocupado, previsaoPaletes = 0) {
+ if (!el.planejamentoBody) return;
+ el.planejamentoBody.innerHTML = '';
+
+ const ordem = ['A', 'BD', 'BE', 'C'];
+ const totalLivre = ordem.reduce((acc, bloco) => acc + Math.max(0, capacidadePlanejamento[bloco] - (ocupado[bloco] || 0)), 0);
+ let restante = previsaoPaletes;
+
+ ordem.forEach((bloco) => {
+ const cap = capacidadePlanejamento[bloco];
+ const ocup = ocupado[bloco] || 0;
+ const livre = Math.max(0, cap - ocup);
+ const alocar = Math.min(restante, livre);
+ restante -= alocar;
+ const apos = ocup + alocar;
+
+ const tr = document.createElement('tr');
+ tr.innerHTML = `
${bloco} ${cap} ${ocup} ${livre} ${apos} ${restante >= 0 && alocar === 0 && previsaoPaletes > 0 ? 'Considerar desfazer blocado' : 'OK'} `;
+ el.planejamentoBody.appendChild(tr);
+ });
+
+ if (el.planejamentoResultado) {
+ if (!previsaoPaletes) {
+ setStatus(el.planejamentoResultado, `Capacidade livre total hoje: ${totalLivre} paletes.`, 'success');
+ } else if (restante > 0) {
+ setStatus(el.planejamentoResultado, `Faltam ${restante} paletes de espaço. Sugestão: desfazer blocados para abrir capacidade.`, 'error');
+ } else {
+ setStatus(el.planejamentoResultado, `Previsão comportada. Entrada prevista: ${previsaoPaletes} paletes.`, 'success');
+ }
+ }
+}
+
function createClient() {
- const url = el.supabaseUrl.value.trim();
- const key = el.supabaseKey.value.trim();
- if (!url || !key) {
- setStatus(el.connectionStatus, 'Informe URL e chave para conectar.', 'error');
- return;
+ try {
+ if (!window.supabase?.createClient) throw new Error('Biblioteca do Supabase indisponível no momento.');
+ supabaseClient = window.supabase.createClient(defaultConfig.url, defaultConfig.key);
+ setStatus(el.connectionStatus, 'Conectado ao Supabase.', 'success');
+ loadAll();
+ } catch (error) {
+ setStatus(el.connectionStatus, 'Falha de conexão com Supabase.', 'error');
+ showFeedback(`Erro ao conectar no Supabase: ${error.message}`, 'error');
}
- supabaseClient = window.supabase.createClient(url, key);
- setStatus(el.connectionStatus, 'Conectado ao Supabase.', 'success');
- loadAll();
}
async function loadEstoque() {
@@ -94,10 +340,19 @@ async function loadMovimentacoes() {
renderMovimentacoes();
}
+async function loadProdutos() {
+ const { data, error } = await supabaseClient
+ .from('produtos')
+ .select('sku, fardos_por_palete');
+
+ if (error) throw error;
+ cache.produtos = data ?? [];
+}
+
async function loadAll() {
if (!supabaseClient) return;
try {
- await Promise.all([loadEstoque(), loadMovimentacoes()]);
+ await Promise.all([loadEstoque(), loadMovimentacoes(), loadProdutos()]);
showFeedback('Dados carregados com sucesso.');
} catch (error) {
showFeedback(`Erro ao carregar dados: ${error.message}`, 'error');
@@ -149,8 +404,11 @@ function renderEstoque() {
});
}
-function groupTotalBySku(rows) {
- const totals = rows.reduce((acc, row) => {
+function groupTotalBySku(rows, options = {}) {
+ const { excludeRetrabalho = false } = options;
+ const filteredRows = excludeRetrabalho ? rows.filter((row) => !isRetrabalhoArea(row.area)) : rows;
+
+ const totals = filteredRows.reduce((acc, row) => {
acc[row.sku] = (acc[row.sku] || 0) + Number(row.paletes);
return acc;
}, {});
@@ -160,24 +418,351 @@ function groupTotalBySku(rows) {
.sort((a, b) => a.sku - b.sku);
}
+function renderSobrasB01() {
+ if (!el.sobrasB01Body) return;
+ el.sobrasB01Body.innerHTML = '';
+
+ const sobras = cache.estoque.filter((row) => ['B01', 'B01E', 'B01D'].includes(normalizeAreaCode(row.area)));
+
+ sobras.forEach((row) => {
+ const tr = document.createElement('tr');
+ tr.innerHTML = `${row.area} ${row.sku} ${row.tipo} ${row.paletes} `;
+ el.sobrasB01Body.appendChild(tr);
+ });
+
+ if (!sobras.length) {
+ const tr = document.createElement('tr');
+ tr.innerHTML = 'Sem sobras cadastradas em B01/B01E/B01D. ';
+ el.sobrasB01Body.appendChild(tr);
+ }
+}
+
+function renderSemanticLayout() {
+ if (!el.semanticLayout) return;
+
+ el.semanticLayout.innerHTML = `
+
+
TISSUE
+
Rua de acesso
+
LONIL
+
+
+
C (12 posições)
+
BE / BD (12 posições)
+
A (12 posições)
+
+ Extintores
+ Escritório
+ Banheiro
+
+
+
+ `;
+}
+
+function getDepositoFromArea(area) {
+ const a = normalizeAreaCode(area);
+ if (a.startsWith('TISSUE')) return 'TISSUE';
+ if (a.startsWith('TTD')) return 'TTD';
+ if (a.startsWith('LONIL')) return 'LONIL';
+ if (/^(R\d+\.\d+|CHAOESTRUTURA)$/.test(a)) return 'ESTRUTURA';
+ return 'PRINCIPAL';
+}
+
+function getSideFromArea(area) {
+ const a = normalizeAreaCode(area);
+ if (a.endsWith('D')) return 'D';
+ if (a.endsWith('E')) return 'E';
+ return 'ALL';
+}
+
+function sideLabel(side) {
+ if (side === 'D') return '→ Direita';
+ if (side === 'E') return '← Esquerda';
+ return '—';
+}
+
+function filterConsultaRows(rows) {
+ const deposito = normalizeText(el.consultaDepositoFilter?.value || 'ALL');
+ const side = normalizeText(el.consultaSideFilter?.value || 'ALL');
+ return rows.filter((row) => {
+ const dep = getDepositoFromArea(row.area);
+ const rowSide = getSideFromArea(row.area);
+ const depositoOk = deposito === 'ALL' ? true : dep === deposito;
+ const sideOk = side === 'ALL' ? true : rowSide === side;
+ return depositoOk && sideOk;
+ });
+}
+
+function renderConsultaSkuArea(rowsFiltrados) {
+ if (!el.consultaSkuAreaBody) return;
+ const grouped = {};
+ rowsFiltrados.forEach((row) => {
+ const dep = getDepositoFromArea(row.area);
+ const key = `${dep}|${row.sku}`;
+ if (!grouped[key]) grouped[key] = { dep, sku: row.sku, paletes: 0, fardos: 0, missingFpp: false };
+ grouped[key].paletes += Number(row.paletes || 0);
+ const fpp = getFardosPorPalete(row.sku);
+ if (fpp) grouped[key].fardos += Number(row.paletes || 0) * fpp;
+ else grouped[key].missingFpp = true;
+ });
+ el.consultaSkuAreaBody.innerHTML = '';
+ Object.values(grouped)
+ .sort((a, b) => a.dep.localeCompare(b.dep) || Number(a.sku) - Number(b.sku))
+ .forEach((item) => {
+ const tr = document.createElement('tr');
+ tr.innerHTML = `${item.dep} ${item.sku} ${item.paletes} ${item.missingFpp ? 'N/D' : item.fardos} `;
+ el.consultaSkuAreaBody.appendChild(tr);
+ });
+}
+
+function renderConsultaChao(rowsFiltrados) {
+ if (!el.consultaChaoBody) return;
+ el.consultaChaoBody.innerHTML = '';
+ const chaoRows = rowsFiltrados.filter((row) => normalizeAreaCode(row.area).includes('CHAO'));
+ chaoRows.forEach((row) => {
+ const dep = getDepositoFromArea(row.area);
+ const tr = document.createElement('tr');
+ tr.innerHTML = `Chão ${dep} ${sideLabel(getSideFromArea(row.area))} ${row.area} ${row.sku} ${row.paletes} `;
+ el.consultaChaoBody.appendChild(tr);
+ });
+ if (!chaoRows.length) {
+ const tr = document.createElement('tr');
+ tr.innerHTML = 'Sem registros de chão no filtro atual. ';
+ el.consultaChaoBody.appendChild(tr);
+ }
+}
+
+function renderConsultaMapaHint() {
+ if (!el.consultaMapaBox) return;
+ const area = normalizeText(el.mapaAreaSelect?.value || 'PRINCIPAL');
+ const pos = normalizeAreaCode(el.mapaPosicaoInput?.value || '');
+ const side = sideLabel(getSideFromArea(pos));
+ const deposito = area === 'TTD' ? 'TDD' : area;
+ el.consultaMapaBox.textContent = pos
+ ? `Você está aqui: ${pos} | Depósito: ${deposito} | Lado: ${side}. Siga para o blocado indicado e confirme o endereço no sentido da seta.`
+ : `Selecione área e informe uma posição para orientação visual (ex.: B02D → Direita, B02E ← Esquerda).`;
+}
+
function renderConsulta() {
+ const rowsFiltrados = filterConsultaRows(cache.estoque);
el.consultaAreaBody.innerHTML = '';
- cache.estoque.forEach((row) => {
+ rowsFiltrados.forEach((row) => {
const tr = document.createElement('tr');
- tr.innerHTML = `${row.area} ${row.sku} ${row.tipo} ${row.paletes} `;
+ tr.innerHTML = `${getDepositoFromArea(row.area)} ${sideLabel(getSideFromArea(row.area))} ${row.area} ${row.sku} ${row.tipo} ${row.paletes} `;
el.consultaAreaBody.appendChild(tr);
});
- const totais = groupTotalBySku(cache.estoque);
+ const totais = groupTotalBySku(rowsFiltrados, { excludeRetrabalho: true });
el.totaisSkuBody.innerHTML = '';
totais.forEach((item) => {
const tr = document.createElement('tr');
tr.innerHTML = `${item.sku} ${item.total_paletes} `;
el.totaisSkuBody.appendChild(tr);
});
+
+ renderSobrasB01();
+ renderConsultaChao(rowsFiltrados);
+ renderConsultaSkuArea(rowsFiltrados);
+ renderConsultaMapaHint();
+ renderPlanejamentoTable(getPlanejamentoOcupacaoAtual(), 0);
+ renderOcupacao();
+}
+
+function inferContagemScopeFromPosicao(posicao) {
+ const p = normalizeAreaCode(posicao);
+ if (/^A\d+$/.test(p)) return 'A';
+ if (/^B\d+[ED]?$/.test(p) || /^BD\d+$/.test(p) || /^BE\d+$/.test(p)) return 'B';
+ if (/^C\d+$/.test(p)) return 'C';
+ if (/^(R\d+\.\d+|CHAOESTRUTURA)$/.test(p)) return 'ESTRUTURA';
+ if (p.startsWith('TISSUE')) return 'TISSUE';
+ if (p.startsWith('LONIL')) return 'LONIL';
+ if (p.startsWith('TTD')) return 'TTD';
+ return null;
+}
+
+function getOccupiedByWarehouse() {
+ const fromEstoque = { principal: 0, tissue: 0, lonil: 0, ttd: 0 };
+ cache.estoque.forEach((row) => {
+ const area = normalizeAreaCode(row.area);
+ const pal = Number(row.paletes || 0);
+ if (/^TISSUE\d+[ED]?$/.test(area)) {
+ fromEstoque.tissue += pal;
+ } else if (/^LONIL\d+[ED]?$/.test(area)) {
+ fromEstoque.lonil += pal;
+ } else if (/^TTD\d+[ED]?$/.test(area)) {
+ fromEstoque.ttd += pal;
+ } else if (/^(A|B|C)\d+[ED]?$/.test(area)) {
+ fromEstoque.principal += pal;
+ }
+ });
+
+ const fromContagem = { principal: 0, tissue: 0, lonil: 0, ttd: 0, linhas: 0 };
+ Object.entries(contagemMap || {}).forEach(([posicao, rawEntries]) => {
+ const scope = inferContagemScopeFromPosicao(posicao);
+ if (!scope) return;
+ const entries = Array.isArray(rawEntries) ? rawEntries : (rawEntries ? [rawEntries] : []);
+ entries.forEach((entry) => {
+ const result = computeContagem(posicao, entry, scope);
+ if (result.paletes <= 0) return;
+ if (['A', 'B', 'C'].includes(scope)) fromContagem.principal += Number(result.paletes || 0);
+ else if (scope === 'TISSUE') fromContagem.tissue += Number(result.paletes || 0);
+ else if (scope === 'LONIL') fromContagem.lonil += Number(result.paletes || 0);
+ else if (scope === 'TTD') fromContagem.ttd += Number(result.paletes || 0);
+ fromContagem.linhas += 1;
+ });
+ });
+
+ const useContagem = fromContagem.linhas > 0;
+ return {
+ principal: useContagem ? fromContagem.principal : fromEstoque.principal,
+ tissue: useContagem ? fromContagem.tissue : fromEstoque.tissue,
+ lonil: useContagem ? fromContagem.lonil : fromEstoque.lonil,
+ ttd: useContagem ? fromContagem.ttd : fromEstoque.ttd,
+ source: useContagem ? 'contagem' : 'sistema'
+ };
+}
+
+function getPrincipalCapacidadePelasRegras() {
+ const calc = (bloco) => {
+ const posicoesUteis = Math.max(0, ocupacaoRules.posicoesPorRua - Number(ocupacaoRules.interditados[bloco] || 0));
+ return posicoesUteis * ocupacaoRules.larguraPl2 * Number(ocupacaoRules.profundidade[bloco] || 0);
+ };
+ return calc('A') + calc('BD') + calc('BE') + calc('C');
+}
+
+function getPaletesResumoPorSetor() {
+ const { principal, tissue, lonil, ttd } = getOccupiedByWarehouse();
+ return { principal, tissue, lonil, ttd };
+}
+
+function exportOcupacaoResumoPDF() {
+ if (!window.jspdf) return showFeedback('Biblioteca de PDF não carregada.', 'error');
+ const resumo = getPaletesResumoPorSetor();
+ const linhas = [
+ ['Principal (Ruas A/B/C + Estruturas)', resumo.principal],
+ ['Tissue', resumo.tissue],
+ ['Lonil', resumo.lonil],
+ ['TTD', resumo.ttd]
+ ];
+
+ const { jsPDF } = window.jspdf;
+ const pdf = new jsPDF('portrait');
+ let y = 15;
+ pdf.setFontSize(14);
+ pdf.text('Resumo de Paletes por Setor', 10, y);
+ y += 8;
+ pdf.setFontSize(10);
+ pdf.text(`Gerado em: ${new Date().toLocaleString('pt-BR')}`, 10, y);
+ y += 10;
+
+ linhas.forEach(([nome, qtd]) => {
+ pdf.text(`${nome}: ${qtd} paletes`, 10, y);
+ y += 7;
+ });
+
+ y += 2;
+ const total = linhas.reduce((acc, item) => acc + Number(item[1] || 0), 0);
+ pdf.setFontSize(11);
+ pdf.text(`Total geral: ${total} paletes`, 10, y);
+ pdf.save('resumo_paletes_setores.pdf');
+ showFeedback('Resumo de paletes exportado em PDF com sucesso.');
+}
+
+function renderOcupacao() {
+ if (!el.ocupacaoBody) return;
+ el.ocupacaoBody.innerHTML = '';
+
+ const { principal, tissue, lonil, ttd, source } = getOccupiedByWarehouse();
+ const data = new Date().toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' });
+ const cap = { g1: 4402, g2: 2520, g3: 1771, g4: 846 };
+ const terceiros = { g1: 856, g2: 0, g3: 521, g4: 846 };
+ const dispTotal = {
+ g1: cap.g1 - terceiros.g1,
+ g2: cap.g2 - terceiros.g2,
+ g3: cap.g3 - terceiros.g3,
+ g4: cap.g4 - terceiros.g4
+ };
+ const ocupadoJsl = { g1: principal, g2: tissue, g3: lonil, g4: ttd };
+ const dispJsl = {
+ g1: dispTotal.g1 - ocupadoJsl.g1,
+ g2: dispTotal.g2 - ocupadoJsl.g2,
+ g3: dispTotal.g3 - ocupadoJsl.g3,
+ g4: dispTotal.g4 - ocupadoJsl.g4
+ };
+ const capacidadeFardos = 350000;
+ const qtdFardos = cache.estoque.reduce((acc, row) => {
+ const fpp = getFardosPorPalete(row.sku);
+ return acc + (fpp ? (Number(row.paletes || 0) * fpp) : 0);
+ }, 0);
+
+ const tr = document.createElement('tr');
+ tr.innerHTML = `
+ ${data} ${cap.g1} ${terceiros.g1} ${dispTotal.g1} ${ocupadoJsl.g1} ${dispJsl.g1}
+ ${data} ${cap.g2} ${terceiros.g2} ${dispTotal.g2} ${ocupadoJsl.g2} ${dispJsl.g2}
+ ${data} ${cap.g3} ${terceiros.g3} ${dispTotal.g3} ${ocupadoJsl.g3} ${dispJsl.g3}
+ ${data} ${cap.g4} ${terceiros.g4} ${dispTotal.g4} ${ocupadoJsl.g4} ${dispJsl.g4}
+ ${capacidadeFardos} ${qtdFardos.toLocaleString('pt-BR')}
+ `;
+ el.ocupacaoBody.appendChild(tr);
+
+ setStatus(el.ocupacaoStatus, source === 'contagem'
+ ? 'Ocupação atualizada pela Contagem (layout em linha reta G1/G2/G3/G4).'
+ : 'Ocupação atualizada com dados do sistema (layout em linha reta G1/G2/G3/G4).', 'success');
+}
+
+function calcularLinhaG1(item) {
+ const total = Math.max(0, (Number(item.posicoes) * Number(item.palletPosicao)) - Number(item.bloqueado));
+ const disponivel = Math.max(0, total - Number(item.terceiros));
+ return { total, disponivel };
+}
+
+function renderG1Detalhe() {
+ if (!el.g1DetalheBody) return;
+ el.g1DetalheBody.innerHTML = '';
+
+ let totalGeral = 0;
+ let terceirosGeral = 0;
+ let disponivelGeral = 0;
+
+ g1Modelo.forEach((item) => {
+ const { total, disponivel } = calcularLinhaG1(item);
+ totalGeral += total;
+ terceirosGeral += Number(item.terceiros);
+ disponivelGeral += disponivel;
+
+ const tr = document.createElement('tr');
+ tr.innerHTML = `
+ ${item.bloco}
+ ${item.posicoes}
+ ${item.palletPosicao}
+
+ ${total}
+
+ ${disponivel}
+ `;
+ el.g1DetalheBody.appendChild(tr);
+ });
+
+ setStatus(el.g1TotaisStatus, `G1 total: ${totalGeral} | terceiros: ${terceirosGeral} | disponível: ${disponivelGeral}.`, 'success');
+
+ el.g1DetalheBody.querySelectorAll('input').forEach((input) => {
+ input.addEventListener('change', (event) => {
+ const bloco = event.target.dataset.bloco;
+ const field = event.target.dataset.field;
+ const row = g1Modelo.find((i) => i.bloco === bloco);
+ if (!row) return;
+ row[field] = Math.max(0, Number(event.target.value || 0));
+ renderG1Detalhe();
+ renderOcupacao();
+ });
+ });
}
function renderMovimentacoes() {
+ if (!el.movimentacoesBody) return;
el.movimentacoesBody.innerHTML = '';
cache.movimentacoes.forEach((row) => {
const tr = document.createElement('tr');
@@ -191,21 +776,677 @@ function renderMovimentacoes() {
});
}
+const estruturaLabels = [
+ 'R1.01-07', 'R2.01-09', 'R3.01-09', 'R3.10-16', 'R4.01-09', 'R4.10-16',
+ 'R5.01-09', 'R5.10-16', 'R6.01-10', 'R6.11-16', 'CHAO ESTRUTURA'
+];
+
+function expandRangeLabel(label) {
+ const normalized = normalizeText(label);
+ if (normalized === 'CHAO ESTRUTURA') return ['CHAO ESTRUTURA'];
+ const match = normalized.match(/^(R\d+)\.(\d+)-(\d+)$/);
+ if (!match) return [normalized];
+ const prefix = match[1];
+ const start = Number(match[2]);
+ const end = Number(match[3]);
+ const values = [];
+ for (let i = start; i <= end; i += 1) values.push(`${prefix}.${String(i).padStart(2, '0')}`);
+ return values;
+}
+
+function shouldShowContagemSide() {
+ const scope = normalizeText(el.contagemScope?.value);
+ return ['B', 'TISSUE', 'TTD', 'LONIL'].includes(scope);
+}
+
+function updateContagemSideVisibility() {
+ if (!el.contagemSide) return;
+ const visible = shouldShowContagemSide();
+ const sideLabel = document.getElementById('contagemSideLabel');
+ sideLabel?.classList.toggle('hidden', !visible);
+ if (!visible) el.contagemSide.value = 'ALL';
+}
+
+function getContagemPositions(scope, side = 'ALL') {
+ const s = normalizeText(scope);
+ if (s === 'A') return Array.from({ length: 26 }, (_, i) => `A${String(i + 1).padStart(2, '0')}`);
+ if (s === 'C') return Array.from({ length: 26 }, (_, i) => `C${String(i + 1).padStart(2, '0')}`);
+ if (s === 'B') {
+ const b = [];
+ for (let i = 1; i <= 22; i += 1) {
+ const base = `B${String(i).padStart(2, '0')}`;
+ if (side === 'D') b.push(`${base}D`);
+ else if (side === 'E') b.push(`${base}E`);
+ else {
+ b.push(`${base}D`);
+ b.push(`${base}E`);
+ }
+ }
+ return b;
+ }
+ if (s === 'ESTRUTURA') return [...new Set(estruturaLabels.flatMap(expandRangeLabel))];
+ if (['TISSUE', 'TTD', 'LONIL'].includes(s)) {
+ const fromDb = cache.estoque
+ .map((row) => normalizeAreaCode(row.area))
+ .filter((area) => area.startsWith(s));
+ const maxPos = fromDb.reduce((max, area) => {
+ const m = area.match(/^(?:TISSUE|TTD|LONIL)(\d+)/);
+ return m ? Math.max(max, Number(m[1])) : max;
+ }, 0);
+ const qty = maxPos || 20;
+ const out = [];
+ for (let i = 1; i <= qty; i += 1) {
+ const base = `${s}${String(i).padStart(2, '0')}`;
+ if (side === 'D') out.push(`${base}D`);
+ else if (side === 'E') out.push(`${base}E`);
+ else {
+ out.push(`${base}D`);
+ out.push(`${base}E`);
+ }
+ }
+ return out;
+ }
+ return [];
+}
+
+function getContagemEntries(posicao) {
+ const raw = contagemMap[posicao];
+ const asArray = Array.isArray(raw) ? raw : (raw ? [raw] : []);
+ if (!asArray.length) return [createEmptyContagemEntry()];
+ return asArray.map((item) => ({
+ sku: item.sku || '',
+ profundidade1: Number(item.profundidade1 || 0),
+ largura1: Number(item.largura1 || 0),
+ segundaCamada: typeof item.segundaCamada === 'boolean' ? item.segundaCamada : (Number(item.profundidade2 || 0) > 0 || Number(item.largura2 || 0) > 0),
+ profundidade2: Number(item.profundidade2 || 0),
+ largura2: Number(item.largura2 || 0),
+ terceiraCamada: Boolean(item.terceiraCamada),
+ paletesTerceira: Number(item.paletesTerceira || 0),
+ fardosFaltando: Number(item.fardosFaltando || 0),
+ totalManual: Number(item.totalManual || 0),
+ usarTotalManual: Boolean(item.usarTotalManual),
+ confirmada: Boolean(item.confirmada),
+ tipoPlt: normalizeText(item.tipoPlt)
+ }));
+}
+
+function createEmptyContagemEntry() {
+ return {
+ sku: '',
+ profundidade1: 0,
+ largura1: 0,
+ segundaCamada: false,
+ profundidade2: 0,
+ largura2: 0,
+ terceiraCamada: false,
+ paletesTerceira: 0,
+ fardosFaltando: 0,
+ totalManual: 0,
+ usarTotalManual: false,
+ confirmada: false,
+ tipoPlt: ''
+ };
+}
+
+function saveContagemEntries(posicao, entries) {
+ const normalized = entries.map((entry) => ({
+ ...createEmptyContagemEntry(),
+ ...entry
+ }));
+ contagemMap[posicao] = normalized;
+ storageSet('wmss_contagem_map', JSON.stringify(contagemMap));
+}
+
+function addContagemEntry(posicao) {
+ const entries = getContagemEntries(posicao);
+ entries.push(createEmptyContagemEntry());
+ saveContagemEntries(posicao, entries);
+}
+
+function removeContagemEntry(posicao, idx) {
+ const entries = getContagemEntries(posicao);
+ if (entries.length <= 1) return;
+ entries.splice(idx, 1);
+ saveContagemEntries(posicao, entries);
+}
+
+function computeContagem(posicao, entry, scope = el.contagemScope?.value) {
+ const st = { ...createEmptyContagemEntry(), ...(entry || {}) };
+ const isEstrutura = normalizeText(scope) === 'ESTRUTURA';
+ const basePrimeira = Math.max(0, st.profundidade1 * st.largura1);
+ const baseSegunda = st.segundaCamada ? Math.max(0, st.profundidade2 * st.largura2) : 0;
+ const base = isEstrutura ? (normalizeText(st.sku) ? 1 : 0) : basePrimeira + baseSegunda;
+ const terceira = st.terceiraCamada ? Math.max(0, st.paletesTerceira) : 0;
+ const paletesCalculados = base + terceira;
+ const paletes = st.usarTotalManual && Number(st.totalManual) > 0 ? Number(st.totalManual) : paletesCalculados;
+ const fpp = getFardosPorPalete(st.sku);
+ const faltando = Math.max(0, st.fardosFaltando || 0);
+ const fardosBrutos = fpp ? paletes * fpp : null;
+ const fardos = Number.isFinite(fardosBrutos) ? Math.max(0, fardosBrutos - faltando) : null;
+ return { ...st, posicao, paletes, paletesCalculados, fardos };
+}
+
+function dividirQuantidade(total, slots) {
+ const qtd = Math.max(0, Number(total) || 0);
+ if (!slots) return [];
+ const base = Math.floor(qtd / slots);
+ const resto = qtd % slots;
+ return Array.from({ length: slots }, (_, idx) => base + (idx < resto ? 1 : 0));
+}
+
+function estimateLayersFromTotal(totalPaletes) {
+ const total = Math.max(0, Number(totalPaletes) || 0);
+ const capacidadeCamada = 15;
+ const primeira = Math.min(total, capacidadeCamada);
+ const segunda = Math.min(Math.max(0, total - capacidadeCamada), capacidadeCamada);
+ const terceira = Math.max(0, total - (capacidadeCamada * 2));
+
+ const toDepthWidth = (qty) => {
+ if (qty <= 0) return { profundidade: 0, largura: 0 };
+ const profundidade = Math.min(5, qty);
+ const largura = Math.min(3, Math.ceil(qty / profundidade));
+ return { profundidade, largura };
+ };
+
+ const c1 = toDepthWidth(primeira);
+ const c2 = toDepthWidth(segunda);
+ return {
+ profundidade1: c1.profundidade,
+ largura1: c1.largura,
+ segundaCamada: segunda > 0,
+ profundidade2: c2.profundidade,
+ largura2: c2.largura,
+ terceiraCamada: terceira > 0,
+ paletesTerceira: terceira
+ };
+}
+
+function hasManualContagemData(entry) {
+ return Boolean(
+ normalizeText(entry?.sku)
+ || Number(entry?.totalManual) > 0
+ || Number(entry?.profundidade1) > 0
+ || Number(entry?.largura1) > 0
+ || Number(entry?.profundidade2) > 0
+ || Number(entry?.largura2) > 0
+ || Number(entry?.paletesTerceira) > 0
+ || Boolean(entry?.usarTotalManual)
+ || Number(entry?.fardosFaltando) > 0
+ );
+}
+
+function estimateContagemFromTurno(scope, side = 'ALL') {
+ const skuTotals = turnoUltimaPlanilhaSku || {};
+ const skusDisponiveis = Object.keys(skuTotals).filter((sku) => Number(skuTotals[sku]) > 0);
+ if (!skusDisponiveis.length) return 0;
+
+ const positions = getContagemPositions(scope, side);
+ const targetsBySku = {};
+ positions.forEach((posicao) => {
+ const entries = getContagemEntries(posicao);
+ entries.forEach((entry, idx) => {
+ const sku = normalizeText(entry.sku);
+ if (!sku || !Number(skuTotals[sku])) return;
+ if (hasManualContagemData(entry)) return;
+ if (!targetsBySku[sku]) targetsBySku[sku] = [];
+ targetsBySku[sku].push({ posicao, idx });
+ });
+ });
+
+ let estimadas = 0;
+ Object.entries(targetsBySku).forEach(([sku, targets]) => {
+ const distribuicao = dividirQuantidade(Number(skuTotals[sku] || 0), targets.length);
+ targets.forEach((target, i) => {
+ const entries = getContagemEntries(target.posicao);
+ const current = { ...entries[target.idx] };
+ const total = distribuicao[i] || 0;
+ current.totalManual = total;
+ current.usarTotalManual = true;
+ if (normalizeText(scope) !== 'ESTRUTURA') Object.assign(current, estimateLayersFromTotal(total));
+ entries[target.idx] = current;
+ saveContagemEntries(target.posicao, entries);
+ estimadas += 1;
+ });
+ });
+
+ return estimadas;
+}
+
+async function importContagemFromPlanilha() {
+ const file = el.contagemFile?.files?.[0];
+ if (!file) return setStatus(el.contagemStatus, 'Selecione a planilha para pré-preencher a contagem.', 'error');
+
+ try {
+ const buffer = await file.arrayBuffer();
+ const workbook = XLSX.read(buffer, { type: 'array' });
+ const sheet = workbook.Sheets[workbook.SheetNames[0]];
+ const rawRows = XLSX.utils.sheet_to_json(sheet, { defval: '' });
+ const scope = el.contagemScope?.value;
+ const positionsAll = new Set(['A', 'B', 'C', 'ESTRUTURA', 'TISSUE', 'LONIL', 'TTD']
+ .flatMap((s) => getContagemPositions(s, 'ALL')));
+ const normalizeHeader = (key) => String(key || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
+ const parseDepositoScope = (deposito) => {
+ const dep = normalizeText(deposito);
+ if (!dep) return '';
+ if (dep.includes('ESTRUT')) return 'ESTRUTURA';
+ if (dep.includes('TISSUE')) return 'TISSUE';
+ if (dep.includes('LONIL')) return 'LONIL';
+ if (dep.includes('TTD')) return 'TTD';
+ if (dep.includes('PRINCIPAL')) return 'PRINCIPAL';
+ return '';
+ };
+
+ const planRows = rawRows
+ .map((raw) => {
+ const row = Object.fromEntries(Object.entries(raw).map(([k, v]) => [normalizeHeader(k), v]));
+ return {
+ deposito: normalizeText(row.deposito ?? row.setor ?? row.rua ?? row.area_contagem),
+ quadrante: normalizeText(row.quadrante ?? row.area ?? row.posicao ?? row.endereco),
+ area: normalizeAreaCode(row.quadrante ?? row.area ?? row.posicao ?? row.endereco),
+ sku: Number(row.sku ?? row.codsku ?? row.cod_sku),
+ paletes: Number(row.qtd_plt ?? row['qtd plt'] ?? row.paletes ?? row.pallets ?? row.quantidade),
+ tipoPlt: normalizeText(row.tipo_plt ?? row['tipo plt'] ?? row.tipo),
+ lado: normalizeText(row.lado ?? row.side),
+ setor: normalizeText(row.setor ?? row.rua ?? row.area_contagem)
+ };
+ })
+ .filter((row) => Number.isFinite(row.sku) && row.paletes > 0)
+ .map((row) => ({ ...row, depositoScope: parseDepositoScope(row.deposito) }));
+
+ const autoIndex = {};
+ const validRows = planRows
+ .map((row) => {
+ let area = normalizeAreaCode(row.area);
+ let guessScope = row.depositoScope;
+ if (!guessScope && /^A\d+/.test(area)) guessScope = 'A';
+ if (!guessScope && /^B\d+[DE]?$/.test(area)) guessScope = 'B';
+ if (!guessScope && /^C\d+/.test(area)) guessScope = 'C';
+ if (!guessScope && area.startsWith('TISSUE')) guessScope = 'TISSUE';
+ if (!guessScope && area.startsWith('LONIL')) guessScope = 'LONIL';
+ if (!guessScope && area.startsWith('TTD')) guessScope = 'TTD';
+ if (!guessScope && /^(R\d+\.\d+|CHAOESTRUTURA)$/.test(area)) guessScope = 'ESTRUTURA';
+
+ if (!area && ['TISSUE', 'TTD', 'LONIL'].includes(guessScope)) {
+ const sideFromQuadrante = row.quadrante.includes('DIREIT') ? 'D' : (row.quadrante.includes('ESQUERD') ? 'E' : '');
+ const preferredSide = row.lado === 'DIREITO' ? 'D'
+ : row.lado === 'ESQUERDO' ? 'E'
+ : (['D', 'E'].includes(row.lado) ? row.lado : (sideFromQuadrante || 'D'));
+ const key = `${guessScope}_${preferredSide}`;
+ autoIndex[key] = (autoIndex[key] || 0) + 1;
+ area = `${guessScope}${String(autoIndex[key]).padStart(2, '0')}${preferredSide}`;
+ }
+
+ if (!area && ['A', 'B', 'C', 'PRINCIPAL'].includes(guessScope)) return null;
+ if ((guessScope === 'A' || /^A/.test(area)) && /^\d+$/.test(area)) area = `A${String(Number(area)).padStart(2, '0')}`;
+ if ((guessScope === 'C' || /^C/.test(area)) && /^\d+$/.test(area)) area = `C${String(Number(area)).padStart(2, '0')}`;
+ if ((guessScope === 'B' || guessScope === 'PRINCIPAL') && /^B?\d+$/.test(area)) {
+ const num = String(Number(area.replace(/^B/, ''))).padStart(2, '0');
+ const bSide = row.quadrante.includes('ESQUERD') || row.lado === 'E' || row.lado === 'ESQUERDO' ? 'E' : 'D';
+ area = `B${num}${bSide}`;
+ }
+
+ if ((guessScope === 'PRINCIPAL' || guessScope === 'B') && /^B\d+[DE]$/.test(area)) guessScope = 'B';
+ if ((guessScope === 'PRINCIPAL' || !guessScope) && /^A\d+$/.test(area)) guessScope = 'A';
+ if ((guessScope === 'PRINCIPAL' || !guessScope) && /^C\d+$/.test(area)) guessScope = 'C';
+
+ return { ...row, area, tipoPlt: ['PL2', 'PBR'].includes(row.tipoPlt) ? row.tipoPlt : '' };
+ })
+ .filter((row) => row && positionsAll.has(normalizeAreaCode(row.area)))
+ .sort((a, b) => normalizeAreaCode(a.area).localeCompare(normalizeAreaCode(b.area), 'pt-BR', { numeric: true }));
+
+ if (!validRows.length) {
+ setStatus(el.contagemStatus, 'Nenhuma linha válida encontrada na planilha. Verifique colunas sku/deposito/quadrante/qtd plt/tipo plt.', 'error');
+ return;
+ }
+
+ const grouped = validRows.reduce((acc, row) => {
+ const area = normalizeAreaCode(row.area);
+ if (!acc[area]) acc[area] = [];
+ acc[area].push(row);
+ return acc;
+ }, {});
+
+ Object.entries(grouped).forEach(([area, group]) => {
+ const entries = group.map((row) => {
+ const total = Number(row.paletes || 0);
+ return {
+ ...createEmptyContagemEntry(),
+ sku: String(row.sku),
+ totalManual: total,
+ confirmada: false,
+ tipoPlt: row.tipoPlt,
+ ...(normalizeText(scope) === 'ESTRUTURA' ? {} : estimateLayersFromTotal(total))
+ };
+ });
+ saveContagemEntries(area, entries);
+ });
+
+ renderContagemTable();
+ setStatus(el.contagemStatus, `Planilha carregada. ${validRows.length} linha(s) aplicadas para conferência manual. Marque "Confirmar" nas linhas corretas e clique em "Atualizar consulta".`, 'success');
+ } catch (error) {
+ setStatus(el.contagemStatus, `Erro ao ler planilha da contagem: ${error.message}`, 'error');
+ }
+}
+
+function inferTipoContagem(posicao, sku) {
+ const samePos = cache.estoque.find((row) => normalizeAreaCode(row.area) === normalizeAreaCode(posicao) && Number(row.sku) === Number(sku));
+ if (samePos?.tipo) return normalizeText(samePos.tipo);
+ const sameSku = cache.estoque.find((row) => Number(row.sku) === Number(sku));
+ if (sameSku?.tipo) return normalizeText(sameSku.tipo);
+ return 'PL2';
+}
+
+async function applyContagemToConsulta() {
+ if (!supabaseClient) return setStatus(el.contagemStatus, 'Banco não conectado para atualizar consulta.', 'error');
+ const scope = el.contagemScope?.value;
+ const side = el.contagemSide?.value || 'ALL';
+ const positions = getContagemPositions(scope, side);
+ const confirmedRows = positions.flatMap((posicao) => getContagemEntries(posicao)
+ .map((entry) => computeContagem(posicao, entry, scope))
+ .filter((row) => row.confirmada && normalizeText(row.sku) && row.paletes > 0)
+ .map((row) => ({
+ area: normalizeAreaCode(row.posicao),
+ sku: Number(row.sku),
+ tipo: ['PL2', 'PBR'].includes(normalizeText(row.tipoPlt)) ? normalizeText(row.tipoPlt) : inferTipoContagem(row.posicao, row.sku),
+ paletes: Number(row.paletes)
+ })));
+
+ if (!confirmedRows.length) {
+ return setStatus(el.contagemStatus, 'Nenhuma linha confirmada para atualizar a consulta.', 'error');
+ }
+
+ const targetAreas = [...new Set(confirmedRows.map((row) => row.area))];
+ try {
+ const { error: deleteError } = await supabaseClient
+ .from('estoque_area')
+ .delete()
+ .in('area', targetAreas);
+ if (deleteError) throw deleteError;
+
+ const { error: insertError } = await supabaseClient
+ .from('estoque_area')
+ .upsert(confirmedRows);
+ if (insertError) throw insertError;
+
+ await loadAll();
+ setStatus(el.contagemStatus, `Consulta atualizada com ${confirmedRows.length} linha(s) confirmada(s) em ${targetAreas.length} posição(ões).`, 'success');
+ showFeedback('Contagem confirmada aplicada na consulta com sucesso.');
+ } catch (error) {
+ setStatus(el.contagemStatus, `Erro ao atualizar consulta pela contagem: ${error.message}`, 'error');
+ }
+}
+
+
+function renderContagemResumo(rows) {
+ if (!el.contagemResumoBody) return;
+ el.contagemResumoBody.innerHTML = '';
+ const grouped = rows.reduce((acc, row) => {
+ const sku = normalizeText(row.sku);
+ if (!sku || row.paletes <= 0) return acc;
+ if (!acc[sku]) acc[sku] = { sku, paletes: 0, fardos: 0, missing: false };
+ acc[sku].paletes += row.paletes;
+ if (Number.isFinite(row.fardos)) acc[sku].fardos += row.fardos;
+ else acc[sku].missing = true;
+ return acc;
+ }, {});
+ const rowsResumo = Object.values(grouped).sort((a, b) => Number(a.sku) - Number(b.sku));
+ rowsResumo.forEach((row) => {
+ const tr = document.createElement('tr');
+ tr.innerHTML = `${row.sku} ${row.paletes} ${row.missing ? 'SKU sem fardos/palete' : row.fardos} `;
+ el.contagemResumoBody.appendChild(tr);
+ });
+ if (!rowsResumo.length) {
+ const tr = document.createElement('tr');
+ tr.innerHTML = 'Sem posições preenchidas. ';
+ el.contagemResumoBody.appendChild(tr);
+ }
+}
+
+function preloadContagemFromEstoque(scope, side = 'ALL') {
+ const positions = new Set(getContagemPositions(scope, side));
+ let preenchidas = 0;
+
+ positions.forEach((posicao) => {
+ const existentes = getContagemEntries(posicao);
+ const temDadosDigitados = existentes.some((entry) => normalizeText(entry.sku) || Number(entry.profundidade1) > 0 || Number(entry.largura1) > 0 || Number(entry.profundidade2) > 0 || Number(entry.largura2) > 0 || Number(entry.paletesTerceira) > 0 || Boolean(entry.usarTotalManual) || Number(entry.fardosFaltando) > 0 || Number(entry.totalManual) > 0);
+ if (temDadosDigitados) return;
+
+ const rows = cache.estoque
+ .filter((row) => normalizeAreaCode(row.area) === posicao)
+ .sort((a, b) => Number(a.sku) - Number(b.sku));
+
+ if (!rows.length) return;
+
+ const entries = rows.map((row) => ({
+ ...createEmptyContagemEntry(),
+ sku: String(row.sku || '')
+ }));
+
+ saveContagemEntries(posicao, entries);
+ preenchidas += 1;
+ });
+
+ return preenchidas;
+}
+
+function renderContagemTable() {
+ if (!el.contagemBody) return;
+ updateContagemSideVisibility();
+ const scope = el.contagemScope?.value;
+ const isEstrutura = normalizeText(scope) === 'ESTRUTURA';
+ const positions = getContagemPositions(scope, el.contagemSide?.value || 'ALL');
+ el.contagemBody.innerHTML = '';
+ const computedRows = [];
+
+ positions.forEach((posicao) => {
+ const entries = getContagemEntries(posicao);
+
+ entries.forEach((entry, idx) => {
+ const result = computeContagem(posicao, entry, scope);
+ computedRows.push(result);
+ const tr = document.createElement('tr');
+ const sideHint = sideLabel(getSideFromArea(posicao));
+ const posLabel = idx === 0 ? `${posicao} ${sideHint} ` : `↳ ${posicao} ${sideHint} `;
+ const plusOrRemove = idx === 0
+ ? `+ `
+ : `− `;
+
+ tr.innerHTML = `
+ ${plusOrRemove}
+ ${posLabel}
+
+
+ ${isEstrutura ? '- ' : ` `}
+ ${isEstrutura ? '- ' : ` `}
+ ${isEstrutura ? '- ' : ` `}
+ ${isEstrutura ? '- ' : (entry.segundaCamada ? ` ` : 'marque 2ª camada ')}
+ ${isEstrutura ? '- ' : (entry.segundaCamada ? ` ` : 'marque 2ª camada ')}
+
+
+
+
+
+ ${result.paletes}
+ ${Number.isFinite(result.fardos) ? result.fardos : '-'}
+ `;
+ el.contagemBody.appendChild(tr);
+ });
+ });
+
+ el.contagemBody.querySelectorAll('button[data-action="add-entry"]').forEach((btn) => {
+ btn.addEventListener('click', (event) => {
+ addContagemEntry(event.currentTarget.dataset.posicao);
+ renderContagemTable();
+ });
+ });
+
+ el.contagemBody.querySelectorAll('button[data-action="remove-entry"]').forEach((btn) => {
+ btn.addEventListener('click', (event) => {
+ const { posicao, entryIdx } = event.currentTarget.dataset;
+ removeContagemEntry(posicao, Number(entryIdx));
+ renderContagemTable();
+ });
+ });
+
+ el.contagemBody.querySelectorAll('input').forEach((input) => {
+ input.addEventListener('keydown', (event) => {
+ if (event.key !== 'Enter') return;
+ event.preventDefault();
+ focusNextContagemInput(event.currentTarget);
+ });
+
+ input.addEventListener('change', (event) => {
+ const { posicao, entryIdx, field } = event.target.dataset;
+ const entries = getContagemEntries(posicao);
+ const idx = Number(entryIdx || 0);
+ const current = { ...entries[idx] };
+ current[field] = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
+ if (field === 'segundaCamada' && !event.target.checked) {
+ current.profundidade2 = 0;
+ current.largura2 = 0;
+ }
+ if (field === 'terceiraCamada' && !event.target.checked) current.paletesTerceira = 0;
+ if (field === 'usarTotalManual' && !event.target.checked) current.totalManual = 0;
+ entries[idx] = current;
+ saveContagemEntries(posicao, entries);
+ renderContagemTable();
+ });
+ });
+
+ renderContagemResumo(computedRows);
+}
+
+
+function focusNextContagemInput(currentInput) {
+ if (!el.contagemBody || !currentInput) return;
+ const fields = Array.from(el.contagemBody.querySelectorAll('input'))
+ .filter((node) => !node.disabled && node.type !== 'hidden');
+ const idx = fields.indexOf(currentInput);
+ if (idx < 0) return;
+ const next = fields[idx + 1];
+ if (!next) return;
+ next.focus();
+ if (next.type !== 'checkbox') next.select?.();
+}
+
+function exportContagemExcel() {
+ const allScopes = ['A', 'B', 'C', 'ESTRUTURA', 'TISSUE', 'TTD', 'LONIL'];
+ const rows = allScopes
+ .flatMap((scope) => getContagemPositions(scope, 'ALL')
+ .flatMap((p) => getContagemEntries(p).map((entry, idx) => ({ ...computeContagem(p, entry, scope), entry_idx: idx + 1, scope }))))
+ .filter((row) => normalizeText(row.sku) && row.paletes > 0)
+ .map((row) => ({
+ area_contagem: row.scope,
+ posicao: row.posicao,
+ sku: row.sku,
+ confirmada: row.confirmada ? 'SIM' : 'NAO',
+ item_posicao: row.entry_idx,
+ profundidade_1: row.profundidade1,
+ largura_1: row.largura1,
+ segunda_camada: row.segundaCamada ? 'SIM' : 'NAO',
+ profundidade_2: row.profundidade2,
+ largura_2: row.largura2,
+ terceira_camada: row.terceiraCamada ? 'SIM' : 'NAO',
+ paletes_terceira: row.terceiraCamada ? row.paletesTerceira : 0,
+ fardos_faltando: row.fardosFaltando || 0,
+ usar_total_editavel: row.usarTotalManual ? 'SIM' : 'NAO',
+ total_editavel: row.totalManual || 0,
+ paletes_totais: row.paletes,
+ fardos_totais: Number.isFinite(row.fardos) ? row.fardos : ''
+ }));
+ if (!rows.length) return showFeedback('Nenhuma posição preenchida para exportar.', 'error');
+ const resumo = Object.values(rows.reduce((acc, row) => {
+ const sku = String(row.sku);
+ if (!acc[sku]) acc[sku] = { sku, paletes: 0, fardos: 0 };
+ acc[sku].paletes += Number(row.paletes_totais || 0);
+ acc[sku].fardos += Number(row.fardos_totais || 0);
+ return acc;
+ }, {}));
+ exportWorkbook('contagem.xlsx', [
+ { name: 'Contagem', data: rows },
+ { name: 'Resumo_SKU', data: resumo }
+ ]);
+ showFeedback('Contagem exportada com sucesso (todas as áreas preenchidas).');
+}
+
+function setupContagem() {
+ updateContagemSideVisibility();
+ el.contagemScope?.addEventListener('change', () => {
+ updateContagemSideVisibility();
+ renderContagemTable();
+ });
+ el.contagemSide?.addEventListener('change', renderContagemTable);
+ el.contagemLoadBtn?.addEventListener('click', () => {
+ const scope = el.contagemScope?.value;
+ const side = el.contagemSide?.value || 'ALL';
+ const preenchidas = preloadContagemFromEstoque(scope, side) || 0;
+ const estimadas = estimateContagemFromTurno(scope, side);
+ renderContagemTable();
+ setStatus(el.contagemStatus, `Posições carregadas para preenchimento. SKU(s) sugeridos em ${preenchidas} posição(ões). Estimativa da conferência aplicada em ${estimadas} linha(s).`, 'success');
+ });
+ el.contagemImportBtn?.addEventListener('click', importContagemFromPlanilha);
+ el.contagemEstimateBtn?.addEventListener('click', () => {
+ const scope = el.contagemScope?.value;
+ const side = el.contagemSide?.value || 'ALL';
+ const estimadas = estimateContagemFromTurno(scope, side);
+ renderContagemTable();
+ setStatus(el.contagemStatus, estimadas
+ ? `Estimativa da última conferência aplicada em ${estimadas} linha(s). Campos continuam editáveis, inclusive o total.`
+ : 'Não há dados da última conferência para estimar (suba a planilha na aba Conferência de turno).', estimadas ? 'success' : 'error');
+ });
+ el.contagemApplyBtn?.addEventListener('click', applyContagemToConsulta);
+ el.contagemForm?.addEventListener('submit', (event) => {
+ event.preventDefault();
+ renderContagemTable();
+ setStatus(el.contagemStatus, 'Cálculo atualizado.', 'success');
+ });
+ el.contagemExportBtn?.addEventListener('click', exportContagemExcel);
+}
+
async function handleEstoqueSubmit(event) {
event.preventDefault();
if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error');
const formData = new FormData(event.target);
const payload = {
- area: normalizeText(formData.get('area')),
+ area: normalizeAreaCode(formData.get('area')),
sku: Number(formData.get('sku')),
tipo: normalizeText(formData.get('tipo')),
paletes: Number(formData.get('paletes'))
};
+ const fardosInput = Number(formData.get('fardos_por_palete'));
+ const fardosExistente = getFardosPorPalete(payload.sku);
+ if (!fardosExistente && (!Number.isFinite(fardosInput) || fardosInput <= 0)) {
+ return showFeedback('SKU sem fardos por palete cadastrado. Informe no campo "Fardos por palete (SKU novo)".', 'error');
+ }
+
+ const paletesIncompletos = Number(formData.get('paletes_incompletos'));
+ const fardosIncompletos = Number(formData.get('fardos_incompletos'));
+ const incompletoAtivo = el.paleteIncompletoToggle?.checked;
+
try {
+ if (!fardosExistente && Number.isFinite(fardosInput) && fardosInput > 0) {
+ const { error: produtoError } = await supabaseClient
+ .from('produtos')
+ .upsert({ sku: payload.sku, fardos_por_palete: fardosInput });
+ if (produtoError) throw produtoError;
+ }
+
const { error } = await supabaseClient.from('estoque_area').upsert(payload);
if (error) throw error;
+
+ const chave = estoqueKey(payload.area, payload.sku, payload.tipo);
+ if (incompletoAtivo) {
+ if (!Number.isFinite(paletesIncompletos) || paletesIncompletos <= 0 || !Number.isFinite(fardosIncompletos) || fardosIncompletos <= 0) {
+ return showFeedback('Informe quantidade de paletes incompletos e total de fardos.', 'error');
+ }
+ fracionadoMap[chave] = { paletes_incompletos: paletesIncompletos, fardos_incompletos: fardosIncompletos };
+ } else {
+ delete fracionadoMap[chave];
+ }
+ storageSet('wmss_fracionado_map', JSON.stringify(fracionadoMap));
+
showFeedback('Estoque salvo com sucesso.');
event.target.reset();
await loadEstoque();
@@ -251,7 +1492,7 @@ async function handleExpedicaoSubmit(event) {
if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error');
const formData = new FormData(event.target);
- const area = normalizeText(formData.get('area'));
+ const area = normalizeAreaCode(formData.get('area'));
const sku = Number(formData.get('sku'));
const tipo = normalizeText(formData.get('tipo'));
const paletes = Number(formData.get('paletes'));
@@ -294,28 +1535,245 @@ async function handleExpedicaoSubmit(event) {
}
}
+function consolidarPorChave(rows) {
+ return rows.reduce((acc, row) => {
+ const key = estoqueKey(row.area, row.sku, row.tipo);
+ acc[key] = (acc[key] || 0) + Number(row.paletes || 0);
+ return acc;
+ }, {});
+}
+
+function saveTurnoSnapshot(snapshotRows) {
+ const item = {
+ created_at: new Date().toISOString(),
+ rows: snapshotRows
+ };
+
+ turnoSnapshots = [item, ...turnoSnapshots].slice(0, 3);
+ storageSet('wmss_turno_snapshots', JSON.stringify(turnoSnapshots));
+ renderTurnoHistory();
+}
+
+function renderTurnoHistory() {
+ if (!el.turnoHistoryBody) return;
+ el.turnoHistoryBody.innerHTML = '';
+
+ if (!turnoSnapshots.length) {
+ const tr = document.createElement('tr');
+ tr.innerHTML = 'Sem snapshots salvos. ';
+ el.turnoHistoryBody.appendChild(tr);
+ return;
+ }
+
+ turnoSnapshots.forEach((snap) => {
+ const tr = document.createElement('tr');
+ tr.innerHTML = `${new Date(snap.created_at).toLocaleString('pt-BR')} ${snap.rows.length} `;
+ el.turnoHistoryBody.appendChild(tr);
+ });
+}
+
+async function handleTurnoSubmit(event) {
+ event.preventDefault();
+ const file = el.turnoFile?.files?.[0];
+ if (!file) return setStatus(el.turnoStatus, 'Selecione a planilha de contagem final.', 'error');
+
+ try {
+ const buffer = await file.arrayBuffer();
+ const workbook = XLSX.read(buffer, { type: 'array' });
+ const sheet = workbook.Sheets[workbook.SheetNames[0]];
+ const rows = XLSX.utils.sheet_to_json(sheet, { defval: '' }).map(mapImportRow);
+ turnoUltimaPlanilhaSku = rows.reduce((acc, row) => {
+ const sku = Number(row.sku);
+ const paletes = Number(row.paletes);
+ if (!Number.isFinite(sku) || !Number.isFinite(paletes) || paletes <= 0) return acc;
+ const key = String(sku);
+ acc[key] = (acc[key] || 0) + paletes;
+ return acc;
+ }, {});
+ storageSet('wmss_turno_ultima_planilha_sku', JSON.stringify(turnoUltimaPlanilhaSku));
+
+ const snapshotAnterior = turnoSnapshots[0]?.rows ?? null;
+ const atual = snapshotAnterior ? consolidarPorChave(snapshotAnterior) : consolidarPorChave(cache.estoque);
+ const finalTurno = consolidarPorChave(rows);
+ const saidaSkuTipo = {};
+ const missing = new Set();
+ lastTurnoResultado = [];
+
+ Object.keys(atual).forEach((key) => {
+ const saiu = Math.max(0, Number(atual[key] || 0) - Number(finalTurno[key] || 0));
+ if (!saiu) return;
+ const [, sku, tipo] = key.split('|');
+ const skuTipo = `${sku}|${tipo}`;
+ saidaSkuTipo[skuTipo] = (saidaSkuTipo[skuTipo] || 0) + saiu;
+ });
+
+ el.turnoSkuBody.innerHTML = '';
+ Object.entries(saidaSkuTipo)
+ .sort(([a], [b]) => a.localeCompare(b, 'pt-BR', { numeric: true }))
+ .forEach(([skuTipo, paletes]) => {
+ const [sku, tipo] = skuTipo.split('|');
+ const fpp = getFardosPorPalete(Number(sku));
+ if (!fpp) missing.add(sku);
+ lastTurnoResultado.push({
+ sku: Number(sku),
+ tipo,
+ paletes_sairam: paletes,
+ fardos_estimados: fpp ? paletes * fpp : 'N/D'
+ });
+ const tr = document.createElement('tr');
+ tr.innerHTML = `${sku} ${tipo} ${paletes} ${fpp ? paletes * fpp : 'N/D'} `;
+ el.turnoSkuBody.appendChild(tr);
+ });
+
+ if (!Object.keys(saidaSkuTipo).length) {
+ const tr = document.createElement('tr');
+ tr.innerHTML = 'Nenhuma saída detectada na comparação. ';
+ el.turnoSkuBody.appendChild(tr);
+ }
+
+ saveTurnoSnapshot(rows);
+
+ if (missing.size) {
+ setStatus(el.turnoStatus, `Comparação concluída (${snapshotAnterior ? 'anterior x atual' : 'estoque atual x planilha'}). SKU(s) sem fardos por palete: ${[...missing].join(', ')}.`, 'error');
+ } else {
+ setStatus(el.turnoStatus, `Comparação concluída com sucesso (${snapshotAnterior ? 'anterior x atual' : 'estoque atual x planilha'}).`, 'success');
+ }
+ } catch (error) {
+ setStatus(el.turnoStatus, `Erro ao processar planilha do turno: ${error.message}`, 'error');
+ }
+}
+
function mapImportRow(rawRow) {
const row = Object.fromEntries(
Object.entries(rawRow).map(([k, v]) => [String(k).trim().toLowerCase(), v])
);
+ const areaRaw = row.area ?? row['área'] ?? row.endereco ?? row.endereço;
+ const skuRaw = row.sku ?? row.codsku ?? row['cód_sku'];
+ const tipoRaw = row.tipo ?? row.produto_tipo;
+ const paletesRaw = row.paletes ?? row.pallets ?? row.quantidade;
+
return {
- area: normalizeText(row.area),
- sku: Number(row.sku),
- tipo: normalizeText(row.tipo),
- paletes: Number(row.paletes),
+ area: normalizeAreaCode(areaRaw),
+ sku: Number(skuRaw),
+ tipo: normalizeText(tipoRaw),
+ paletes: Number(paletesRaw),
acao: normalizeText(row.acao)
};
}
+function isEmptyImportItem(item) {
+ return !item.area && !Number.isFinite(item.sku) && !item.tipo && !Number.isFinite(item.paletes) && !item.acao;
+}
+
function validateImportItem(item, line) {
+ if (isEmptyImportItem(item)) return 'SKIP';
if (!item.area) return `Linha ${line}: área inválida`;
if (!Number.isFinite(item.sku) || item.sku <= 0) return `Linha ${line}: sku inválido`;
if (!item.tipo) return `Linha ${line}: tipo inválido`;
- if (!Number.isFinite(item.paletes) || item.paletes < 0) return `Linha ${line}: paletes inválido`;
+ const deleteRow = shouldDeleteByAction(item.acao);
+ if (!deleteRow && (!Number.isFinite(item.paletes) || item.paletes < 0)) return `Linha ${line}: paletes inválido`;
return null;
}
+function parsePastedTable(text) {
+ const raw = String(text || '').trim();
+ if (!raw) return [];
+ const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
+ if (!lines.length) return [];
+
+ const delim = lines[0].includes(' ') ? ' ' : (lines[0].includes(';') ? ';' : ',');
+ const parseLine = (line) => {
+ if (delim === ',') {
+ const out = [];
+ let cur = '';
+ let quoted = false;
+ for (let i = 0; i < line.length; i += 1) {
+ const ch = line[i];
+ if (ch === '"') {
+ if (quoted && line[i + 1] === '"') {
+ cur += '"';
+ i += 1;
+ } else quoted = !quoted;
+ } else if (ch === ',' && !quoted) {
+ out.push(cur.trim());
+ cur = '';
+ } else cur += ch;
+ }
+ out.push(cur.trim());
+ return out;
+ }
+ return line.split(delim).map((v) => String(v || '').trim());
+ };
+
+ const headers = parseLine(lines[0]).map((h) => normalizeText(h).toLowerCase());
+ return lines.slice(1).map((line) => {
+ const cols = parseLine(line);
+ const row = {};
+ headers.forEach((h, idx) => {
+ row[h] = cols[idx] ?? '';
+ });
+ return row;
+ });
+}
+
+async function processImportRows(rows, syncMode) {
+ if (!rows.length) throw new Error('Planilha vazia.');
+
+ let insertedOrUpdated = 0;
+ let deleted = 0;
+ const mentionedKeys = new Set();
+
+ for (let i = 0; i < rows.length; i += 1) {
+ const item = mapImportRow(rows[i]);
+ const error = validateImportItem(item, i + 2);
+ if (error === 'SKIP') continue;
+ if (error) throw new Error(error);
+
+ if (shouldDeleteByAction(item.acao) || item.paletes === 0) {
+ const { error: deleteError } = await supabaseClient
+ .from('estoque_area')
+ .delete()
+ .match({ area: item.area, sku: item.sku, tipo: item.tipo });
+
+ if (deleteError) throw new Error(`Linha ${i + 2}: ${deleteError.message}`);
+ deleted += 1;
+ } else {
+ mentionedKeys.add(estoqueKey(item.area, item.sku, item.tipo));
+ const payload = {
+ area: item.area,
+ sku: item.sku,
+ tipo: item.tipo,
+ paletes: item.paletes
+ };
+ const { error: upsertError } = await supabaseClient.from('estoque_area').upsert(payload);
+ if (upsertError) throw new Error(`Linha ${i + 2}: ${upsertError.message}`);
+ insertedOrUpdated += 1;
+ }
+ }
+
+ if (syncMode) {
+ const { data: atuais, error: fetchAtualError } = await supabaseClient
+ .from('estoque_area')
+ .select('area, sku, tipo');
+ if (fetchAtualError) throw fetchAtualError;
+
+ const paraRemover = (atuais || []).filter((row) => !mentionedKeys.has(estoqueKey(row.area, row.sku, row.tipo)));
+ for (const row of paraRemover) {
+ const { error: deleteSyncError } = await supabaseClient
+ .from('estoque_area')
+ .delete()
+ .match({ area: row.area, sku: row.sku, tipo: row.tipo });
+ if (deleteSyncError) throw deleteSyncError;
+ deleted += 1;
+ }
+ }
+
+ await loadEstoque();
+ maybeAutoExport();
+ return { insertedOrUpdated, deleted };
+}
+
async function handleImportSubmit(event) {
event.preventDefault();
if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error');
@@ -328,53 +1786,40 @@ async function handleImportSubmit(event) {
const workbook = XLSX.read(buffer, { type: 'array' });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json(sheet, { defval: '' });
-
- if (!rows.length) throw new Error('Planilha vazia.');
-
- let insertedOrUpdated = 0;
- let deleted = 0;
-
- for (let i = 0; i < rows.length; i += 1) {
- const item = mapImportRow(rows[i]);
- const error = validateImportItem(item, i + 2);
- if (error) throw new Error(error);
-
- if (shouldDeleteByAction(item.acao) || item.paletes === 0) {
- const { error: deleteError } = await supabaseClient
- .from('estoque_area')
- .delete()
- .match({ area: item.area, sku: item.sku, tipo: item.tipo });
-
- if (deleteError) throw new Error(`Linha ${i + 2}: ${deleteError.message}`);
- deleted += 1;
- } else {
- const payload = {
- area: item.area,
- sku: item.sku,
- tipo: item.tipo,
- paletes: item.paletes
- };
- const { error: upsertError } = await supabaseClient.from('estoque_area').upsert(payload);
- if (upsertError) throw new Error(`Linha ${i + 2}: ${upsertError.message}`);
- insertedOrUpdated += 1;
- }
- }
-
- await loadEstoque();
- maybeAutoExport();
- showFeedback(`Importação concluída. Incluídos/atualizados: ${insertedOrUpdated}. Apagados: ${deleted}.`);
+ const { insertedOrUpdated, deleted } = await processImportRows(rows, Boolean(el.importSyncMode?.checked));
+ showFeedback(`Importação concluída (${el.importSyncMode?.checked ? 'sincronização agressiva' : 'modo seguro'}). Incluídos/atualizados: ${insertedOrUpdated}. Apagados: ${deleted}.`);
el.importForm.reset();
+ if (el.importFileName) el.importFileName.textContent = 'Nenhum arquivo selecionado';
} catch (error) {
showFeedback(`Erro na importação: ${error.message}`, 'error');
}
}
+async function handleAdminPasteSubmit(event) {
+ event.preventDefault();
+ if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error');
+ try {
+ const rows = parsePastedTable(el.adminPasteInput?.value);
+ const { insertedOrUpdated, deleted } = await processImportRows(rows, Boolean(el.adminPasteSyncMode?.checked));
+ showFeedback(`Atualização ADM concluída (${el.adminPasteSyncMode?.checked ? 'sincronização agressiva' : 'modo seguro'}). Incluídos/atualizados: ${insertedOrUpdated}. Apagados: ${deleted}.`);
+ } catch (error) {
+ showFeedback(`Erro na atualização ADM: ${error.message}`, 'error');
+ }
+}
+
function parseAreaForLayout(areaRaw) {
const area = normalizeText(areaRaw);
- const normalized = area.replace(/[-_\s]/g, '');
+ const normalized = normalizeAreaCode(areaRaw);
+
+ const bSuffixedMatch = normalized.match(/^B(\d+)([ED])$/);
+ if (bSuffixedMatch) {
+ const pos = Number(bSuffixedMatch[1]);
+ const lado = bSuffixedMatch[2] === 'E' ? 'BE' : 'BD';
+ return { bloco: lado, pos, area };
+ }
- const directMatch = normalized.match(/^(TISSUE|LONIL|A|C|BE|BD)(\d+)$/);
+ const directMatch = normalized.match(/^(TISSUE|LONIL|A|B|C|BE|BD)(\d+)$/);
if (directMatch) {
return { bloco: directMatch[1], pos: Number(directMatch[2]), area };
}
@@ -386,53 +1831,140 @@ function parseAreaForLayout(areaRaw) {
const legacyBMatch = normalized.match(/^B(\d+)$/);
if (legacyBMatch) {
- return { bloco: 'BE', pos: Number(legacyBMatch[1]), area };
+ return { bloco: 'B', pos: Number(legacyBMatch[1]), area };
+ }
+
+ const tunelMatch = normalized.match(/^TUNEL(\d+)$/);
+ if (tunelMatch) {
+ return { bloco: 'TUNEL', pos: Number(tunelMatch[1]), area };
}
return null;
}
+function getAreaVariants(areaCode) {
+ const normalized = normalizeAreaCode(areaCode);
+ const match = normalized.match(/^([A-Z]+)(\d+)([A-Z]?)$/);
+ if (!match) return [normalized];
+ const prefix = match[1];
+ const pos = Number(match[2]);
+ const suffix = match[3] || '';
+ const noPad = `${prefix}${pos}${suffix}`;
+ const pad2 = `${prefix}${String(pos).padStart(2, '0')}${suffix}`;
+ return [...new Set([normalized, noPad, pad2])];
+}
+
+function getAreaItems(areaCode, byArea) {
+ const variants = getAreaVariants(areaCode);
+ const merged = [];
+ variants.forEach((key) => {
+ const arr = byArea.get(key);
+ if (arr?.length) merged.push(...arr);
+ });
+ return merged;
+}
+
+function getAreaText(areaCode, byArea, fallback = 'Vazio') {
+ const items = getAreaItems(areaCode, byArea);
+ if (!items.length) return fallback;
+ return items.map((item) => `${item.sku}`).join('; ');
+}
+
function gerarLayoutVisual() {
- if (!el.layoutGrid || !el.layoutContainer) return;
+ if (!el.layoutGrid || !el.layoutContainer || !el.estruturasGrid) return;
el.layoutGrid.innerHTML = '';
- const colunas = ['TISSUE', 'C', 'BE', 'BD', 'A', 'LONIL'];
+ el.estruturasGrid.innerHTML = '';
- const parsed = cache.estoque
- .map((item) => ({ item, meta: parseAreaForLayout(item.area) }))
- .filter((entry) => entry.meta);
+ const byArea = new Map();
+ cache.estoque.forEach((row) => {
+ const key = normalizeAreaCode(row.area);
+ if (!byArea.has(key)) byArea.set(key, []);
+ byArea.get(key).push(row);
+ });
+
+ const posicoes = Array.from({ length: 26 }, (_, i) => 26 - i);
+ const bPosicoes = Array.from({ length: 22 }, (_, i) => 22 - i);
+
+ const anotacoesA = { 26: 'Bloqueado', 23: 'Bloqueado', 14: 'Recebimento', 1: 'Carregamento' };
+ const anotacoesC = { 1: 'Sala ADM', 2: 'Retrabalho' };
- const maxLinha = Math.max(12, ...parsed.map((entry) => entry.meta.pos));
+ const criarLinha = (prefixo, anotacoes = {}) => {
+ const row = document.createElement('div');
+ row.className = 'bp-row';
- for (let i = 1; i <= maxLinha; i += 1) {
- colunas.forEach((coluna) => {
+ posicoes.forEach((pos) => {
+ const area = `${prefixo}${String(pos).padStart(2, '0')}`;
const cell = document.createElement('div');
- cell.className = 'celula vazio';
- const areaNome = `${coluna}${i}`;
+ cell.className = 'bp-cell';
+ const fallback = manualOcupados.has(area) ? 'Ocupado' : (anotacoes[pos] || 'Vazio');
+ const texto = getAreaText(area, byArea, fallback);
+ cell.innerHTML = `${area} ${texto}
`;
+ row.appendChild(cell);
+ });
- const itens = parsed
- .filter((entry) => entry.meta.bloco === coluna && entry.meta.pos === i)
- .map((entry) => entry.item);
+ return row;
+ };
- let conteudo = `${areaNome} `;
- if (itens.length) {
- cell.classList.remove('vazio');
- cell.classList.add('ocupado');
+ const bRow = document.createElement('div');
+ bRow.className = 'bp-row bp-row-b';
+ Array.from({ length: 4 }).forEach(() => {
+ const empty = document.createElement('div');
+ empty.className = 'bp-cell bp-empty';
+ bRow.appendChild(empty);
+ });
- itens.forEach((item) => {
- const tipoClass = `material-${normalizeText(item.tipo).toLowerCase()}`;
- conteudo += `SKU: ${item.sku} ${item.paletes} pal (${item.tipo})
`;
- });
- }
+ bPosicoes.forEach((pos) => {
+ const areaD = `B${String(pos).padStart(2, '0')}D`;
+ const areaE = `B${String(pos).padStart(2, '0')}E`;
+ const textoD = getAreaText(areaD, byArea, manualOcupados.has(areaD) ? 'Ocupado' : 'Vazio');
+ const textoE = getAreaText(areaE, byArea, manualOcupados.has(areaE) ? 'Ocupado' : 'Vazio');
+ const cell = document.createElement('div');
+ cell.className = 'bp-cell';
+ const extra = pos === 1 ? 'Picking
' : '';
+ cell.innerHTML = `${areaD} / ${areaE} D: ${textoD}
E: ${textoE}
${extra}`;
+ bRow.appendChild(cell);
+ });
+
+ const blueprint = document.createElement('div');
+ blueprint.className = 'blueprint-wrap';
+ blueprint.appendChild(criarLinha('A', anotacoesA));
+ blueprint.appendChild(bRow);
+ blueprint.appendChild(criarLinha('C', anotacoesC));
+ el.layoutGrid.appendChild(blueprint);
- cell.innerHTML = conteudo;
- el.layoutGrid.appendChild(cell);
+ const parsed = cache.estoque
+ .map((item) => ({ item, meta: parseAreaForLayout(item.area) }))
+ .filter((entry) => entry.meta);
+
+ const parsedEstruturas = parsed.filter((entry) =>
+ (entry.meta.bloco === 'A' && entry.meta.pos <= 5) || entry.meta.bloco === 'TUNEL'
+ );
+
+ const totaisEstruturas = parsedEstruturas.reduce((acc, entry) => {
+ const chave = entry.meta.bloco === 'TUNEL' ? `TÚNEL ${entry.meta.pos}` : `A${entry.meta.pos}`;
+ acc[chave] = (acc[chave] || 0) + Number(entry.item.paletes || 0);
+ return acc;
+ }, {});
+
+ Object.entries(totaisEstruturas)
+ .sort(([a], [b]) => a.localeCompare(b, 'pt-BR', { numeric: true }))
+ .forEach(([area, caixas]) => {
+ const box = document.createElement('div');
+ box.className = 'estrutura-box';
+ box.innerHTML = `${area} ${caixas} caixas `;
+ el.estruturasGrid.appendChild(box);
});
+
+ if (!Object.keys(totaisEstruturas).length) {
+ el.estruturasGrid.innerHTML = 'Sem caixas cadastradas em Estruturas/Túnel.
';
}
+ renderSemanticLayout();
el.layoutContainer.classList.remove('hidden');
}
+
async function exportarLayoutPDF() {
if (!window.html2canvas || !window.jspdf) {
showFeedback('Bibliotecas de PDF não carregadas.', 'error');
@@ -461,16 +1993,25 @@ async function exportarLayoutPDF() {
}
-function exportPlanilhaEspelho() {
- const totaisEstoque = groupTotalBySku(cache.estoque);
+async function exportPlanilhaEspelho() {
+ if (supabaseClient) {
+ await loadAll();
+ }
+
+ const { rows: estoqueExportRows, missingSkus } = buildEstoqueExportRows();
+ const totaisEstoque = groupTotalBySku(cache.estoque, { excludeRetrabalho: true });
const totaisExpedido = groupTotalBySku(cache.movimentacoes);
exportWorkbook('planilha_espelho_wmss.xlsx', [
- { name: 'Estoque', data: cache.estoque },
+ { name: 'Estoque', data: estoqueExportRows },
{ name: 'Totais_SKU', data: totaisEstoque },
{ name: 'Movimentacoes', data: cache.movimentacoes },
{ name: 'Totais_Expedido_SKU', data: totaisExpedido }
]);
+
+ if (missingSkus.length) {
+ showFeedback(`Aviso: SKU(s) sem fardos por palete cadastrado: ${missingSkus.join(', ')}.`, 'error');
+ }
}
function maybeAutoExport() {
@@ -478,6 +2019,28 @@ function maybeAutoExport() {
exportPlanilhaEspelho();
}
+function buildEstoqueExportRows() {
+ const missing = new Set();
+ const rows = cache.estoque.map((row) => {
+ const chave = estoqueKey(row.area, row.sku, row.tipo);
+ const frac = fracionadoMap[chave];
+ const fpp = getFardosPorPalete(row.sku);
+ if (!fpp) missing.add(row.sku);
+ const paletesIncompletos = Number(frac?.paletes_incompletos || 0);
+ const fardosIncompletos = Number(frac?.fardos_incompletos || 0);
+ const paletesContabilizados = Math.max(0, Number(row.paletes) - paletesIncompletos);
+ const fardosTotaisBlocado = fpp ? (paletesContabilizados * fpp) + fardosIncompletos : null;
+ return {
+ ...row,
+ paletes_contabilizados: paletesContabilizados,
+ paletes_incompletos: paletesIncompletos,
+ fardos_incompletos: fardosIncompletos,
+ fardos_totais_blocado: fardosTotaisBlocado
+ };
+ });
+ return { rows, missingSkus: [...missing] };
+}
+
function exportWorkbook(fileName, sheets) {
const wb = XLSX.utils.book_new();
sheets.forEach(({ name, data }) => {
@@ -489,22 +2052,26 @@ function exportWorkbook(fileName, sheets) {
function setupExports() {
el.exportCadastroBtn.addEventListener('click', () => {
- const totais = groupTotalBySku(cache.estoque);
+ const { rows: estoqueExportRows, missingSkus } = buildEstoqueExportRows();
+ const totais = groupTotalBySku(cache.estoque, { excludeRetrabalho: true });
exportWorkbook('cadastro_estoque.xlsx', [
- { name: 'Estoque', data: cache.estoque },
+ { name: 'Estoque', data: estoqueExportRows },
{ name: 'Totais_SKU', data: totais }
]);
+ if (missingSkus.length) showFeedback(`Aviso: SKU(s) sem fardos por palete: ${missingSkus.join(', ')}.`, 'error');
});
el.exportConsultaBtn.addEventListener('click', () => {
- const totais = groupTotalBySku(cache.estoque);
+ const { rows: estoqueExportRows, missingSkus } = buildEstoqueExportRows();
+ const totais = groupTotalBySku(cache.estoque, { excludeRetrabalho: true });
exportWorkbook('consulta_estoque.xlsx', [
- { name: 'Consulta_Areas', data: cache.estoque },
+ { name: 'Consulta_Areas', data: estoqueExportRows },
{ name: 'Totais_SKU', data: totais }
]);
+ if (missingSkus.length) showFeedback(`Aviso: SKU(s) sem fardos por palete: ${missingSkus.join(', ')}.`, 'error');
});
- el.exportExpedicaoBtn.addEventListener('click', () => {
+ el.exportExpedicaoBtn?.addEventListener('click', () => {
const totaisExpedido = groupTotalBySku(cache.movimentacoes);
exportWorkbook('expedicao.xlsx', [
{ name: 'Expedicoes', data: cache.movimentacoes },
@@ -512,9 +2079,13 @@ function setupExports() {
]);
});
- el.exportEspelhoBtn?.addEventListener('click', () => {
- exportPlanilhaEspelho();
- showFeedback('Planilha espelho gerada com sucesso.');
+ el.exportEspelhoBtn?.addEventListener('click', async () => {
+ try {
+ await exportPlanilhaEspelho();
+ showFeedback('Planilha espelho gerada com sucesso.');
+ } catch (error) {
+ showFeedback(`Erro ao gerar planilha espelho: ${error.message}`, 'error');
+ }
});
}
@@ -529,20 +2100,112 @@ function setupTabs() {
});
}
+function setupPlanejamento() {
+ el.planejamentoForm?.addEventListener('submit', async (event) => {
+ event.preventDefault();
+ let itens = parseIncomingForecast(el.previsaoEntrada?.value);
+ const file = el.planejamentoFile?.files?.[0];
+ if (file) {
+ const buffer = await file.arrayBuffer();
+ const workbook = XLSX.read(buffer, { type: 'array' });
+ const sheet = workbook.Sheets[workbook.SheetNames[0]];
+ const rows = XLSX.utils.sheet_to_json(sheet, { defval: '' });
+ itens = parseIncomingForecastRows(rows);
+ }
+ const totalPrevisto = itens.reduce((acc, item) => acc + item.paletes, 0);
+ renderPlanejamentoTable(getPlanejamentoOcupacaoAtual(), totalPrevisto);
+ });
+}
+
+function exportConsultaResumoPDF() {
+ if (!window.jspdf) return showFeedback('Biblioteca de PDF não carregada.', 'error');
+ const { jsPDF } = window.jspdf;
+ const pdf = new jsPDF('portrait');
+ let y = 12;
+ pdf.setFontSize(13);
+ pdf.text('Resumo Consulta / Ocupação', 10, y);
+ y += 8;
+ pdf.setFontSize(10);
+ const resumo = getPaletesResumoPorSetor();
+ pdf.text(`Principal: ${resumo.principal} paletes`, 10, y); y += 6;
+ pdf.text(`Tissue: ${resumo.tissue} paletes`, 10, y); y += 6;
+ pdf.text(`TDD: ${resumo.ttd} paletes`, 10, y); y += 6;
+ pdf.text(`Lonil: ${resumo.lonil} paletes`, 10, y); y += 8;
+ const totals = groupTotalBySku(filterConsultaRows(cache.estoque), { excludeRetrabalho: true }).slice(0, 20);
+ pdf.text('SKUs (top 20 no filtro):', 10, y); y += 6;
+ totals.forEach((item) => {
+ pdf.text(`SKU ${item.sku}: ${item.total_paletes} paletes`, 10, y);
+ y += 5;
+ if (y > 280) { pdf.addPage(); y = 12; }
+ });
+ pdf.save('resumo_consulta_ocupacao.pdf');
+}
+
+function setupConsulta() {
+ el.consultaFilterForm?.addEventListener('change', renderConsulta);
+ el.mapaAreaSelect?.addEventListener('change', renderConsultaMapaHint);
+ el.mapaPosicaoInput?.addEventListener('input', renderConsultaMapaHint);
+ el.exportConsultaResumoPdfBtn?.addEventListener('click', exportConsultaResumoPDF);
+ document.querySelectorAll('.consulta-subnav-btn').forEach((btn) => {
+ btn.addEventListener('click', () => {
+ document.querySelectorAll('.consulta-subnav-btn').forEach((b) => b.classList.remove('active'));
+ document.querySelectorAll('.consulta-view').forEach((v) => v.classList.remove('active'));
+ btn.classList.add('active');
+ const viewMap = {
+ geral: 'consultaViewGeral',
+ chao: 'consultaViewChao',
+ 'sku-area': 'consultaViewSkuArea',
+ mapa: 'consultaViewMapa'
+ };
+ document.getElementById(viewMap[btn.dataset.consultaView])?.classList.add('active');
+ });
+ });
+}
+
+function setupOcupacao() {
+ el.ocupacaoForm?.addEventListener('submit', (event) => {
+ event.preventDefault();
+ renderOcupacao();
+ });
+}
+
function init() {
setupTabs();
setupExports();
- el.connectBtn.addEventListener('click', createClient);
+ setupConsulta();
+ setupPlanejamento();
+ setupOcupacao();
+ setupContagem();
+ renderTurnoHistory();
+ el.turnoForm?.addEventListener('submit', handleTurnoSubmit);
+ el.exportTurnoExcelBtn?.addEventListener('click', exportTurnoResultadoExcel);
+ el.exportTurnoPdfBtn?.addEventListener('click', exportTurnoResultadoPDF);
+ el.exportOcupacaoPdfBtn?.addEventListener('click', exportOcupacaoResumoPDF);
+ el.paleteIncompletoToggle?.addEventListener('change', (event) => {
+ el.paleteIncompletoFields?.classList.toggle('hidden', !event.target.checked);
+ });
el.estoqueForm.addEventListener('submit', handleEstoqueSubmit);
el.produtoForm.addEventListener('submit', handleProdutoSubmit);
- el.expedicaoForm.addEventListener('submit', handleExpedicaoSubmit);
- el.importForm.addEventListener('submit', handleImportSubmit);
+ el.expedicaoForm?.addEventListener('submit', handleExpedicaoSubmit);
+ el.importForm?.addEventListener('submit', handleImportSubmit);
+ el.adminPasteForm?.addEventListener('submit', handleAdminPasteSubmit);
+ el.selectImportBtn?.addEventListener('click', () => el.importFile?.click());
+ el.importFile?.addEventListener('change', () => {
+ const name = el.importFile.files?.[0]?.name || 'Nenhum arquivo selecionado';
+ if (el.importFileName) el.importFileName.textContent = name;
+ });
el.visualizarLayoutBtn?.addEventListener('click', gerarLayoutVisual);
el.exportLayoutPdfBtn?.addEventListener('click', exportarLayoutPDF);
el.fecharLayoutBtn?.addEventListener('click', () => el.layoutContainer.classList.add('hidden'));
+ el.themeToggleBtn?.addEventListener('click', () => {
+ const isDark = !document.body.classList.contains('dark');
+ document.body.classList.toggle('dark', isDark);
+ storageSet('wmss_theme', isDark ? 'dark' : 'light');
+ el.themeToggleBtn.textContent = isDark ? '☀️ Modo claro' : '🌙 Modo escuro';
+ });
el.autoExportToggle?.addEventListener('change', (event) => {
const enabled = event.target.checked;
- localStorage.setItem('wmss_auto_export', enabled ? '1' : '0');
+ storageSet('wmss_auto_export', enabled ? '1' : '0');
showFeedback(enabled ? 'Auto planilha ativado.' : 'Auto planilha desativado.');
});
createClient();
diff --git a/index.html b/index.html
index 32b4c95..8fa0fc1 100644
--- a/index.html
+++ b/index.html
@@ -14,29 +14,19 @@
-
+
Cadastro
Consulta
- Expedição
+ Conferência de turno
+ Planejamento
+ Ocupação
+ Contagem
@@ -53,6 +43,15 @@ Cadastrar produto em área
Paletes
+ Fardos por palete (SKU novo)
+
+
+ Possui palete(s) incompleto(s)
+
+
+ Qtd de paletes incompletos
+ Total de fardos nos incompletos
+
Salvar / Atualizar
@@ -69,25 +68,31 @@ Cadastro de produto (SKU)
-
Importar planilha (incluir / apagar posições)
-
- Colunas esperadas na planilha: area , sku , tipo ,
- paletes e opcional acao .
-
- Se acao for APAGAR /EXCLUIR /DELETE , o registro será removido.
- Caso contrário, será feito incluir/atualizar.
-
-
+
Importar planilha (cadastro)
+
Função desativada nesta tela. A operação passa a usar planilha fixa + Contagem/Conferência.
+
+
+
ADM: atualizar por colar planilha
+
Cole aqui o conteúdo copiado do Excel (com cabeçalho). Aceita colunas area , sku , tipo , paletes e opcional acao . Use para atualizar quando o upload de arquivo falhar.
+
+
+
Planilha espelho automática
Ative para baixar automaticamente uma planilha atualizada sempre que houver alteração no sistema.
@@ -120,16 +125,79 @@
Estoque por área
Consulta: onde estão os produtos
+
+ Geral
+ Chão
+ SKU por Área
+ Mapa
+
+
+ Depósito
+
+ Todos
+ Principal
+ Tissue
+ TDD Principal
+ Lonil
+ Estrutura
+
+
+ Lado
+
+ Direita + Esquerda
+ → Direita
+ ← Esquerda
+
+
+
+
- Área SKU Tipo Quantidade
+ Depósito Lado Endereço SKU Tipo Quantidade
+
+
+
+
+ Tipo de chão Lado Endereço SKU Paletes
+
+
+
+
+
+
+
+ Área/Depósito SKU Paletes Fardos
+
+
+
+
+
+
+ Área do mapa
+
+ Principal
+ Tissue
+ TDD
+ Lonil
+
+
+ Posição atual
+
+
+
+
+
+
+ Exportar resumo (PDF)
+
Totais por SKU (todas as áreas)
+
Observação: a área C01 (retrabalho) não entra na contagem de totais.
SKU Total Paletes
@@ -138,47 +206,193 @@ Totais por SKU (todas as áreas)
Exportar planilha (consulta + totais SKU)
+
+
+
Sobras B01 (esquerdo/direito)
+
Lista rápida dos SKUs misturados em B01, B01E e B01D.
+
+
+
+
+
Histórico de snapshots (últimos 3)
+
+
+
+
+
Saída calculada por SKU
+
+
+ SKU Tipo Paletes saíram Fardos estimados
+
+
+
+
+
+
+
+
+
Planejamento de recebimento (NF)
+
Informe previsão de chegada por SKU para estimar ocupação e blocados que precisam ser desfeitos.
+
+
+ Previsão de chegada (uma linha por SKU: SKU,PALETES)
+
+
+
+ Ou subir planilha de previsão (.xlsx/.xls/.csv) com colunas sku e paletes
+
+
+
+ Calcular planejamento
+
+
+
+
+
+ Bloco Capacidade Ocupado Livre Após NF Ação
+
+
+
+
+
+
+
+
+
Cálculo de ocupação do galpão
+
A ocupação agora prioriza a Contagem manual (A/B/C + Estruturas + Tissue + Lonil + TTD). Se não houver contagem preenchida, usa o sistema.
+
+ Atualizar ocupação Exportar resumo (PDF)
+
+
+
+
+
+
+ DATA Cap G1 OCUP Terceiros G1 Disponivel G1 OCUPADO JSL G1 DISPONIVEL G1
+ DATA Cap G2 OCUP Terceiros G2 Disponivel G2 OCUPADO JSL G2 DISPONIVEL G2
+ DATA Cap G3 OCUP Terceiros G3 Disponivel G3 OCUPADO JSL G3 DISPONIVEL G3
+ DATA Cap G4 OCUP Terceiros G4 Disponivel G4 JSL OCUPADO JSL G4 DISPONIVEL G4
+ Capacidade Fardos Qtd Fardos
+
+
+
+
+
+
+
+
+
+
+
Contagem por rua/estrutura
+
Selecione a área. Use o botão + para adicionar mais de 1 SKU na mesma posição. Para importar planilha, o formato recomendado é: sku , deposito (principal/estruturas/lonil/tissue/ttd), quadrante (A01, B02D, chão direito/esquerdo), qtd plt e tipo plt (PL2/PBR).
+
+ Área de contagem
+
+ Rua A
+ Rua B
+ Rua C
+ Estrutura
+ Tissue
+ TTD
+ Lonil
- Quantidade para expedir
- Expedir / Dar baixa
+ Lado (Rua B / Tissue / TTD / Lonil)
+
+ Direito + Esquerdo
+ Somente direito
+ Somente esquerdo
+
+
+ Importar planilha para pré-preenchimento
+
+
+
+ Carregar posições
+ Preencher pela planilha
+ Estimar pela conferência
+ Calcular
+ Atualizar consulta (confirmados)
+ Exportar Excel (todas as áreas)
+
+
+
+
+
+
+ + Posição SKU
+ Confirmar?
+ Prof. 1ª Larg. 1ª
+ 2ª camada? Prof. 2ª Larg. 2ª
+ 3ª camada? Paletes 3ª
+ Fardos faltando (palete não inteiro)
+ Usar total editável? Total editável Paletes Fardos
+
+
+
+
+
-
Histórico de expedição
+
Resumo por SKU
-
- Data SKU Tipo Qtd expedida
+
- Exportar planilha (expedidas + totais SKU expedido)
+
+
Estruturas / Túnel (somente caixas)
+
+
+
+
Mapa semântico (somente áreas físicas)
+
+
diff --git a/styles.css b/styles.css
index 34a690a..b72b840 100644
--- a/styles.css
+++ b/styles.css
@@ -1,19 +1,48 @@
:root {
font-family: Inter, Arial, sans-serif;
- color: #182028;
- background: #f5f7fa;
+}
+
+:root {
+ --bg: #f5f7fa;
+ --text: #182028;
+ --card: #ffffff;
+ --header: #1f4d8f;
+ --muted: #4a5563;
+ --border: #ccd4dd;
+ --table-border: #e1e6eb;
+ --btn-secondary: #51606f;
+ --tab: #8c96a3;
+}
+
+body.dark {
+ --bg: #10151c;
+ --text: #e6edf3;
+ --card: #18202a;
+ --header: #0f2747;
+ --muted: #b8c2cf;
+ --border: #334152;
+ --table-border: #2a3442;
+ --btn-secondary: #3f4c5b;
+ --tab: #4f5c6c;
}
* { box-sizing: border-box; }
body {
margin: 0;
+ background: var(--bg);
+ color: var(--text);
}
header {
- background: #1f4d8f;
+ background: var(--header);
color: #fff;
padding: 1.5rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.75rem;
}
main {
@@ -23,7 +52,7 @@ main {
}
.card {
- background: #fff;
+ background: var(--card);
border-radius: 10px;
padding: 1rem;
margin-bottom: 1rem;
@@ -54,10 +83,25 @@ label {
input, select, button {
padding: 0.55rem 0.7rem;
border-radius: 8px;
- border: 1px solid #ccd4dd;
+ border: 1px solid var(--border);
font-size: 0.95rem;
}
+textarea {
+ padding: 0.55rem 0.7rem;
+ border-radius: 8px;
+ border: 1px solid var(--border);
+ font-size: 0.95rem;
+ font-family: inherit;
+}
+
+.config-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.8rem;
+}
+
button {
background: #1f4d8f;
color: #fff;
@@ -65,7 +109,7 @@ button {
cursor: pointer;
}
-button.secondary { background: #51606f; }
+button.secondary { background: var(--btn-secondary); }
button.danger { background: #b93636; }
button.edit-btn { background: #2a7f46; margin-right: 0.35rem; }
button.delete-btn { background: #b93636; }
@@ -73,6 +117,8 @@ button.delete-btn { background: #b93636; }
.actions {
display: flex;
align-items: flex-end;
+ gap: 0.55rem;
+ flex-wrap: wrap;
}
.tabs {
@@ -81,7 +127,7 @@ button.delete-btn { background: #b93636; }
margin-bottom: 1rem;
}
-.tab-btn { background: #8c96a3; }
+.tab-btn { background: var(--tab); }
.tab-btn.active { background: #1f4d8f; }
.tab-content { display: none; }
@@ -91,13 +137,20 @@ button.delete-btn { background: #b93636; }
overflow-x: auto;
}
+.consulta-view { display: none; }
+.consulta-view.active { display: block; }
+
+.ocupacao-linha-reta-wrapper table {
+ min-width: 1900px;
+}
+
table {
width: 100%;
border-collapse: collapse;
}
th, td {
- border-bottom: 1px solid #e1e6eb;
+ border-bottom: 1px solid var(--table-border);
padding: 0.55rem;
text-align: left;
}
@@ -113,11 +166,21 @@ th, td {
.helper-text {
margin-top: 0;
- color: #4a5563;
+ color: var(--muted);
font-size: 0.9rem;
line-height: 1.4;
}
+
+.file-picker-label input[type="file"] {
+ display: none;
+}
+
+.file-name {
+ color: #51606f;
+ font-size: 0.85rem;
+}
+
.layout-actions {
display: flex;
gap: 0.6rem;
@@ -130,7 +193,7 @@ th, td {
.layout-container {
position: fixed;
inset: 0;
- background: #f4f6f8;
+ background: var(--bg);
overflow: auto;
padding: 1rem;
z-index: 1000;
@@ -141,7 +204,7 @@ th, td {
justify-content: space-between;
align-items: center;
gap: 0.8rem;
- background: #fff;
+ background: var(--card);
border-radius: 8px;
padding: 0.8rem;
margin-bottom: 1rem;
@@ -155,29 +218,42 @@ th, td {
.hidden { display: none !important; }
.layout-grid {
- display: grid;
- grid-template-columns: 120px repeat(4, minmax(160px, 1fr)) 120px;
- gap: 8px;
+ overflow: auto;
}
-.celula {
- border: 2px solid #444;
- min-height: 78px;
- padding: 6px;
- font-size: 12px;
- background: #f5f5f5;
+.blueprint-wrap {
+ min-width: 1900px;
+ display: grid;
+ gap: 0.45rem;
+ background: var(--card);
+ padding: 0.5rem;
+ border-radius: 8px;
}
-.vazio { background: #e0e0e0; }
-.ocupado { background: #90caf9; }
+.bp-row {
+ display: grid;
+ grid-template-columns: repeat(26, minmax(68px, 1fr));
+ gap: 2px;
+}
-.sku {
+.bp-cell {
+ border: 1px dotted #666;
+ background: #f8f8f8;
+ min-height: 68px;
+ padding: 2px 4px;
font-size: 11px;
- margin-top: 4px;
- border-radius: 4px;
- padding: 4px;
- color: #fff;
- font-weight: 600;
+ line-height: 1.25;
+}
+
+.bp-cell strong {
+ display: block;
+ font-size: 12px;
+ margin-bottom: 2px;
+}
+
+.bp-empty {
+ background: transparent;
+ border: 0;
}
.material-pl2 { background: #1f4d8f; }
@@ -197,3 +273,154 @@ th, td {
width: 18px;
height: 18px;
}
+
+
+.estruturas-section {
+ margin-top: 1rem;
+ background: #fff;
+ border-radius: 8px;
+ padding: 0.8rem;
+}
+
+.estruturas-section h3 {
+ margin: 0 0 0.6rem;
+}
+
+.estruturas-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+ gap: 0.6rem;
+}
+
+.estrutura-box {
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 0.55rem;
+ display: grid;
+ gap: 0.25rem;
+ background: #f9fbfd;
+}
+
+.estrutura-box span {
+ color: #3e4a58;
+ font-weight: 600;
+}
+
+.semantico-section {
+ margin-top: 1rem;
+ background: #fff;
+ border-radius: 8px;
+ padding: 0.8rem;
+}
+
+.semantico-section h3 {
+ margin: 0 0 0.6rem;
+}
+
+.semantic-layout {
+ display: grid;
+ gap: 0.7rem;
+}
+
+.sem-row {
+ display: grid;
+ gap: 0.6rem;
+}
+
+.sem-top {
+ grid-template-columns: 1fr 2fr 1fr;
+}
+
+.sem-main {
+ grid-template-columns: repeat(4, minmax(120px, 1fr));
+}
+
+.sem-tenda,
+.sem-rua-top,
+.sem-bloco,
+.sem-servicos {
+ border: 1px dashed #90a0b0;
+ border-radius: 8px;
+ padding: 0.6rem;
+ background: #f8fafc;
+ text-align: center;
+ font-weight: 600;
+}
+
+.sem-servicos {
+ display: grid;
+ gap: 0.35rem;
+}
+
+.sem-foot {
+ color: #3e4a58;
+}
+
+#contagemTable input[type="number"],
+#contagemTable input[type="text"],
+#contagemTable input:not([type]) {
+ width: 100%;
+ min-width: 72px;
+}
+
+#contagemTable input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+}
+
+
+
+#contagemTable .contagem-sku-input {
+ min-width: 240px;
+}
+
+#contagemTable .contagem-collapsed {
+ font-size: 0.78rem;
+ opacity: 0.75;
+ white-space: nowrap;
+}
+
+#contagemSideLabel.hidden { display: none; }
+body.dark .bp-cell { background: #1f2a36; border-color: #5b6776; }
+body.dark .bp-empty { background: transparent; }
+
+
+body.dark .card,
+body.dark .layout-header,
+body.dark .semantico-section,
+body.dark .estrutura-box,
+body.dark .sem-tenda,
+body.dark .sem-rua-top,
+body.dark .sem-bloco,
+body.dark .sem-servicos {
+ background: var(--card);
+ color: var(--text);
+}
+
+body.dark table { color: var(--text); }
+body.dark input, body.dark select, body.dark textarea {
+ background: #0f1721;
+ color: var(--text);
+}
+
+
+.contagem-plus-btn,
+.contagem-remove-btn {
+ min-width: 32px;
+ padding: 0.35rem 0.5rem;
+ line-height: 1;
+}
+
+.contagem-remove-btn {
+ background: #b93636;
+}
+
+.contagem-posicao-sub {
+ opacity: 0.75;
+}
+
+#adminPasteInput {
+ width: 100%;
+ min-height: 170px;
+ resize: vertical;
+}
From 6cdcf71ae554b0e0494c40f296c89178dfc6971e Mon Sep 17 00:00:00 2001
From: hhhthiti <109553200+hhhthiti@users.noreply.github.com>
Date: Tue, 10 Mar 2026 07:18:51 -0300
Subject: [PATCH 2/5] Add URL-only compact spreadsheet-to-PDF print page
---
impressao-planilha.html | 186 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 186 insertions(+)
create mode 100644 impressao-planilha.html
diff --git a/impressao-planilha.html b/impressao-planilha.html
new file mode 100644
index 0000000..46b2449
--- /dev/null
+++ b/impressao-planilha.html
@@ -0,0 +1,186 @@
+
+
+
+
+
+ WMSS • Impressão compacta de planilha
+
+
+
+
+
+
+
+
Impressão compacta de planilha
+
+ Página exclusiva por URL para reduzir gasto de folha. Use colunas sku, endereco/área e quantidade/paletes.
+
+
+
+ Gerar PDF otimizado (A4)
+ Limpar
+
+
+
+
+
+
Prévia dos dados extraídos
+
Nenhum arquivo carregado.
+
+
+ SKU Endereço Quantidade
+
+
+ Sem dados.
+
+
+
+
+
+
+
+
From c8a5775ccb7d885efa156273cc033903d73baeb6 Mon Sep 17 00:00:00 2001
From: hhhthiti <109553200+hhhthiti@users.noreply.github.com>
Date: Tue, 10 Mar 2026 07:28:39 -0300
Subject: [PATCH 3/5] Support deposito+lados-only rows in compact print
extractor
---
impressao-planilha.html | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/impressao-planilha.html b/impressao-planilha.html
index 46b2449..f412708 100644
--- a/impressao-planilha.html
+++ b/impressao-planilha.html
@@ -29,7 +29,7 @@
Impressão compacta de planilha
- Página exclusiva por URL para reduzir gasto de folha. Use colunas sku, endereco/área e quantidade/paletes.
+ Página exclusiva por URL para reduzir gasto de folha. Use colunas sku e quantidade/paletes. O endereço pode vir como endereco/área/quadrante ou somente lado (direito/esquerdo) + deposito (lonil/tissue/ttd).
@@ -72,7 +72,17 @@
Prévia dos dados extraídos
function obterRegistro(raw) {
const row = Object.fromEntries(Object.entries(raw).map(([k, v]) => [normalizarChave(k), v]));
const sku = String(row.sku ?? row.codsku ?? row.cod_sku ?? '').trim();
- const endereco = String(row.endereco ?? row.endereço ?? row.area ?? row['área'] ?? row.quadrante ?? '').trim();
+ const deposito = String(row.deposito ?? row.setor ?? row.deposito_tipo ?? '').trim().toUpperCase();
+ const ladoRaw = String(row.lado ?? row.endereco ?? row.endereço ?? row.quadrante ?? row.area ?? row['área'] ?? '').trim().toUpperCase();
+ const lado = ladoRaw.includes('ESQUER') || ladoRaw === 'E' ? 'E'
+ : (ladoRaw.includes('DIREIT') || ladoRaw === 'D' ? 'D' : '');
+ let endereco = String(row.endereco ?? row.endereço ?? row.area ?? row['área'] ?? row.quadrante ?? '').trim();
+ if ((!endereco || ['D', 'E', 'DIREITO', 'ESQUERDO'].includes(endereco.toUpperCase())) && deposito && lado) {
+ const depNorm = deposito.includes('LONIL') ? 'LONIL'
+ : (deposito.includes('TISSUE') ? 'TISSUE'
+ : (deposito.includes('TTD') || deposito.includes('TDD') ? 'TTD' : ''));
+ if (depNorm) endereco = `${depNorm} ${lado === 'D' ? '→ DIREITA' : '← ESQUERDA'}`;
+ }
const quantidade = Number(row.quantidade ?? row.paletes ?? row.qtd ?? row.qtd_plt ?? row['qtd plt']);
if (!sku || !endereco || !Number.isFinite(quantidade) || quantidade <= 0) return null;
return { sku, endereco, quantidade };
From 8ef627a5aa87a9bc3ce61501137fee3b2a609c8f Mon Sep 17 00:00:00 2001
From: hhhthiti <109553200+hhhthiti@users.noreply.github.com>
Date: Tue, 10 Mar 2026 07:39:52 -0300
Subject: [PATCH 4/5] Use compact deposito.lado labels in print extractor
---
impressao-planilha.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/impressao-planilha.html b/impressao-planilha.html
index f412708..3d1960a 100644
--- a/impressao-planilha.html
+++ b/impressao-planilha.html
@@ -78,10 +78,10 @@
Prévia dos dados extraídos
: (ladoRaw.includes('DIREIT') || ladoRaw === 'D' ? 'D' : '');
let endereco = String(row.endereco ?? row.endereço ?? row.area ?? row['área'] ?? row.quadrante ?? '').trim();
if ((!endereco || ['D', 'E', 'DIREITO', 'ESQUERDO'].includes(endereco.toUpperCase())) && deposito && lado) {
- const depNorm = deposito.includes('LONIL') ? 'LONIL'
- : (deposito.includes('TISSUE') ? 'TISSUE'
- : (deposito.includes('TTD') || deposito.includes('TDD') ? 'TTD' : ''));
- if (depNorm) endereco = `${depNorm} ${lado === 'D' ? '→ DIREITA' : '← ESQUERDA'}`;
+ const depNorm = deposito.includes('LONIL') ? 'lonil'
+ : (deposito.includes('TISSUE') ? 'tissue'
+ : (deposito.includes('TTD') || deposito.includes('TDD') ? 'ttd' : ''));
+ if (depNorm) endereco = `${depNorm}.${lado.toLowerCase()}`;
}
const quantidade = Number(row.quantidade ?? row.paletes ?? row.qtd ?? row.qtd_plt ?? row['qtd plt']);
if (!sku || !endereco || !Number.isFinite(quantidade) || quantidade <= 0) return null;
From a30d3f2275b2d0a9091a9d3f930964205be9a492 Mon Sep 17 00:00:00 2001
From: hhhthiti <109553200+hhhthiti@users.noreply.github.com>
Date: Wed, 11 Mar 2026 16:50:20 -0300
Subject: [PATCH 5/5] Refine contagem import reset and checkbox-driven detail
entry
---
app.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++-------
index.html | 6 ++++++
2 files changed, 56 insertions(+), 7 deletions(-)
diff --git a/app.js b/app.js
index 7eb2bc2..bd2740c 100644
--- a/app.js
+++ b/app.js
@@ -80,8 +80,10 @@ const el = {
contagemLoadBtn: document.getElementById('contagemLoadBtn'),
contagemImportBtn: document.getElementById('contagemImportBtn'),
contagemEstimateBtn: document.getElementById('contagemEstimateBtn'),
+ contagemClearChecksBtn: document.getElementById('contagemClearChecksBtn'),
contagemApplyBtn: document.getElementById('contagemApplyBtn'),
contagemFile: document.getElementById('contagemFile'),
+ contagemImportResetToggle: document.getElementById('contagemImportResetToggle'),
contagemExportBtn: document.getElementById('contagemExportBtn'),
contagemStatus: document.getElementById('contagemStatus'),
contagemBody: document.querySelector('#contagemTable tbody'),
@@ -865,6 +867,7 @@ function getContagemEntries(posicao) {
fardosFaltando: Number(item.fardosFaltando || 0),
totalManual: Number(item.totalManual || 0),
usarTotalManual: Boolean(item.usarTotalManual),
+ blocadoPresente: typeof item.blocadoPresente === 'boolean' ? item.blocadoPresente : true,
confirmada: Boolean(item.confirmada),
tipoPlt: normalizeText(item.tipoPlt)
}));
@@ -883,6 +886,7 @@ function createEmptyContagemEntry() {
fardosFaltando: 0,
totalManual: 0,
usarTotalManual: false,
+ blocadoPresente: true,
confirmada: false,
tipoPlt: ''
};
@@ -913,6 +917,15 @@ function removeContagemEntry(posicao, idx) {
function computeContagem(posicao, entry, scope = el.contagemScope?.value) {
const st = { ...createEmptyContagemEntry(), ...(entry || {}) };
const isEstrutura = normalizeText(scope) === 'ESTRUTURA';
+ const ativo = st.blocadoPresente !== false;
+ if (!ativo) {
+ const paletes = st.usarTotalManual && Number(st.totalManual) > 0 ? Number(st.totalManual) : 0;
+ const fpp = getFardosPorPalete(st.sku);
+ const faltando = Math.max(0, st.fardosFaltando || 0);
+ const fardosBrutos = fpp ? paletes * fpp : null;
+ const fardos = Number.isFinite(fardosBrutos) ? Math.max(0, fardosBrutos - faltando) : null;
+ return { ...st, posicao, paletes, paletesCalculados: 0, fardos };
+ }
const basePrimeira = Math.max(0, st.profundidade1 * st.largura1);
const baseSegunda = st.segundaCamada ? Math.max(0, st.profundidade2 * st.largura2) : 0;
const base = isEstrutura ? (normalizeText(st.sku) ? 1 : 0) : basePrimeira + baseSegunda;
@@ -1099,6 +1112,11 @@ async function importContagemFromPlanilha() {
return;
}
+ if (el.contagemImportResetToggle?.checked) {
+ contagemMap = {};
+ storageSet('wmss_contagem_map', JSON.stringify(contagemMap));
+ }
+
const grouped = validRows.reduce((acc, row) => {
const area = normalizeAreaCode(row.area);
if (!acc[area]) acc[area] = [];
@@ -1256,13 +1274,14 @@ function renderContagemTable() {
${posLabel}
-
${isEstrutura ? '- ' : ` `}
-
${isEstrutura ? '- ' : ` `}
-
${isEstrutura ? '- ' : ` `}
-
${isEstrutura ? '- ' : (entry.segundaCamada ? ` ` : 'marque 2ª camada ')}
-
${isEstrutura ? '- ' : (entry.segundaCamada ? ` ` : 'marque 2ª camada ')}
-
-
+
+
${isEstrutura || !entry.blocadoPresente ? '- ' : ` `}
+
${isEstrutura || !entry.blocadoPresente ? '- ' : ` `}
+
${isEstrutura || !entry.blocadoPresente ? '- ' : ` `}
+
${isEstrutura || !entry.blocadoPresente ? '- ' : (entry.segundaCamada ? ` ` : 'marque 2ª camada ')}
+
${isEstrutura || !entry.blocadoPresente ? '- ' : (entry.segundaCamada ? ` ` : 'marque 2ª camada ')}
+
${!entry.blocadoPresente ? '- ' : ` `}
+
${!entry.blocadoPresente ? '- ' : ` `}
@@ -1305,6 +1324,15 @@ function renderContagemTable() {
current.profundidade2 = 0;
current.largura2 = 0;
}
+ if (field === 'blocadoPresente' && !event.target.checked) {
+ current.profundidade1 = 0;
+ current.largura1 = 0;
+ current.segundaCamada = false;
+ current.profundidade2 = 0;
+ current.largura2 = 0;
+ current.terceiraCamada = false;
+ current.paletesTerceira = 0;
+ }
if (field === 'terceiraCamada' && !event.target.checked) current.paletesTerceira = 0;
if (field === 'usarTotalManual' && !event.target.checked) current.totalManual = 0;
entries[idx] = current;
@@ -1385,6 +1413,21 @@ function setupContagem() {
setStatus(el.contagemStatus, `Posições carregadas para preenchimento. SKU(s) sugeridos em ${preenchidas} posição(ões). Estimativa da conferência aplicada em ${estimadas} linha(s).`, 'success');
});
el.contagemImportBtn?.addEventListener('click', importContagemFromPlanilha);
+ el.contagemClearChecksBtn?.addEventListener('click', () => {
+ Object.keys(contagemMap || {}).forEach((posicao) => {
+ const entries = getContagemEntries(posicao).map((entry) => ({
+ ...entry,
+ confirmada: false,
+ blocadoPresente: true,
+ usarTotalManual: false,
+ segundaCamada: false,
+ terceiraCamada: false
+ }));
+ saveContagemEntries(posicao, entries);
+ });
+ renderContagemTable();
+ setStatus(el.contagemStatus, 'Checkboxes limpos para iniciar novo turno.', 'success');
+ });
el.contagemEstimateBtn?.addEventListener('click', () => {
const scope = el.contagemScope?.value;
const side = el.contagemSide?.value || 'ALL';
diff --git a/index.html b/index.html
index 8fa0fc1..022313c 100644
--- a/index.html
+++ b/index.html
@@ -337,10 +337,15 @@
Contagem por rua/estrutura
Importar planilha para pré-preenchimento
+
+
+ Limpar contagem anterior ao importar planilha
+
Carregar posições
Preencher pela planilha
Estimar pela conferência
+ Limpar checkboxes (novo turno)
Calcular
Atualizar consulta (confirmados)
Exportar Excel (todas as áreas)
@@ -353,6 +358,7 @@
Contagem por rua/estrutura
+ Posição SKU
Confirmar?
+ Blocado no local?
Prof. 1ª Larg. 1ª
2ª camada? Prof. 2ª Larg. 2ª
3ª camada? Paletes 3ª