Improve planning data normalization
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m25s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m25s
This commit is contained in:
@@ -13,6 +13,7 @@ import { parseProductName } from '../productParsing';
|
||||
import { formatColorLabel } from '../displayFormatters';
|
||||
import { averageRecentValues, formatDateBucketLabel, formatDateBucketLongLabel, getAutoDateBucket, getMovingAverageWindow, getRangeDayCount, getDateBucketKey, type DateBucket } from '../chartUtils';
|
||||
import { getProductTypeConfig, resolveProductType } from '../productClassification';
|
||||
import { getPlanningStock } from '../planningStock';
|
||||
|
||||
const CHART_GRID_COLOR = 'var(--chart-grid)';
|
||||
const CHART_AXIS_COLOR = 'var(--chart-axis)';
|
||||
@@ -229,7 +230,7 @@ const ProductDetails = () => {
|
||||
const dateBucket = isHourlyChart ? 'day' : getAutoDateBucket(dateRange);
|
||||
const periodDays = getRangeDayCount(dateRange);
|
||||
const dailyAverageSold = totalSold / periodDays;
|
||||
const projectedStockDays = dailyAverageSold > 0 ? productInfo.stock / dailyAverageSold : null;
|
||||
const projectedStockDays = dailyAverageSold > 0 ? getPlanningStock(productInfo.stock) / dailyAverageSold : null;
|
||||
const stockActionLabel = projectedStockDays === null
|
||||
? 'Sem venda no período'
|
||||
: projectedStockDays <= 7
|
||||
|
||||
@@ -14,6 +14,7 @@ import { decodeProductGroupKey, normalizeProductText, parseProductName } from '.
|
||||
import { formatColorLabel } from '../displayFormatters';
|
||||
import { getProductColor } from '../productColors';
|
||||
import { getDominantProductType, getProductTypeConfig, resolveProductType, type ProductTypeKey } from '../productClassification';
|
||||
import { getPlanningStock } from '../planningStock';
|
||||
import type { CuttingSettings, DateRange, ProductAnalyticsItem, ProductionOrderItem } from '../types';
|
||||
|
||||
type VariantRow = ProductAnalyticsItem & {
|
||||
@@ -263,7 +264,7 @@ const ProductGroupDetails = () => {
|
||||
const dailySales = product.quantitySold / rangeDays;
|
||||
const projectedDemand = dailySales * REPLENISHMENT_TARGET_DAYS;
|
||||
const openProductionQuantity = openProductionByProductId[product.id] || 0;
|
||||
const availableQuantity = product.stock + openProductionQuantity;
|
||||
const availableQuantity = getPlanningStock(product.stock) + openProductionQuantity;
|
||||
const suggestedReplenishment = Math.max(0, Math.ceil(projectedDemand - availableQuantity));
|
||||
|
||||
return {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { exportToCSV, fetchCuttingSettings, fetchProductAnalytics, saveCuttingSe
|
||||
import { encodeProductGroupKey, parseProductName, sortProductSizes } from '../productParsing';
|
||||
import { formatColorLabel } from '../displayFormatters';
|
||||
import { getDominantProductType, getProductTypeConfig, productTypeOptions, resolveProductType, type ProductTypeKey } from '../productClassification';
|
||||
import { getPlanningStock } from '../planningStock';
|
||||
|
||||
type StockRisk = 'rupture' | 'critical' | 'attention' | 'monitor' | 'healthy' | 'no_sales';
|
||||
type StockStatusFilter = 'all' | StockRisk;
|
||||
@@ -262,7 +263,8 @@ const Products = () => {
|
||||
const normalizedSearch = searchTerm.trim().toLowerCase();
|
||||
const skuRows = productAnalytics.map(product => {
|
||||
const dailySales = product.quantitySold / days;
|
||||
const risk = classifyStockRisk(product.stock, dailySales);
|
||||
const planningStock = getPlanningStock(product.stock);
|
||||
const risk = classifyStockRisk(planningStock, dailySales);
|
||||
const style = riskStyles[risk];
|
||||
const metadata = parseProductName(product.name);
|
||||
const productType = resolveProductType(product.name, planningSettings.productOverrides[product.id]);
|
||||
@@ -270,7 +272,7 @@ const Products = () => {
|
||||
return {
|
||||
...product,
|
||||
dailySales,
|
||||
daysOfCover: dailySales > 0 ? product.stock / dailySales : null,
|
||||
daysOfCover: dailySales > 0 ? planningStock / dailySales : null,
|
||||
risk,
|
||||
riskLabel: style.label,
|
||||
baseName: metadata.baseName,
|
||||
@@ -302,10 +304,11 @@ const Products = () => {
|
||||
const quantitySold = group.reduce((total, product) => total + product.quantitySold, 0);
|
||||
const revenue = group.reduce((total, product) => total + product.revenue, 0);
|
||||
const stock = group.reduce((total, product) => total + product.stock, 0);
|
||||
const planningStock = group.reduce((total, product) => total + getPlanningStock(product.stock), 0);
|
||||
const orderLineCount = group.reduce((total, product) => total + product.orderLineCount, 0);
|
||||
const dailySales = quantitySold / days;
|
||||
const daysOfCover = dailySales > 0 ? stock / dailySales : null;
|
||||
const risk = classifyStockRisk(stock, dailySales);
|
||||
const daysOfCover = dailySales > 0 ? planningStock / dailySales : null;
|
||||
const risk = classifyStockRisk(planningStock, dailySales);
|
||||
const style = riskStyles[risk];
|
||||
const colorTotals = new Map<string, number>();
|
||||
const sizeTotals = new Map<string, number>();
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { DateRange, ProductAnalyticsItem, ProductionOrderItem } from '../ty
|
||||
import { exportToCSV, fetchProductAnalytics, fetchProductionOrders } from '../dataService';
|
||||
import { encodeProductGroupKey, parseProductName, sortProductSizes } from '../productParsing';
|
||||
import { formatColorLabel } from '../displayFormatters';
|
||||
import { getPlanningStock } from '../planningStock';
|
||||
|
||||
type ReplenishmentStatus = 'need' | 'covered' | 'no_sales' | 'no_stock';
|
||||
type ReplenishmentFilter = 'all' | ReplenishmentStatus;
|
||||
@@ -174,7 +175,7 @@ const Replenishment = () => {
|
||||
const dailySales = product.quantitySold / rangeDays;
|
||||
const projectedDemand = dailySales * targetCoverageDays;
|
||||
const openProductionQuantity = openProductionByProductId[product.id] || 0;
|
||||
const availableQuantity = product.stock + openProductionQuantity;
|
||||
const availableQuantity = getPlanningStock(product.stock) + openProductionQuantity;
|
||||
const rawNeed = projectedDemand - availableQuantity;
|
||||
const suggestedQuantity = Math.max(0, Math.ceil(rawNeed));
|
||||
const daysOfCover = dailySales > 0 ? availableQuantity / dailySales : null;
|
||||
|
||||
@@ -31,6 +31,7 @@ import { classifyCutFamily } from '../analytics/cutting';
|
||||
import { adjustSupplyLotInventory, approveSupplyReceipt, consumeSupplyLotForProduction, createSupplyFabricPlan, createSupplyReceipt, deleteSupplyFabricPlan, deleteSupplyReceipt, fetchCuttingSettings, fetchProductAnalytics, fetchSupplySummary } from '../dataService';
|
||||
import { parseProductName } from '../productParsing';
|
||||
import { resolveProductType, type ProductTypeKey } from '../productClassification';
|
||||
import { getPlanningStock } from '../planningStock';
|
||||
import type { CuttingSettings, DateRange, ProductAnalyticsItem, SupplyFabricPlan, SupplyLot, SupplyMovement, SupplyPurchaseNeed, SupplyReceipt, SupplySummary } from '../types';
|
||||
|
||||
type InventoryTab = 'dashboard' | 'balance' | 'receipts' | 'inventory' | 'movements';
|
||||
@@ -230,9 +231,10 @@ const buildDemandRow = (
|
||||
const color = override?.color || metadata.color;
|
||||
const size = (override?.size || metadata.size).toUpperCase();
|
||||
const dailySales = product.quantitySold / rangeDays;
|
||||
const daysOfCover = dailySales > 0 ? product.stock / dailySales : null;
|
||||
const planningStock = getPlanningStock(product.stock);
|
||||
const daysOfCover = dailySales > 0 ? planningStock / dailySales : null;
|
||||
const targetDemand = dailySales * targetCoverageDays;
|
||||
const suggestedUnits = Math.max(0, Math.ceil(targetDemand - product.stock));
|
||||
const suggestedUnits = Math.max(0, Math.ceil(targetDemand - planningStock));
|
||||
const missingData: string[] = [];
|
||||
|
||||
if (planningReviewTypes.has(productType)) missingData.push('tipo');
|
||||
|
||||
Reference in New Issue
Block a user