Apply gemini suggested calculate improvements
This commit is contained in:
@@ -83,6 +83,7 @@ process.on('SIGTERM', cancelCalculation);
|
|||||||
async function calculateMetrics() {
|
async function calculateMetrics() {
|
||||||
let connection;
|
let connection;
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
// Initialize all counts to 0
|
||||||
let processedProducts = 0;
|
let processedProducts = 0;
|
||||||
let processedOrders = 0;
|
let processedOrders = 0;
|
||||||
let processedPurchaseOrders = 0;
|
let processedPurchaseOrders = 0;
|
||||||
@@ -140,6 +141,7 @@ async function calculateMetrics() {
|
|||||||
totalProducts = productCount.total;
|
totalProducts = productCount.total;
|
||||||
totalOrders = orderCount.total;
|
totalOrders = orderCount.total;
|
||||||
totalPurchaseOrders = poCount.total;
|
totalPurchaseOrders = poCount.total;
|
||||||
|
connection.release();
|
||||||
|
|
||||||
// If nothing needs updating, we can exit early
|
// If nothing needs updating, we can exit early
|
||||||
if (totalProducts === 0 && totalOrders === 0 && totalPurchaseOrders === 0) {
|
if (totalProducts === 0 && totalOrders === 0 && totalPurchaseOrders === 0) {
|
||||||
@@ -153,6 +155,7 @@ async function calculateMetrics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create history record for this calculation
|
// Create history record for this calculation
|
||||||
|
connection = await getConnection(); // Re-establish connection
|
||||||
const [historyResult] = await connection.query(`
|
const [historyResult] = await connection.query(`
|
||||||
INSERT INTO calculate_history (
|
INSERT INTO calculate_history (
|
||||||
start_time,
|
start_time,
|
||||||
@@ -209,7 +212,7 @@ async function calculateMetrics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isCancelled = false;
|
isCancelled = false;
|
||||||
connection = await getConnection();
|
connection = await getConnection(); // Get a new connection for the main processing
|
||||||
|
|
||||||
try {
|
try {
|
||||||
global.outputProgress({
|
global.outputProgress({
|
||||||
@@ -228,14 +231,12 @@ async function calculateMetrics() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update progress periodically
|
// Update progress periodically - REFACTORED
|
||||||
const updateProgress = async (products = null, orders = null, purchaseOrders = null) => {
|
const updateProgress = async (products = null, orders = null, purchaseOrders = null) => {
|
||||||
// Ensure all values are valid numbers or default to previous value
|
|
||||||
if (products !== null) processedProducts = Number(products) || processedProducts || 0;
|
if (products !== null) processedProducts = Number(products) || processedProducts || 0;
|
||||||
if (orders !== null) processedOrders = Number(orders) || processedOrders || 0;
|
if (orders !== null) processedOrders = Number(orders) || processedOrders || 0;
|
||||||
if (purchaseOrders !== null) processedPurchaseOrders = Number(purchaseOrders) || processedPurchaseOrders || 0;
|
if (purchaseOrders !== null) processedPurchaseOrders = Number(purchaseOrders) || processedPurchaseOrders || 0;
|
||||||
|
|
||||||
// Ensure we never send NaN to the database
|
|
||||||
const safeProducts = Number(processedProducts) || 0;
|
const safeProducts = Number(processedProducts) || 0;
|
||||||
const safeOrders = Number(processedOrders) || 0;
|
const safeOrders = Number(processedOrders) || 0;
|
||||||
const safePurchaseOrders = Number(processedPurchaseOrders) || 0;
|
const safePurchaseOrders = Number(processedPurchaseOrders) || 0;
|
||||||
@@ -250,14 +251,14 @@ async function calculateMetrics() {
|
|||||||
`, [safeProducts, safeOrders, safePurchaseOrders, calculateHistoryId]);
|
`, [safeProducts, safeOrders, safePurchaseOrders, calculateHistoryId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to ensure valid progress numbers
|
// Helper function to ensure valid progress numbers - this is fine
|
||||||
const ensureValidProgress = (current, total) => ({
|
const ensureValidProgress = (current, total) => ({
|
||||||
current: Number(current) || 0,
|
current: Number(current) || 0,
|
||||||
total: Number(total) || 1, // Default to 1 to avoid division by zero
|
total: Number(total) || 1, // Default to 1 to avoid division by zero
|
||||||
percentage: (((Number(current) || 0) / (Number(total) || 1)) * 100).toFixed(1)
|
percentage: (((Number(current) || 0) / (Number(total) || 1)) * 100).toFixed(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initial progress
|
// Initial progress - this is fine
|
||||||
const initialProgress = ensureValidProgress(0, totalProducts);
|
const initialProgress = ensureValidProgress(0, totalProducts);
|
||||||
global.outputProgress({
|
global.outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
@@ -275,37 +276,28 @@ async function calculateMetrics() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Call each module, passing totals and accumulating processed counts ---
|
||||||
|
|
||||||
if (!SKIP_PRODUCT_METRICS) {
|
if (!SKIP_PRODUCT_METRICS) {
|
||||||
const result = await calculateProductMetrics(startTime, totalProducts, processedProducts, isCancelled);
|
const result = await calculateProductMetrics(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders); // Update with accumulated values
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Product metrics calculation failed');
|
throw new Error('Product metrics calculation failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Skipping product metrics calculation...');
|
console.log('Skipping product metrics calculation...');
|
||||||
processedProducts = Math.floor(totalProducts * 0.6);
|
// Don't artificially inflate processedProducts if skipping
|
||||||
await updateProgress(processedProducts);
|
|
||||||
global.outputProgress({
|
|
||||||
status: 'running',
|
|
||||||
operation: 'Skipping product metrics calculation',
|
|
||||||
current: processedProducts,
|
|
||||||
total: totalProducts,
|
|
||||||
elapsed: global.formatElapsedTime(startTime),
|
|
||||||
remaining: global.estimateRemaining(startTime, processedProducts, totalProducts),
|
|
||||||
rate: global.calculateRate(startTime, processedProducts),
|
|
||||||
percentage: '60',
|
|
||||||
timing: {
|
|
||||||
start_time: new Date(startTime).toISOString(),
|
|
||||||
end_time: new Date().toISOString(),
|
|
||||||
elapsed_seconds: Math.round((Date.now() - startTime) / 1000)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate time-based aggregates
|
|
||||||
if (!SKIP_TIME_AGGREGATES) {
|
if (!SKIP_TIME_AGGREGATES) {
|
||||||
const result = await calculateTimeAggregates(startTime, totalProducts, processedProducts);
|
const result = await calculateTimeAggregates(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Time aggregates calculation failed');
|
throw new Error('Time aggregates calculation failed');
|
||||||
}
|
}
|
||||||
@@ -313,10 +305,12 @@ async function calculateMetrics() {
|
|||||||
console.log('Skipping time aggregates calculation');
|
console.log('Skipping time aggregates calculation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate financial metrics
|
|
||||||
if (!SKIP_FINANCIAL_METRICS) {
|
if (!SKIP_FINANCIAL_METRICS) {
|
||||||
const result = await calculateFinancialMetrics(startTime, totalProducts, processedProducts);
|
const result = await calculateFinancialMetrics(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Financial metrics calculation failed');
|
throw new Error('Financial metrics calculation failed');
|
||||||
}
|
}
|
||||||
@@ -324,10 +318,12 @@ async function calculateMetrics() {
|
|||||||
console.log('Skipping financial metrics calculation');
|
console.log('Skipping financial metrics calculation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate vendor metrics
|
|
||||||
if (!SKIP_VENDOR_METRICS) {
|
if (!SKIP_VENDOR_METRICS) {
|
||||||
const result = await calculateVendorMetrics(startTime, totalProducts, processedProducts);
|
const result = await calculateVendorMetrics(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Vendor metrics calculation failed');
|
throw new Error('Vendor metrics calculation failed');
|
||||||
}
|
}
|
||||||
@@ -335,10 +331,12 @@ async function calculateMetrics() {
|
|||||||
console.log('Skipping vendor metrics calculation');
|
console.log('Skipping vendor metrics calculation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate category metrics
|
|
||||||
if (!SKIP_CATEGORY_METRICS) {
|
if (!SKIP_CATEGORY_METRICS) {
|
||||||
const result = await calculateCategoryMetrics(startTime, totalProducts, processedProducts);
|
const result = await calculateCategoryMetrics(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Category metrics calculation failed');
|
throw new Error('Category metrics calculation failed');
|
||||||
}
|
}
|
||||||
@@ -346,10 +344,12 @@ async function calculateMetrics() {
|
|||||||
console.log('Skipping category metrics calculation');
|
console.log('Skipping category metrics calculation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate brand metrics
|
|
||||||
if (!SKIP_BRAND_METRICS) {
|
if (!SKIP_BRAND_METRICS) {
|
||||||
const result = await calculateBrandMetrics(startTime, totalProducts, processedProducts);
|
const result = await calculateBrandMetrics(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Brand metrics calculation failed');
|
throw new Error('Brand metrics calculation failed');
|
||||||
}
|
}
|
||||||
@@ -357,10 +357,12 @@ async function calculateMetrics() {
|
|||||||
console.log('Skipping brand metrics calculation');
|
console.log('Skipping brand metrics calculation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate sales forecasts
|
|
||||||
if (!SKIP_SALES_FORECASTS) {
|
if (!SKIP_SALES_FORECASTS) {
|
||||||
const result = await calculateSalesForecasts(startTime, totalProducts, processedProducts);
|
const result = await calculateSalesForecasts(startTime, totalProducts, processedProducts, isCancelled); // Pass totals
|
||||||
await updateProgress(result.processedProducts, result.processedOrders, result.processedPurchaseOrders);
|
processedProducts += result.processedProducts; // Accumulate
|
||||||
|
processedOrders += result.processedOrders;
|
||||||
|
processedPurchaseOrders += result.processedPurchaseOrders;
|
||||||
|
await updateProgress(processedProducts, processedOrders, processedPurchaseOrders);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Sales forecasts calculation failed');
|
throw new Error('Sales forecasts calculation failed');
|
||||||
}
|
}
|
||||||
@@ -368,23 +370,7 @@ async function calculateMetrics() {
|
|||||||
console.log('Skipping sales forecasts calculation');
|
console.log('Skipping sales forecasts calculation');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate ABC classification
|
// --- ABC Classification (Refactored) ---
|
||||||
outputProgress({
|
|
||||||
status: 'running',
|
|
||||||
operation: 'Starting ABC classification',
|
|
||||||
current: processedProducts || 0,
|
|
||||||
total: totalProducts || 0,
|
|
||||||
elapsed: formatElapsedTime(startTime),
|
|
||||||
remaining: estimateRemaining(startTime, processedProducts || 0, totalProducts || 0),
|
|
||||||
rate: calculateRate(startTime, processedProducts || 0),
|
|
||||||
percentage: (((processedProducts || 0) / (totalProducts || 1)) * 100).toFixed(1),
|
|
||||||
timing: {
|
|
||||||
start_time: new Date(startTime).toISOString(),
|
|
||||||
end_time: new Date().toISOString(),
|
|
||||||
elapsed_seconds: Math.round((Date.now() - startTime) / 1000)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isCancelled) return {
|
if (isCancelled) return {
|
||||||
processedProducts: processedProducts || 0,
|
processedProducts: processedProducts || 0,
|
||||||
processedOrders: processedOrders || 0,
|
processedOrders: processedOrders || 0,
|
||||||
@@ -402,21 +388,26 @@ async function calculateMetrics() {
|
|||||||
pid BIGINT NOT NULL,
|
pid BIGINT NOT NULL,
|
||||||
total_revenue DECIMAL(10,3),
|
total_revenue DECIMAL(10,3),
|
||||||
rank_num INT,
|
rank_num INT,
|
||||||
|
dense_rank_num INT,
|
||||||
|
percentile DECIMAL(5,2),
|
||||||
total_count INT,
|
total_count INT,
|
||||||
PRIMARY KEY (pid),
|
PRIMARY KEY (pid),
|
||||||
INDEX (rank_num)
|
INDEX (rank_num),
|
||||||
|
INDEX (dense_rank_num),
|
||||||
|
INDEX (percentile)
|
||||||
) ENGINE=MEMORY
|
) ENGINE=MEMORY
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
let processedCount = processedProducts;
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Creating revenue rankings',
|
operation: 'Creating revenue rankings',
|
||||||
current: processedProducts || 0,
|
current: processedCount,
|
||||||
total: totalProducts || 0,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedProducts || 0, totalProducts || 0),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
rate: calculateRate(startTime, processedProducts || 0),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: (((processedProducts || 0) / (totalProducts || 1)) * 100).toFixed(1),
|
percentage: ((processedCount / totalProducts) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -431,49 +422,35 @@ async function calculateMetrics() {
|
|||||||
success: false
|
success: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Calculate rankings with proper tie handling and get total count in one go.
|
||||||
await connection.query(`
|
await connection.query(`
|
||||||
INSERT INTO temp_revenue_ranks
|
INSERT INTO temp_revenue_ranks
|
||||||
|
WITH revenue_data AS (
|
||||||
SELECT
|
SELECT
|
||||||
pid,
|
pid,
|
||||||
total_revenue,
|
total_revenue,
|
||||||
@rank := @rank + 1 as rank_num,
|
COUNT(*) OVER () as total_count,
|
||||||
@total_count := @rank as total_count
|
PERCENT_RANK() OVER (ORDER BY total_revenue DESC) * 100 as percentile,
|
||||||
FROM (
|
RANK() OVER (ORDER BY total_revenue DESC) as rank_num,
|
||||||
SELECT pid, total_revenue
|
DENSE_RANK() OVER (ORDER BY total_revenue DESC) as dense_rank_num
|
||||||
FROM product_metrics
|
FROM product_metrics
|
||||||
WHERE total_revenue > 0
|
WHERE total_revenue > 0
|
||||||
ORDER BY total_revenue DESC
|
)
|
||||||
) ranked,
|
SELECT
|
||||||
(SELECT @rank := 0) r
|
pid,
|
||||||
|
total_revenue,
|
||||||
|
rank_num,
|
||||||
|
dense_rank_num,
|
||||||
|
percentile,
|
||||||
|
total_count
|
||||||
|
FROM revenue_data
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Get total count for percentage calculation
|
// Get total count for percentage calculation (already done in the above query)
|
||||||
const [rankingCount] = await connection.query('SELECT MAX(rank_num) as total_count FROM temp_revenue_ranks');
|
// No need for this separate query:
|
||||||
const totalCount = rankingCount[0].total_count || 1;
|
// const [rankingCount] = await connection.query('SELECT MAX(rank_num) as total_count FROM temp_revenue_ranks');
|
||||||
const max_rank = totalCount; // Store max_rank for use in classification
|
// const totalCount = rankingCount[0].total_count || 1;
|
||||||
|
// const max_rank = totalCount; // Store max_rank for use in classification
|
||||||
outputProgress({
|
|
||||||
status: 'running',
|
|
||||||
operation: 'Updating ABC classifications',
|
|
||||||
current: processedProducts || 0,
|
|
||||||
total: totalProducts || 0,
|
|
||||||
elapsed: formatElapsedTime(startTime),
|
|
||||||
remaining: estimateRemaining(startTime, processedProducts || 0, totalProducts || 0),
|
|
||||||
rate: calculateRate(startTime, processedProducts || 0),
|
|
||||||
percentage: (((processedProducts || 0) / (totalProducts || 1)) * 100).toFixed(1),
|
|
||||||
timing: {
|
|
||||||
start_time: new Date(startTime).toISOString(),
|
|
||||||
end_time: new Date().toISOString(),
|
|
||||||
elapsed_seconds: Math.round((Date.now() - startTime) / 1000)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isCancelled) return {
|
|
||||||
processedProducts: processedProducts || 0,
|
|
||||||
processedOrders: processedOrders || 0,
|
|
||||||
processedPurchaseOrders: 0,
|
|
||||||
success: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// ABC classification progress tracking
|
// ABC classification progress tracking
|
||||||
let abcProcessedCount = 0;
|
let abcProcessedCount = 0;
|
||||||
@@ -489,7 +466,7 @@ async function calculateMetrics() {
|
|||||||
success: false
|
success: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// First get a batch of PIDs that need updating
|
// Get a batch of PIDs that need updating - REFACTORED to use percentile
|
||||||
const [pids] = await connection.query(`
|
const [pids] = await connection.query(`
|
||||||
SELECT pm.pid
|
SELECT pm.pid
|
||||||
FROM product_metrics pm
|
FROM product_metrics pm
|
||||||
@@ -497,41 +474,38 @@ async function calculateMetrics() {
|
|||||||
WHERE pm.abc_class IS NULL
|
WHERE pm.abc_class IS NULL
|
||||||
OR pm.abc_class !=
|
OR pm.abc_class !=
|
||||||
CASE
|
CASE
|
||||||
WHEN tr.rank_num IS NULL THEN 'C'
|
WHEN tr.pid IS NULL THEN 'C'
|
||||||
WHEN (tr.rank_num / ?) * 100 <= ? THEN 'A'
|
WHEN tr.percentile <= ? THEN 'A'
|
||||||
WHEN (tr.rank_num / ?) * 100 <= ? THEN 'B'
|
WHEN tr.percentile <= ? THEN 'B'
|
||||||
ELSE 'C'
|
ELSE 'C'
|
||||||
END
|
END
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
`, [max_rank, abcThresholds.a_threshold,
|
`, [abcThresholds.a_threshold, abcThresholds.b_threshold, batchSize]);
|
||||||
max_rank, abcThresholds.b_threshold,
|
|
||||||
batchSize]);
|
|
||||||
|
|
||||||
if (pids.length === 0) {
|
if (pids.length === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then update just those PIDs
|
// Update just those PIDs - REFACTORED to use percentile
|
||||||
const [result] = await connection.query(`
|
await connection.query(`
|
||||||
UPDATE product_metrics pm
|
UPDATE product_metrics pm
|
||||||
LEFT JOIN temp_revenue_ranks tr ON pm.pid = tr.pid
|
LEFT JOIN temp_revenue_ranks tr ON pm.pid = tr.pid
|
||||||
SET pm.abc_class =
|
SET pm.abc_class =
|
||||||
CASE
|
CASE
|
||||||
WHEN tr.rank_num IS NULL THEN 'C'
|
WHEN tr.pid IS NULL THEN 'C'
|
||||||
WHEN (tr.rank_num / ?) * 100 <= ? THEN 'A'
|
WHEN tr.percentile <= ? THEN 'A'
|
||||||
WHEN (tr.rank_num / ?) * 100 <= ? THEN 'B'
|
WHEN tr.percentile <= ? THEN 'B'
|
||||||
ELSE 'C'
|
ELSE 'C'
|
||||||
END,
|
END,
|
||||||
pm.last_calculated_at = NOW()
|
pm.last_calculated_at = NOW()
|
||||||
WHERE pm.pid IN (?)
|
WHERE pm.pid IN (?)
|
||||||
`, [max_rank, abcThresholds.a_threshold,
|
`, [abcThresholds.a_threshold, abcThresholds.b_threshold, pids.map(row => row.pid)]);
|
||||||
max_rank, abcThresholds.b_threshold,
|
|
||||||
pids.map(row => row.pid)]);
|
|
||||||
|
|
||||||
abcProcessedCount += result.affectedRows;
|
abcProcessedCount += pids.length; // Use pids.length, more accurate
|
||||||
|
processedProducts += pids.length; // Add to the main processedProducts
|
||||||
|
|
||||||
// Calculate progress ensuring valid numbers
|
// Calculate progress ensuring valid numbers
|
||||||
const currentProgress = Math.floor(totalProducts * (0.99 + (abcProcessedCount / (totalCount || 1)) * 0.01));
|
const currentProgress = Math.floor(totalProducts * (0.99 + (abcProcessedCount / (totalProducts || 1)) * 0.01));
|
||||||
processedProducts = Number(currentProgress) || processedProducts || 0;
|
processedProducts = Number(currentProgress) || processedProducts || 0;
|
||||||
|
|
||||||
// Only update progress at most once per second
|
// Only update progress at most once per second
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
let myProcessedProducts = 0; // Not *directly* processing products, tracking brands
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get last calculation timestamp
|
// Get last calculation timestamp
|
||||||
@@ -26,12 +27,12 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
OR o.id IS NOT NULL
|
OR o.id IS NOT NULL
|
||||||
)
|
)
|
||||||
`, [lastCalculationTime, lastCalculationTime]);
|
`, [lastCalculationTime, lastCalculationTime]);
|
||||||
const totalBrands = brandCount[0].count;
|
const totalBrands = brandCount[0].count; // Track total *brands*
|
||||||
|
|
||||||
if (totalBrands === 0) {
|
if (totalBrands === 0) {
|
||||||
console.log('No brands need metric updates');
|
console.log('No brands need metric updates');
|
||||||
return {
|
return {
|
||||||
processedProducts: 0,
|
processedProducts: 0, // Not directly processing products
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success: true
|
success: true
|
||||||
@@ -42,12 +43,12 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
operation: 'Brand metrics calculation cancelled',
|
operation: 'Brand metrics calculation cancelled',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalBrands,
|
total: totalBrands, // Report total *brands*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: null,
|
remaining: null,
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalBrands) * 100).toFixed(1),
|
percentage: ((processedCount / totalBrands) * 100).toFixed(1), // Base on brands
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -55,7 +56,7 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: 0, // Not directly processing products
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -65,12 +66,12 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Starting brand metrics calculation',
|
operation: 'Starting brand metrics calculation',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalBrands,
|
total: totalBrands, // Report total *brands*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalBrands),
|
remaining: estimateRemaining(startTime, processedCount, totalBrands),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalBrands) * 100).toFixed(1),
|
percentage: ((processedCount / totalBrands) * 100).toFixed(1), // Base on brands
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -80,6 +81,7 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
|
|
||||||
// Process in batches
|
// Process in batches
|
||||||
let lastBrand = '';
|
let lastBrand = '';
|
||||||
|
let processedBrands = 0; // Track processed brands
|
||||||
while (true) {
|
while (true) {
|
||||||
if (isCancelled) break;
|
if (isCancelled) break;
|
||||||
|
|
||||||
@@ -243,17 +245,17 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_sales_stats');
|
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_sales_stats');
|
||||||
|
|
||||||
lastBrand = batch[batch.length - 1].brand;
|
lastBrand = batch[batch.length - 1].brand;
|
||||||
processedCount += batch.length;
|
processedBrands += batch.length; // Increment processed *brands*
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Processing brand metrics batch',
|
operation: 'Processing brand metrics batch',
|
||||||
current: processedCount,
|
current: processedCount + processedBrands, // Use cumulative brand count
|
||||||
total: totalBrands,
|
total: totalBrands, // Report total *brands*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalBrands),
|
remaining: estimateRemaining(startTime, processedCount + processedBrands, totalBrands),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount + processedBrands),
|
||||||
percentage: ((processedCount / totalBrands) * 100).toFixed(1),
|
percentage: (((processedCount + processedBrands) / totalBrands) * 100).toFixed(1), // Base on brands
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -273,7 +275,7 @@ async function calculateBrandMetrics(startTime, totalProducts, processedCount =
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: 0, // Not directly processing products
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
let myProcessedProducts = 0; // Not *directly* processing products, but tracking categories
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get last calculation timestamp
|
// Get last calculation timestamp
|
||||||
@@ -28,12 +29,12 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
OR o.id IS NOT NULL
|
OR o.id IS NOT NULL
|
||||||
)
|
)
|
||||||
`, [lastCalculationTime, lastCalculationTime]);
|
`, [lastCalculationTime, lastCalculationTime]);
|
||||||
const totalCategories = categoryCount[0].count;
|
const totalCategories = categoryCount[0].count; // Track total *categories*
|
||||||
|
|
||||||
if (totalCategories === 0) {
|
if (totalCategories === 0) {
|
||||||
console.log('No categories need metric updates');
|
console.log('No categories need metric updates');
|
||||||
return {
|
return {
|
||||||
processedProducts: 0,
|
processedProducts: 0, // Not directly processing products
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success: true
|
success: true
|
||||||
@@ -44,12 +45,12 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
operation: 'Category metrics calculation cancelled',
|
operation: 'Category metrics calculation cancelled',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalCategories,
|
total: totalCategories, // Report total *categories*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: null,
|
remaining: null,
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalCategories) * 100).toFixed(1),
|
percentage: ((processedCount / totalCategories) * 100).toFixed(1), // Base on categories
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -57,7 +58,7 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: 0, // Not directly processing products
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -67,12 +68,12 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Starting category metrics calculation',
|
operation: 'Starting category metrics calculation',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalCategories,
|
total: totalCategories, // Report total *categories*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalCategories),
|
remaining: estimateRemaining(startTime, processedCount, totalCategories),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalCategories) * 100).toFixed(1),
|
percentage: ((processedCount / totalCategories) * 100).toFixed(1), // Base on categories
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -82,6 +83,7 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
|
|
||||||
// Process in batches
|
// Process in batches
|
||||||
let lastCatId = 0;
|
let lastCatId = 0;
|
||||||
|
let processedCategories = 0; // Track processed categories
|
||||||
while (true) {
|
while (true) {
|
||||||
if (isCancelled) break;
|
if (isCancelled) break;
|
||||||
|
|
||||||
@@ -247,17 +249,17 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_sales_stats');
|
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_sales_stats');
|
||||||
|
|
||||||
lastCatId = batch[batch.length - 1].cat_id;
|
lastCatId = batch[batch.length - 1].cat_id;
|
||||||
processedCount += batch.length;
|
processedCategories += batch.length; // Increment processed *categories*
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Processing category metrics batch',
|
operation: 'Processing category metrics batch',
|
||||||
current: processedCount,
|
current: processedCount + processedCategories, // Use cumulative category count
|
||||||
total: totalCategories,
|
total: totalCategories, // Report total *categories*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalCategories),
|
remaining: estimateRemaining(startTime, processedCount + processedCategories, totalCategories),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount + processedCategories),
|
||||||
percentage: ((processedCount / totalCategories) * 100).toFixed(1),
|
percentage: (((processedCount + processedCategories) / totalCategories) * 100).toFixed(1), // Base on categories
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -277,7 +279,7 @@ async function calculateCategoryMetrics(startTime, totalProducts, processedCount
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: 0, // Not directly processing products
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ async function calculateFinancialMetrics(startTime, totalProducts, processedCoun
|
|||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
let myProcessedProducts = 0; // Track products processed *within this module*
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get last calculation timestamp
|
// Get last calculation timestamp
|
||||||
@@ -54,7 +55,7 @@ async function calculateFinancialMetrics(startTime, totalProducts, processedCoun
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts,
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -133,17 +134,17 @@ async function calculateFinancialMetrics(startTime, totalProducts, processedCoun
|
|||||||
`, [batch.map(row => row.pid)]);
|
`, [batch.map(row => row.pid)]);
|
||||||
|
|
||||||
lastPid = batch[batch.length - 1].pid;
|
lastPid = batch[batch.length - 1].pid;
|
||||||
processedCount += batch.length;
|
myProcessedProducts += batch.length;
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Processing financial metrics batch',
|
operation: 'Processing financial metrics batch',
|
||||||
current: processedCount,
|
current: processedCount + myProcessedProducts,
|
||||||
total: totalProducts,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
remaining: estimateRemaining(startTime, processedCount + myProcessedProducts, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount + myProcessedProducts),
|
||||||
percentage: ((processedCount / totalProducts) * 100).toFixed(1),
|
percentage: (((processedCount + myProcessedProducts) / totalProducts) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -163,7 +164,7 @@ async function calculateFinancialMetrics(startTime, totalProducts, processedCoun
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts,
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
let processedOrders = 0;
|
let processedOrders = 0;
|
||||||
|
let myProcessedProducts = 0; // Track products processed *within this module*
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -24,24 +25,10 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
`);
|
`);
|
||||||
const lastCalculationTime = lastCalc[0]?.last_calculation_timestamp || '1970-01-01';
|
const lastCalculationTime = lastCalc[0]?.last_calculation_timestamp || '1970-01-01';
|
||||||
|
|
||||||
// Get total product count if not provided
|
|
||||||
if (!totalProducts) {
|
|
||||||
const [productCount] = await connection.query(`
|
|
||||||
SELECT COUNT(DISTINCT p.pid) as count
|
|
||||||
FROM products p
|
|
||||||
LEFT JOIN orders o ON p.pid = o.pid AND o.updated > ?
|
|
||||||
LEFT JOIN purchase_orders po ON p.pid = po.pid AND po.updated > ?
|
|
||||||
WHERE p.updated > ?
|
|
||||||
OR o.pid IS NOT NULL
|
|
||||||
OR po.pid IS NOT NULL
|
|
||||||
`, [lastCalculationTime, lastCalculationTime, lastCalculationTime]);
|
|
||||||
totalProducts = productCount[0].count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalProducts === 0) {
|
if (totalProducts === 0) {
|
||||||
console.log('No products need updating');
|
console.log('No products need updating');
|
||||||
return {
|
return {
|
||||||
processedProducts: 0,
|
processedProducts: myProcessedProducts,
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success: true
|
success: true
|
||||||
@@ -69,7 +56,7 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts,
|
||||||
processedOrders,
|
processedOrders,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -290,6 +277,7 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
|
|
||||||
lastPid = batch[batch.length - 1].pid;
|
lastPid = batch[batch.length - 1].pid;
|
||||||
processedCount += batch.length;
|
processedCount += batch.length;
|
||||||
|
myProcessedProducts += batch.length; // Increment the *module's* count
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
@@ -361,12 +349,12 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Starting product time aggregates calculation',
|
operation: 'Starting product time aggregates calculation',
|
||||||
current: processedCount || 0,
|
current: processedCount,
|
||||||
total: totalProducts || 0,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount || 0, totalProducts || 0),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount || 0),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: (((processedCount || 0) / (totalProducts || 1)) * 100).toFixed(1),
|
percentage: (((processedCount) / (totalProducts || 1)) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -428,12 +416,12 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Product time aggregates calculated',
|
operation: 'Product time aggregates calculated',
|
||||||
current: processedCount || 0,
|
current: processedCount,
|
||||||
total: totalProducts || 0,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount || 0, totalProducts || 0),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount || 0),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: (((processedCount || 0) / (totalProducts || 1)) * 100).toFixed(1),
|
percentage: (((processedCount) / (totalProducts || 1)) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -445,12 +433,12 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Skipping product time aggregates calculation',
|
operation: 'Skipping product time aggregates calculation',
|
||||||
current: processedCount || 0,
|
current: processedCount,
|
||||||
total: totalProducts || 0,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount || 0, totalProducts || 0),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount || 0),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: (((processedCount || 0) / (totalProducts || 1)) * 100).toFixed(1),
|
percentage: (((processedCount) / (totalProducts || 1)) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -479,7 +467,7 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
if (isCancelled) return {
|
if (isCancelled) return {
|
||||||
processedProducts: processedCount,
|
processedProducts: processedCount,
|
||||||
processedOrders,
|
processedOrders,
|
||||||
processedPurchaseOrders: 0, // This module doesn't process POs
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -540,7 +528,7 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
if (isCancelled) return {
|
if (isCancelled) return {
|
||||||
processedProducts: processedCount,
|
processedProducts: processedCount,
|
||||||
processedOrders,
|
processedOrders,
|
||||||
processedPurchaseOrders: 0, // This module doesn't process POs
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -620,9 +608,9 @@ async function calculateProductMetrics(startTime, totalProducts, processedCount
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount || 0,
|
processedProducts: processedCount,
|
||||||
processedOrders: processedOrders || 0,
|
processedOrders: processedOrders || 0,
|
||||||
processedPurchaseOrders: 0, // This module doesn't process POs
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const { getConnection } = require('./utils/db');
|
|||||||
async function calculateSalesForecasts(startTime, totalProducts, processedCount = 0, isCancelled = false) {
|
async function calculateSalesForecasts(startTime, totalProducts, processedCount = 0, isCancelled = false) {
|
||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
|
let myProcessedProducts = 0; // Track products processed *within this module*
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -43,7 +44,7 @@ async function calculateSalesForecasts(startTime, totalProducts, processedCount
|
|||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
operation: 'Sales forecast calculation cancelled',
|
operation: 'Sales forecast calculation cancelled',
|
||||||
current: processedCount,
|
current: processedCount,
|
||||||
total: totalProductsToUpdate,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: null,
|
remaining: null,
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
@@ -55,7 +56,7 @@ async function calculateSalesForecasts(startTime, totalProducts, processedCount
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts,
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -66,9 +67,9 @@ async function calculateSalesForecasts(startTime, totalProducts, processedCount
|
|||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Starting sales forecast calculation',
|
operation: 'Starting sales forecast calculation',
|
||||||
current: processedCount,
|
current: processedCount,
|
||||||
total: totalProductsToUpdate,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalProductsToUpdate),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalProductsToUpdate) * 100).toFixed(1),
|
percentage: ((processedCount / totalProductsToUpdate) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
@@ -255,15 +256,15 @@ async function calculateSalesForecasts(startTime, totalProducts, processedCount
|
|||||||
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_confidence_calc');
|
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_confidence_calc');
|
||||||
|
|
||||||
lastPid = batch[batch.length - 1].pid;
|
lastPid = batch[batch.length - 1].pid;
|
||||||
processedCount += batch.length;
|
myProcessedProducts += batch.length;
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Processing sales forecast batch',
|
operation: 'Processing sales forecast batch',
|
||||||
current: processedCount,
|
current: processedCount,
|
||||||
total: totalProductsToUpdate,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalProductsToUpdate),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalProductsToUpdate) * 100).toFixed(1),
|
percentage: ((processedCount / totalProductsToUpdate) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
@@ -285,7 +286,7 @@ async function calculateSalesForecasts(startTime, totalProducts, processedCount
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts,
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
let myProcessedProducts = 0; // Track products processed *within this module*
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get last calculation timestamp
|
// Get last calculation timestamp
|
||||||
@@ -15,17 +16,7 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
`);
|
`);
|
||||||
const lastCalculationTime = lastCalc[0]?.last_calculation_timestamp || '1970-01-01';
|
const lastCalculationTime = lastCalc[0]?.last_calculation_timestamp || '1970-01-01';
|
||||||
|
|
||||||
// Get total count of products needing updates
|
// We now receive totalProducts as an argument, so we don't need to query for it here.
|
||||||
if (!totalProducts) {
|
|
||||||
const [productCount] = await connection.query(`
|
|
||||||
SELECT COUNT(DISTINCT p.pid) as count
|
|
||||||
FROM products p
|
|
||||||
LEFT JOIN orders o ON p.pid = o.pid AND o.updated > ?
|
|
||||||
WHERE p.updated > ?
|
|
||||||
OR o.pid IS NOT NULL
|
|
||||||
`, [lastCalculationTime, lastCalculationTime]);
|
|
||||||
totalProducts = productCount[0].count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalProducts === 0) {
|
if (totalProducts === 0) {
|
||||||
console.log('No products need time aggregate updates');
|
console.log('No products need time aggregate updates');
|
||||||
@@ -41,7 +32,7 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
operation: 'Time aggregates calculation cancelled',
|
operation: 'Time aggregates calculation cancelled',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalProducts,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: null,
|
remaining: null,
|
||||||
@@ -54,7 +45,7 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts, // Return only what *this* module processed
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -64,7 +55,7 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Starting time aggregates calculation',
|
operation: 'Starting time aggregates calculation',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalProducts,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
||||||
@@ -253,17 +244,17 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_time_aggregates');
|
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_time_aggregates');
|
||||||
|
|
||||||
lastPid = batch[batch.length - 1].pid;
|
lastPid = batch[batch.length - 1].pid;
|
||||||
processedCount += batch.length;
|
myProcessedProducts += batch.length; // Increment *this module's* count
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Processing time aggregates batch',
|
operation: 'Processing time aggregates batch',
|
||||||
current: processedCount,
|
current: processedCount + myProcessedProducts, // Show cumulative progress
|
||||||
total: totalProducts,
|
total: totalProducts,
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalProducts),
|
remaining: estimateRemaining(startTime, processedCount + myProcessedProducts, totalProducts),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount + myProcessedProducts),
|
||||||
percentage: ((processedCount / totalProducts) * 100).toFixed(1),
|
percentage: (((processedCount + myProcessedProducts) / totalProducts) * 100).toFixed(1),
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -283,7 +274,7 @@ async function calculateTimeAggregates(startTime, totalProducts, processedCount
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: myProcessedProducts, // Return only what *this* module processed
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
const connection = await getConnection();
|
const connection = await getConnection();
|
||||||
let success = false;
|
let success = false;
|
||||||
const BATCH_SIZE = 5000;
|
const BATCH_SIZE = 5000;
|
||||||
|
let myProcessedProducts = 0; // Not directly processing products, but we'll track vendors
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get last calculation timestamp
|
// Get last calculation timestamp
|
||||||
@@ -33,12 +34,12 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
`, [lastCalculationTime, lastCalculationTime]);
|
`, [lastCalculationTime, lastCalculationTime]);
|
||||||
const totalVendors = vendorCount[0].count;
|
const totalVendors = vendorCount[0].count; // Track total *vendors*
|
||||||
|
|
||||||
if (totalVendors === 0) {
|
if (totalVendors === 0) {
|
||||||
console.log('No vendors need metric updates');
|
console.log('No vendors need metric updates');
|
||||||
return {
|
return {
|
||||||
processedProducts: 0,
|
processedProducts: 0, // No products directly processed
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success: true
|
success: true
|
||||||
@@ -49,12 +50,12 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
operation: 'Vendor metrics calculation cancelled',
|
operation: 'Vendor metrics calculation cancelled',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value (for consistency)
|
||||||
total: totalVendors,
|
total: totalVendors, // Report total *vendors*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: null,
|
remaining: null,
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalVendors) * 100).toFixed(1),
|
percentage: ((processedCount / totalVendors) * 100).toFixed(1), // Base on vendors
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -62,7 +63,7 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: 0, // No products directly processed
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
@@ -72,12 +73,12 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Starting vendor metrics calculation',
|
operation: 'Starting vendor metrics calculation',
|
||||||
current: processedCount,
|
current: processedCount, // Use passed-in value
|
||||||
total: totalVendors,
|
total: totalVendors, // Report total *vendors*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalVendors),
|
remaining: estimateRemaining(startTime, processedCount, totalVendors),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount),
|
||||||
percentage: ((processedCount / totalVendors) * 100).toFixed(1),
|
percentage: ((processedCount / totalVendors) * 100).toFixed(1), // Base on vendors
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -87,6 +88,7 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
|
|
||||||
// Process in batches
|
// Process in batches
|
||||||
let lastVendor = '';
|
let lastVendor = '';
|
||||||
|
let processedVendors = 0; // Track processed vendors
|
||||||
while (true) {
|
while (true) {
|
||||||
if (isCancelled) break;
|
if (isCancelled) break;
|
||||||
|
|
||||||
@@ -253,17 +255,17 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_product_stats');
|
await connection.query('DROP TEMPORARY TABLE IF EXISTS temp_product_stats');
|
||||||
|
|
||||||
lastVendor = batch[batch.length - 1].vendor;
|
lastVendor = batch[batch.length - 1].vendor;
|
||||||
processedCount += batch.length;
|
processedVendors += batch.length; // Increment processed *vendors*
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
operation: 'Processing vendor metrics batch',
|
operation: 'Processing vendor metrics batch',
|
||||||
current: processedCount,
|
current: processedCount + processedVendors, // Use cumulative vendor count
|
||||||
total: totalVendors,
|
total: totalVendors, // Report total *vendors*
|
||||||
elapsed: formatElapsedTime(startTime),
|
elapsed: formatElapsedTime(startTime),
|
||||||
remaining: estimateRemaining(startTime, processedCount, totalVendors),
|
remaining: estimateRemaining(startTime, processedCount + processedVendors, totalVendors),
|
||||||
rate: calculateRate(startTime, processedCount),
|
rate: calculateRate(startTime, processedCount + processedVendors),
|
||||||
percentage: ((processedCount / totalVendors) * 100).toFixed(1),
|
percentage: (((processedCount + processedVendors) / totalVendors) * 100).toFixed(1), // Base on vendors
|
||||||
timing: {
|
timing: {
|
||||||
start_time: new Date(startTime).toISOString(),
|
start_time: new Date(startTime).toISOString(),
|
||||||
end_time: new Date().toISOString(),
|
end_time: new Date().toISOString(),
|
||||||
@@ -283,7 +285,7 @@ async function calculateVendorMetrics(startTime, totalProducts, processedCount =
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processedProducts: processedCount,
|
processedProducts: 0, // No products directly processed
|
||||||
processedOrders: 0,
|
processedOrders: 0,
|
||||||
processedPurchaseOrders: 0,
|
processedPurchaseOrders: 0,
|
||||||
success
|
success
|
||||||
|
|||||||
Reference in New Issue
Block a user