From b4e51eb8ebbcfabf0f9a88f7587e0b72c243bcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Tue, 21 Jul 2026 13:56:10 -0300 Subject: [PATCH] Refine purchase needs setup queue --- src/pages/Supplies.tsx | 296 ++++++++++++++++++++++++++++------------- 1 file changed, 205 insertions(+), 91 deletions(-) diff --git a/src/pages/Supplies.tsx b/src/pages/Supplies.tsx index 0682314..02412f5 100644 --- a/src/pages/Supplies.tsx +++ b/src/pages/Supplies.tsx @@ -1781,6 +1781,26 @@ const summarizePurchaseNeeds = (needs: SupplyPurchaseNeed[]) => { return summaries.length ? summaries.join(' + ') : '0 kg'; }; +const getNeedDisplayName = (need: SupplyPurchaseNeed) => ( + need.material.replace(/^Cadastrar (consumo|rendimento):\s*/i, '') +); + +const getNeedPendingLabel = (need: SupplyPurchaseNeed) => { + if (!need.missingReference) return purchaseStatusLabels[need.status]; + return /^Cadastrar rendimento:/i.test(need.material) ? 'Rendimento' : 'Referência'; +}; + +const sumNeedProducts = ( + need: SupplyPurchaseNeed, + field: 'suggestedQuantity' | 'quantitySold' | 'stockQuantity' +) => ( + (need.products || []).reduce((total, product) => total + Number(product[field] || 0), 0) +); + +const countImpactedSkus = (needs: SupplyPurchaseNeed[]) => ( + new Set(needs.flatMap(need => (need.products || []).map(product => product.productId).filter(Boolean))).size +); + const PurchaseNeedsScreen = () => { const [summary, setSummary] = useState(emptySupplySummary); const [isLoading, setIsLoading] = useState(true); @@ -1815,7 +1835,7 @@ const PurchaseNeedsScreen = () => { const normalizedSearch = normalizeSearch(search); const visibleNeeds = summary.purchaseNeeds.filter(need => ( - !normalizedSearch || normalizeSearch(`${need.material} ${need.suppliers.join(' ')} ${need.colors.join(' ')}`).includes(normalizedSearch) + !normalizedSearch || normalizeSearch(`${need.material} ${getNeedDisplayName(need)} ${need.suppliers.join(' ')} ${need.colors.join(' ')} ${(need.products || []).map(product => `${product.productId} ${product.name}`).join(' ')}`).includes(normalizedSearch) )); const totalPages = Math.ceil(visibleNeeds.length / itemsPerPage); const safeCurrentPage = Math.min(currentPage, totalPages || 1); @@ -1825,17 +1845,36 @@ const PurchaseNeedsScreen = () => { const suggestedPurchaseSummary = summarizePurchaseNeeds(summary.purchaseNeeds); const pendingSupplierCount = new Set(summary.purchaseNeeds.flatMap(need => need.suppliers)).size; const missingReferenceCount = summary.purchaseNeeds.filter(need => need.missingReference).length; + const mappedNeedCount = summary.purchaseNeeds.length - missingReferenceCount; + const impactedSkuCount = countImpactedSkus(summary.purchaseNeeds); + const setupMode = missingReferenceCount > 0 && missingReferenceCount >= mappedNeedCount; + const pageTitle = setupMode ? 'Pendências para Compra' : 'Necessidade de Compra'; + const pageSubtitle = setupMode + ? 'Complete referências de consumo para liberar o cálculo real de compra por material.' + : 'Materiais abaixo do mínimo e necessidade projetada para compra.'; + const panelTitle = setupMode ? 'Fila de cadastro para compra' : 'Necessidade por material'; + const panelSubtitle = setupMode + ? 'Priorize os SKUs com maior impacto e cadastre material, rendimento e unidade de consumo.' + : 'Planejado - estoque aprovado - recebimentos pendentes.'; + const stats = setupMode + ? [ + { label: 'Pendências', value: `${missingReferenceCount}` }, + { label: 'SKUs impactados', value: `${impactedSkuCount || missingReferenceCount}` }, + { label: 'Impacto estimado', value: suggestedPurchaseSummary }, + { label: 'Referências prontas', value: `${mappedNeedCount}` }, + ] + : [ + { label: 'Itens a comprar', value: `${purchaseItemCount}` }, + { label: 'Compra sugerida', value: suggestedPurchaseSummary }, + { label: 'Fornecedores', value: `${pendingSupplierCount}` }, + { label: 'Sem referência', value: `${missingReferenceCount}` }, + ]; return (
-
+
- {[ - { label: 'Itens a comprar', value: `${purchaseItemCount}` }, - { label: 'Compra sugerida', value: suggestedPurchaseSummary }, - { label: 'Fornecedores', value: `${pendingSupplierCount}` }, - { label: 'Sem referência', value: `${missingReferenceCount}` }, - ].map(stat => ( + {stats.map(stat => (

{stat.label}

{stat.value}

@@ -1845,19 +1884,20 @@ const PurchaseNeedsScreen = () => {
-

Necessidade por material

-

Planejado - estoque aprovado - recebimentos pendentes.

+

{panelTitle}

+

{panelSubtitle}

+ {setupMode && !isLoading && summary.purchaseNeeds.length > 0 && ( +
+ Esta fila ainda não é uma lista final de compra. Os valores mostram impacto estimado por SKU até que as referências de consumo sejam cadastradas. +
+ )} {isLoading ? ( @@ -1891,80 +1936,149 @@ const PurchaseNeedsScreen = () => { ) : visibleNeeds.length ? (
- - - - - - - - - - - - - - - - - - - - - {paginatedNeeds.map(need => { - const referenceProduct = need.products?.[0]; - return ( - - - - - - - - - ); - })} - -
MaterialPlanejadoEstoquePendenteComprarCobertura
-
-

{need.material}

-
-

- {need.missingReference - ? 'Cadastre produto/material em Cadastros > Referência de Consumo' - : `${(need.colors.length ? need.colors.join(', ') : 'Todas as cores')} · ${(need.suppliers.length ? need.suppliers.join(', ') : 'Sem fornecedor')}`} -

- {need.missingReference && referenceProduct ? ( - - - - ) : null} -
- {need.products?.length ? ( -

- {need.products.slice(0, 2).map(product => product.productId).join(', ')} - {need.products.length > 2 ? ` +${need.products.length - 2}` : ''} -

- ) : null} -
-
{formatNeedQuantity(need, need.plannedKg)}{formatNeedQuantity(need, need.stockKg)}{formatNeedQuantity(need, need.pendingKg)} 0 ? 'text-amber-300' : 'text-emerald-300'}`}>{formatNeedQuantity(need, need.purchaseKg)} - - {need.missingReference ? 'Sem referência' : purchaseStatusLabels[need.status]} - -
+ {setupMode ? ( + + + + + + + + + + + + + + + + + + + + + {paginatedNeeds.map(need => { + const referenceProduct = need.products?.[0]; + const productName = referenceProduct?.name || getNeedDisplayName(need); + const productId = referenceProduct?.productId || ''; + const soldQuantity = referenceProduct?.quantitySold ?? sumNeedProducts(need, 'quantitySold'); + const stockQuantity = referenceProduct?.stockQuantity ?? sumNeedProducts(need, 'stockQuantity'); + + return ( + + + + + + + + + ); + })} + +
ProdutoVendidoEstoque produtoImpacto estimadoPendênciaAção
+
+

{productName}

+

+ {productId ? `SKU ${productId}` : 'SKU não informado'} +

+
+
{formatNumber(soldQuantity, 0)} un.{formatNumber(stockQuantity, 0)} un.{formatNeedQuantity(need, need.purchaseKg)} +
+ + {getNeedPendingLabel(need)} + + Referência de consumo incompleta +
+
+ {referenceProduct ? ( + + + Cadastrar + + ) : ( + Sem SKU + )} +
+ ) : ( + + + + + + + + + + + + + + + + + + + + + {paginatedNeeds.map(need => { + const referenceProduct = need.products?.[0]; + return ( + + + + + + + + + ); + })} + +
MaterialPlanejadoEstoquePendenteComprarCobertura
+
+

{getNeedDisplayName(need)}

+
+

+ {need.missingReference + ? 'Cadastre produto/material em Cadastros > Referência de Consumo' + : `${(need.colors.length ? need.colors.join(', ') : 'Todas as cores')} · ${(need.suppliers.length ? need.suppliers.join(', ') : 'Sem fornecedor')}`} +

+ {need.missingReference && referenceProduct ? ( + + + + ) : null} +
+ {need.products?.length ? ( +

+ {need.products.slice(0, 2).map(product => product.productId).join(', ')} + {need.products.length > 2 ? ` +${need.products.length - 2}` : ''} +

+ ) : null} +
+
{formatNeedQuantity(need, need.plannedKg)}{formatNeedQuantity(need, need.stockKg)}{formatNeedQuantity(need, need.pendingKg)} 0 ? 'text-amber-300' : 'text-emerald-300'}`}>{formatNeedQuantity(need, need.purchaseKg)} + + {need.missingReference ? 'Sem referência' : purchaseStatusLabels[need.status]} + +
+ )}