Enhance purchase order import with advanced receiving tracking and fulfillment logic

- Implement FIFO-based receiving fulfillment tracking
- Add detailed receiving history with excess and partial fulfillment support
- Improve vendor name resolution and fallback handling
- Optimize incremental update queries by removing redundant conditions
- Enhance receiving status calculation with more granular tracking
This commit is contained in:
2025-01-31 01:25:48 -05:00
parent b506f89dd7
commit 1be97d6610
2 changed files with 65 additions and 37 deletions

View File

@@ -80,9 +80,10 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
oi.qty_ordered as quantity,
COALESCE(oi.prod_price_reg - oi.prod_price, 0) * oi.qty_ordered as base_discount
FROM order_items oi
USE INDEX (PRIMARY)
JOIN _order o ON oi.order_id = o.order_id
WHERE o.order_status >= 15
AND o.date_placed_onlydate >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
AND o.date_placed_onlydate >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
AND o.date_placed_onlydate IS NOT NULL
${incrementalUpdate ? `
AND (

View File

@@ -30,7 +30,6 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
const incrementalWhereClause = incrementalUpdate
? `AND (
p.date_updated > ?
OR p.date_modified > ?
OR p.date_ordered > ?
OR p.date_estin > ?
OR r.stamp > ?
@@ -39,7 +38,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
)`
: "";
const incrementalParams = incrementalUpdate
? [lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime]
? [lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime]
: [];
// First get all relevant PO IDs with basic info
@@ -51,14 +50,14 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
USE INDEX (idx_date_created)
JOIN po_products pop ON p.po_id = pop.po_id
JOIN suppliers s ON p.supplier_id = s.supplierid
WHERE p.date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
WHERE p.date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
${incrementalWhereClause}
UNION
SELECT DISTINCT r.receiving_id as po_id, rp.pid
FROM receivings_products rp
USE INDEX (received_date)
LEFT JOIN receivings r ON r.receiving_id = rp.receiving_id
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
${incrementalWhereClause}
) all_items
`, [...incrementalParams, ...incrementalParams]);
@@ -66,11 +65,11 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
const [poList] = await prodConnection.query(`
SELECT DISTINCT
COALESCE(p.po_id, r.receiving_id) as po_id,
CASE
WHEN p.po_id IS NOT NULL THEN s1.companyname
WHEN r.supplier_id IS NOT NULL THEN s2.companyname
ELSE 'No Supplier'
END as vendor,
COALESCE(
NULLIF(s1.companyname, ''),
NULLIF(s2.companyname, ''),
'Unknown Vendor'
) as vendor,
CASE WHEN p.po_id IS NOT NULL THEN DATE(p.date_ordered) END as date,
CASE WHEN p.po_id IS NOT NULL THEN DATE(p.date_estin) END as expected_date,
COALESCE(p.status, 50) as status,
@@ -79,15 +78,14 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
FROM (
SELECT po_id FROM po
USE INDEX (idx_date_created)
WHERE date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
WHERE date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
AND (date_ordered > ?
OR date_updated > ?
OR date_modified > ?)
OR date_updated > ?)
UNION
SELECT DISTINCT r.receiving_id as po_id
FROM receivings r
JOIN receivings_products rp USE INDEX (received_date) ON r.receiving_id = rp.receiving_id
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
AND (rp.received_date > ?
OR rp.stamp > ?)
) ids
@@ -96,7 +94,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
LEFT JOIN receivings r ON ids.po_id = r.receiving_id
LEFT JOIN suppliers s2 ON r.supplier_id = s2.supplierid
ORDER BY po_id
`, [lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime]);
`, [lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime]);
const totalItems = total;
let processed = 0;
@@ -215,24 +213,52 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
const altReceivingHistory = altReceivingMap.get(product.pid) || [];
const noPOReceivingHistory = noPOReceivingMap.get(product.pid) || [];
const received = receivingHistory.reduce((sum, r) => sum + r.qty_each, 0);
const altReceived = altReceivingHistory.reduce((sum, r) => sum + r.qty_each, 0);
const noPOReceived = noPOReceivingHistory.reduce((sum, r) => sum + r.qty_each, 0);
const totalReceived = received + altReceived + noPOReceived;
// Combine all receivings and sort by date
const allReceivings = [
...receivingHistory.map(r => ({ ...r, type: 'original' })),
...altReceivingHistory.map(r => ({ ...r, type: 'alternate' })),
...noPOReceivingHistory.map(r => ({ ...r, type: 'no_po' }))
].sort((a, b) => new Date(a.received_date) - new Date(b.received_date));
// Track FIFO fulfillment
let remainingToFulfill = product.ordered;
const fulfillmentTracking = [];
let totalReceived = 0;
for (const receiving of allReceivings) {
const qtyToApply = Math.min(remainingToFulfill, receiving.qty_each);
if (qtyToApply > 0) {
fulfillmentTracking.push({
receiving_id: receiving.receiving_id,
qty_applied: qtyToApply,
qty_total: receiving.qty_each,
cost: receiving.cost_each,
date: receiving.received_date,
received_by: receiving.received_by,
type: receiving.type,
remaining_qty: receiving.qty_each - qtyToApply
});
remainingToFulfill -= qtyToApply;
} else {
// Track excess receivings
fulfillmentTracking.push({
receiving_id: receiving.receiving_id,
qty_applied: 0,
qty_total: receiving.qty_each,
cost: receiving.cost_each,
date: receiving.received_date,
received_by: receiving.received_by,
type: receiving.type,
is_excess: true
});
}
totalReceived += receiving.qty_each;
}
const receiving_status = !totalReceived ? 1 : // created
totalReceived < product.ordered ? 30 : // partial
remainingToFulfill > 0 ? 30 : // partial
40; // full
const allReceivings = [...receivingHistory];
if (altReceivingHistory.length > 0) {
allReceivings.push(...altReceivingHistory);
}
if (noPOReceivingHistory.length > 0) {
allReceivings.push(...noPOReceivingHistory);
}
allReceivings.sort((a, b) => new Date(a.received_date) - new Date(b.received_date));
const firstReceiving = allReceivings[0] || {};
const lastReceiving = allReceivings[allReceivings.length - 1] || {};
@@ -250,18 +276,19 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
case 'long_note': return po.long_note;
case 'ordered': return product.ordered;
case 'received': return totalReceived;
case 'unfulfilled': return remainingToFulfill;
case 'excess_received': return Math.max(0, totalReceived - product.ordered);
case 'received_date': return firstReceiving.received_date || null;
case 'last_received_date': return lastReceiving.received_date || null;
case 'received_by': return firstReceiving.received_by || null;
case 'receiving_status': return receiving_status;
case 'receiving_history': return JSON.stringify(allReceivings.map(r => ({
receiving_id: r.receiving_id,
qty: r.qty_each,
cost: r.cost_each,
date: r.received_date,
received_by: r.received_by,
alt_po: r.is_alt_po
})));
case 'receiving_history': return JSON.stringify({
fulfillment: fulfillmentTracking,
ordered_qty: product.ordered,
total_received: totalReceived,
remaining_unfulfilled: remainingToFulfill,
excess_received: Math.max(0, totalReceived - product.ordered)
});
default: return null;
}
}));