Refine material planning workflows
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m0s
This commit is contained in:
@@ -340,7 +340,12 @@ const ProductDetails = () => {
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
const isServiceComponent = (name: string) => /\b(?:SERVI[CÇ]O|FRETE|TINTURARIA|TECELAGEM|COSTURA|ESTAMPARIA|LAVANDERIA)\b/i.test(name);
|
||||
const isSuspiciousConsumption = (quantity: number, unit: string) => (
|
||||
['kg', 'quilo', 'quilos'].includes(unit.trim().toLowerCase()) && quantity >= 0.5
|
||||
);
|
||||
const materialPlan = (composition?.components || []).map(component => {
|
||||
const isService = isServiceComponent(component.componentName);
|
||||
const candidates = new Set([
|
||||
component.componentTinyId,
|
||||
component.componentSku,
|
||||
@@ -350,7 +355,7 @@ const ProductDetails = () => {
|
||||
const stockMatch = materialStock.find(item => (
|
||||
candidates.has(normalizeMaterialKey(item.produto_id)) || candidates.has(normalizeMaterialKey(item.nome))
|
||||
));
|
||||
const availableStock = stockMatch ? getPlanningStock(stockMatch.saldo) : null;
|
||||
const availableStock = isService ? null : stockMatch ? getPlanningStock(stockMatch.saldo) : null;
|
||||
const requiredForPeriod = totalSold * component.quantityPerUnit;
|
||||
const productionCapacity = availableStock === null || component.quantityPerUnit <= 0
|
||||
? null
|
||||
@@ -358,10 +363,10 @@ const ProductDetails = () => {
|
||||
const periodCoverage = requiredForPeriod > 0 && availableStock !== null
|
||||
? (availableStock / requiredForPeriod) * periodDays
|
||||
: null;
|
||||
return { component, availableStock, requiredForPeriod, productionCapacity, periodCoverage };
|
||||
return { component, availableStock, requiredForPeriod, productionCapacity, periodCoverage, isService, suspicious: isSuspiciousConsumption(component.quantityPerUnit, component.unit) };
|
||||
});
|
||||
const blockingMaterial = materialPlan
|
||||
.filter(item => item.productionCapacity !== null)
|
||||
.filter(item => !item.isService && item.productionCapacity !== null)
|
||||
.sort((a, b) => (a.productionCapacity || 0) - (b.productionCapacity || 0))[0];
|
||||
|
||||
return (
|
||||
@@ -477,20 +482,21 @@ const ProductDetails = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto rounded-xl border border-dark-border">
|
||||
<table className="w-full min-w-[920px] text-left text-sm">
|
||||
<table className="w-full min-w-[1040px] text-left text-sm">
|
||||
<thead className="bg-dark-input/60 text-[10px] font-bold uppercase tracking-widest text-dark-muted">
|
||||
<tr>
|
||||
<th className="px-4 py-3">Produto / insumo</th>
|
||||
<th className="px-4 py-3">SKU</th>
|
||||
<th className="px-4 py-3 text-right">Quantidade por unidade</th>
|
||||
<th className="px-4 py-3">Unidade</th>
|
||||
<th className="px-4 py-3">Validação</th>
|
||||
<th className="px-4 py-3 text-right">Necessário no período</th>
|
||||
<th className="px-4 py-3 text-right">Estoque Tiny</th>
|
||||
<th className="px-4 py-3 text-right">Cobertura</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-dark-border">
|
||||
{materialPlan.map(({ component, requiredForPeriod, availableStock, productionCapacity, periodCoverage }) => (
|
||||
{materialPlan.map(({ component, requiredForPeriod, availableStock, productionCapacity, periodCoverage, isService, suspicious }) => (
|
||||
<tr key={component.id} className="text-dark-text">
|
||||
<td className="px-4 py-3 font-semibold">
|
||||
{component.productId ? (
|
||||
@@ -502,12 +508,18 @@ const ProductDetails = () => {
|
||||
<td className="px-4 py-3 font-mono text-xs text-dark-muted">{component.componentSku || '—'}</td>
|
||||
<td className="px-4 py-3 text-right font-semibold">{formatNumber(component.quantityPerUnit)}</td>
|
||||
<td className="px-4 py-3 text-dark-muted">{component.unit || '—'}</td>
|
||||
<td className="px-4 py-3">
|
||||
{isService ? <span className="rounded-full border border-sky-400/30 bg-sky-400/10 px-2 py-1 text-[10px] font-bold text-sky-300">Serviço</span>
|
||||
: suspicious ? <span className="rounded-full border border-amber-400/30 bg-amber-400/10 px-2 py-1 text-[10px] font-bold text-amber-300">Revisar consumo</span>
|
||||
: <span className="text-xs font-bold text-emerald-300">OK</span>}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right font-semibold">{formatNumber(requiredForPeriod)} {component.unit || ''}</td>
|
||||
<td className="px-4 py-3 text-right font-semibold">{availableStock === null ? 'Sem vínculo' : `${formatNumber(availableStock)} ${component.unit || ''}`}</td>
|
||||
<td className={`px-4 py-3 text-right font-bold ${availableStock === null || (productionCapacity || 0) < totalSold ? 'text-red-300' : 'text-emerald-300'}`}>
|
||||
{availableStock === null
|
||||
? 'Revisar link'
|
||||
: `${formatNumber(productionCapacity || 0)} un. · ${periodCoverage === null ? '-' : `${formatNumber(periodCoverage)} dias`}`}
|
||||
<td className="px-4 py-3 text-right font-semibold">{isService ? 'Não aplicável' : availableStock === null ? 'Sem vínculo' : `${formatNumber(availableStock)} ${component.unit || ''}`}</td>
|
||||
<td className={`px-4 py-3 text-right font-bold ${isService ? 'text-sky-300' : availableStock === null || (productionCapacity || 0) < totalSold ? 'text-red-300' : 'text-emerald-300'}`}>
|
||||
{isService ? 'Terceirizar'
|
||||
: availableStock === null
|
||||
? 'Revisar link'
|
||||
: `${formatNumber(productionCapacity || 0)} un. · ${periodCoverage === null ? '-' : periodCoverage > 365 ? '> 365 dias' : `${formatNumber(periodCoverage)} dias`}`}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user