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:
@@ -80,9 +80,10 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
|
|||||||
oi.qty_ordered as quantity,
|
oi.qty_ordered as quantity,
|
||||||
COALESCE(oi.prod_price_reg - oi.prod_price, 0) * oi.qty_ordered as base_discount
|
COALESCE(oi.prod_price_reg - oi.prod_price, 0) * oi.qty_ordered as base_discount
|
||||||
FROM order_items oi
|
FROM order_items oi
|
||||||
|
USE INDEX (PRIMARY)
|
||||||
JOIN _order o ON oi.order_id = o.order_id
|
JOIN _order o ON oi.order_id = o.order_id
|
||||||
WHERE o.order_status >= 15
|
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
|
AND o.date_placed_onlydate IS NOT NULL
|
||||||
${incrementalUpdate ? `
|
${incrementalUpdate ? `
|
||||||
AND (
|
AND (
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const incrementalWhereClause = incrementalUpdate
|
const incrementalWhereClause = incrementalUpdate
|
||||||
? `AND (
|
? `AND (
|
||||||
p.date_updated > ?
|
p.date_updated > ?
|
||||||
OR p.date_modified > ?
|
|
||||||
OR p.date_ordered > ?
|
OR p.date_ordered > ?
|
||||||
OR p.date_estin > ?
|
OR p.date_estin > ?
|
||||||
OR r.stamp > ?
|
OR r.stamp > ?
|
||||||
@@ -39,7 +38,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
)`
|
)`
|
||||||
: "";
|
: "";
|
||||||
const incrementalParams = incrementalUpdate
|
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
|
// First get all relevant PO IDs with basic info
|
||||||
@@ -51,14 +50,14 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
USE INDEX (idx_date_created)
|
USE INDEX (idx_date_created)
|
||||||
JOIN po_products pop ON p.po_id = pop.po_id
|
JOIN po_products pop ON p.po_id = pop.po_id
|
||||||
JOIN suppliers s ON p.supplier_id = s.supplierid
|
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}
|
${incrementalWhereClause}
|
||||||
UNION
|
UNION
|
||||||
SELECT DISTINCT r.receiving_id as po_id, rp.pid
|
SELECT DISTINCT r.receiving_id as po_id, rp.pid
|
||||||
FROM receivings_products rp
|
FROM receivings_products rp
|
||||||
USE INDEX (received_date)
|
USE INDEX (received_date)
|
||||||
LEFT JOIN receivings r ON r.receiving_id = rp.receiving_id
|
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}
|
${incrementalWhereClause}
|
||||||
) all_items
|
) all_items
|
||||||
`, [...incrementalParams, ...incrementalParams]);
|
`, [...incrementalParams, ...incrementalParams]);
|
||||||
@@ -66,11 +65,11 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const [poList] = await prodConnection.query(`
|
const [poList] = await prodConnection.query(`
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
COALESCE(p.po_id, r.receiving_id) as po_id,
|
COALESCE(p.po_id, r.receiving_id) as po_id,
|
||||||
CASE
|
COALESCE(
|
||||||
WHEN p.po_id IS NOT NULL THEN s1.companyname
|
NULLIF(s1.companyname, ''),
|
||||||
WHEN r.supplier_id IS NOT NULL THEN s2.companyname
|
NULLIF(s2.companyname, ''),
|
||||||
ELSE 'No Supplier'
|
'Unknown Vendor'
|
||||||
END as 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_ordered) END as date,
|
||||||
CASE WHEN p.po_id IS NOT NULL THEN DATE(p.date_estin) END as expected_date,
|
CASE WHEN p.po_id IS NOT NULL THEN DATE(p.date_estin) END as expected_date,
|
||||||
COALESCE(p.status, 50) as status,
|
COALESCE(p.status, 50) as status,
|
||||||
@@ -79,15 +78,14 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
FROM (
|
FROM (
|
||||||
SELECT po_id FROM po
|
SELECT po_id FROM po
|
||||||
USE INDEX (idx_date_created)
|
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 > ?
|
AND (date_ordered > ?
|
||||||
OR date_updated > ?
|
OR date_updated > ?)
|
||||||
OR date_modified > ?)
|
|
||||||
UNION
|
UNION
|
||||||
SELECT DISTINCT r.receiving_id as po_id
|
SELECT DISTINCT r.receiving_id as po_id
|
||||||
FROM receivings r
|
FROM receivings r
|
||||||
JOIN receivings_products rp USE INDEX (received_date) ON r.receiving_id = rp.receiving_id
|
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 > ?
|
AND (rp.received_date > ?
|
||||||
OR rp.stamp > ?)
|
OR rp.stamp > ?)
|
||||||
) ids
|
) ids
|
||||||
@@ -96,7 +94,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
LEFT JOIN receivings r ON ids.po_id = r.receiving_id
|
LEFT JOIN receivings r ON ids.po_id = r.receiving_id
|
||||||
LEFT JOIN suppliers s2 ON r.supplier_id = s2.supplierid
|
LEFT JOIN suppliers s2 ON r.supplier_id = s2.supplierid
|
||||||
ORDER BY po_id
|
ORDER BY po_id
|
||||||
`, [lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime]);
|
`, [lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime]);
|
||||||
|
|
||||||
const totalItems = total;
|
const totalItems = total;
|
||||||
let processed = 0;
|
let processed = 0;
|
||||||
@@ -215,24 +213,52 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const altReceivingHistory = altReceivingMap.get(product.pid) || [];
|
const altReceivingHistory = altReceivingMap.get(product.pid) || [];
|
||||||
const noPOReceivingHistory = noPOReceivingMap.get(product.pid) || [];
|
const noPOReceivingHistory = noPOReceivingMap.get(product.pid) || [];
|
||||||
|
|
||||||
const received = receivingHistory.reduce((sum, r) => sum + r.qty_each, 0);
|
// Combine all receivings and sort by date
|
||||||
const altReceived = altReceivingHistory.reduce((sum, r) => sum + r.qty_each, 0);
|
const allReceivings = [
|
||||||
const noPOReceived = noPOReceivingHistory.reduce((sum, r) => sum + r.qty_each, 0);
|
...receivingHistory.map(r => ({ ...r, type: 'original' })),
|
||||||
const totalReceived = received + altReceived + noPOReceived;
|
...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
|
const receiving_status = !totalReceived ? 1 : // created
|
||||||
totalReceived < product.ordered ? 30 : // partial
|
remainingToFulfill > 0 ? 30 : // partial
|
||||||
40; // full
|
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 firstReceiving = allReceivings[0] || {};
|
||||||
const lastReceiving = allReceivings[allReceivings.length - 1] || {};
|
const lastReceiving = allReceivings[allReceivings.length - 1] || {};
|
||||||
|
|
||||||
@@ -250,18 +276,19 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
case 'long_note': return po.long_note;
|
case 'long_note': return po.long_note;
|
||||||
case 'ordered': return product.ordered;
|
case 'ordered': return product.ordered;
|
||||||
case 'received': return totalReceived;
|
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 'received_date': return firstReceiving.received_date || null;
|
||||||
case 'last_received_date': return lastReceiving.received_date || null;
|
case 'last_received_date': return lastReceiving.received_date || null;
|
||||||
case 'received_by': return firstReceiving.received_by || null;
|
case 'received_by': return firstReceiving.received_by || null;
|
||||||
case 'receiving_status': return receiving_status;
|
case 'receiving_status': return receiving_status;
|
||||||
case 'receiving_history': return JSON.stringify(allReceivings.map(r => ({
|
case 'receiving_history': return JSON.stringify({
|
||||||
receiving_id: r.receiving_id,
|
fulfillment: fulfillmentTracking,
|
||||||
qty: r.qty_each,
|
ordered_qty: product.ordered,
|
||||||
cost: r.cost_each,
|
total_received: totalReceived,
|
||||||
date: r.received_date,
|
remaining_unfulfilled: remainingToFulfill,
|
||||||
received_by: r.received_by,
|
excess_received: Math.max(0, totalReceived - product.ordered)
|
||||||
alt_po: r.is_alt_po
|
});
|
||||||
})));
|
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user