Fix/add data to PO script
This commit is contained in:
@@ -137,7 +137,9 @@ CREATE TABLE purchase_orders (
|
|||||||
expected_date DATE,
|
expected_date DATE,
|
||||||
pid BIGINT NOT NULL,
|
pid BIGINT NOT NULL,
|
||||||
sku VARCHAR(50) NOT NULL,
|
sku VARCHAR(50) NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL COMMENT 'Product name from products.description',
|
||||||
cost_price DECIMAL(10, 3) NOT NULL,
|
cost_price DECIMAL(10, 3) NOT NULL,
|
||||||
|
po_cost_price DECIMAL(10, 3) NOT NULL COMMENT 'Original cost from PO, before receiving adjustments',
|
||||||
status TINYINT UNSIGNED DEFAULT 1 COMMENT '0=canceled,1=created,10=electronically_ready_send,11=ordered,12=preordered,13=electronically_sent,15=receiving_started,50=done',
|
status TINYINT UNSIGNED DEFAULT 1 COMMENT '0=canceled,1=created,10=electronically_ready_send,11=ordered,12=preordered,13=electronically_sent,15=receiving_started,50=done',
|
||||||
receiving_status TINYINT UNSIGNED DEFAULT 1 COMMENT '0=canceled,1=created,30=partial_received,40=full_received,50=paid',
|
receiving_status TINYINT UNSIGNED DEFAULT 1 COMMENT '0=canceled,1=created,30=partial_received,40=full_received,50=paid',
|
||||||
notes TEXT,
|
notes TEXT,
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ const importPurchaseOrders = require('./import/purchase-orders');
|
|||||||
dotenv.config({ path: path.join(__dirname, "../.env") });
|
dotenv.config({ path: path.join(__dirname, "../.env") });
|
||||||
|
|
||||||
// Constants to control which imports run
|
// Constants to control which imports run
|
||||||
const IMPORT_CATEGORIES = true;
|
const IMPORT_CATEGORIES = false;
|
||||||
const IMPORT_PRODUCTS = true;
|
const IMPORT_PRODUCTS = false;
|
||||||
const IMPORT_ORDERS = true;
|
const IMPORT_ORDERS = false;
|
||||||
const IMPORT_PURCHASE_ORDERS = true;
|
const IMPORT_PURCHASE_ORDERS = true;
|
||||||
|
|
||||||
// Add flag for incremental updates
|
// Add flag for incremental updates
|
||||||
|
|||||||
@@ -108,31 +108,62 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
NULLIF(s2.companyname, ''),
|
NULLIF(s2.companyname, ''),
|
||||||
'Unknown Vendor'
|
'Unknown Vendor'
|
||||||
) as vendor,
|
) as vendor,
|
||||||
CASE WHEN p.po_id IS NOT NULL THEN DATE(p.date_ordered) END as date,
|
CASE
|
||||||
CASE WHEN p.po_id IS NOT NULL THEN DATE(p.date_estin) END as expected_date,
|
WHEN p.po_id IS NOT NULL THEN
|
||||||
|
DATE(COALESCE(
|
||||||
|
NULLIF(p.date_ordered, '0000-00-00 00:00:00'),
|
||||||
|
p.date_created
|
||||||
|
))
|
||||||
|
WHEN r.receiving_id IS NOT NULL THEN
|
||||||
|
DATE(r.date_created)
|
||||||
|
END as date,
|
||||||
|
NULLIF(p.date_estin, '0000-00-00') as expected_date,
|
||||||
COALESCE(p.status, 50) as status,
|
COALESCE(p.status, 50) as status,
|
||||||
COALESCE(p.short_note, '') as notes,
|
p.short_note as notes,
|
||||||
COALESCE(p.notes, '') as long_note
|
p.notes as long_note
|
||||||
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 ${incrementalUpdate ? '1' : '5'} YEAR)
|
WHERE date_ordered >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
|
||||||
AND (date_ordered > ?
|
${incrementalUpdate ? `
|
||||||
OR date_updated > ?)
|
AND (
|
||||||
|
date_ordered > ?
|
||||||
|
OR date_updated > ?
|
||||||
|
OR date_estin > ?
|
||||||
|
)
|
||||||
|
` : ''}
|
||||||
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 ${incrementalUpdate ? '1' : '5'} YEAR)
|
WHERE rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL ${incrementalUpdate ? '1' : '5'} YEAR)
|
||||||
AND (rp.received_date > ?
|
${incrementalUpdate ? `
|
||||||
OR rp.stamp > ?)
|
AND (
|
||||||
|
r.date_created > ?
|
||||||
|
OR r.date_checked > ?
|
||||||
|
OR rp.stamp > ?
|
||||||
|
OR rp.received_date > ?
|
||||||
|
)
|
||||||
|
` : ''}
|
||||||
) ids
|
) ids
|
||||||
LEFT JOIN po p ON ids.po_id = p.po_id
|
LEFT JOIN po p ON ids.po_id = p.po_id
|
||||||
LEFT JOIN suppliers s1 ON p.supplier_id = s1.supplierid
|
LEFT JOIN suppliers s1 ON p.supplier_id = s1.supplierid
|
||||||
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]);
|
`, incrementalUpdate ? [
|
||||||
|
lastSyncTime, lastSyncTime, lastSyncTime, // PO conditions
|
||||||
|
lastSyncTime, lastSyncTime, lastSyncTime, lastSyncTime // Receiving conditions
|
||||||
|
] : []);
|
||||||
|
|
||||||
|
console.log('Sample PO dates:', poList.slice(0, 5).map(po => ({
|
||||||
|
po_id: po.po_id,
|
||||||
|
raw_date_ordered: po.raw_date_ordered,
|
||||||
|
raw_date_created: po.raw_date_created,
|
||||||
|
raw_date_estin: po.raw_date_estin,
|
||||||
|
computed_date: po.date,
|
||||||
|
expected_date: po.expected_date
|
||||||
|
})));
|
||||||
|
|
||||||
const totalItems = total;
|
const totalItems = total;
|
||||||
let processed = 0;
|
let processed = 0;
|
||||||
@@ -156,7 +187,8 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
pop.po_id,
|
pop.po_id,
|
||||||
pop.pid,
|
pop.pid,
|
||||||
pr.itemnumber as sku,
|
pr.itemnumber as sku,
|
||||||
pop.cost_each as cost_price,
|
pr.description as name,
|
||||||
|
pop.cost_each,
|
||||||
pop.qty_each as ordered
|
pop.qty_each as ordered
|
||||||
FROM po_products pop
|
FROM po_products pop
|
||||||
USE INDEX (PRIMARY)
|
USE INDEX (PRIMARY)
|
||||||
@@ -171,7 +203,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const productPids = [...new Set(productBatch.map(p => p.pid))];
|
const productPids = [...new Set(productBatch.map(p => p.pid))];
|
||||||
const batchPoIds = [...new Set(productBatch.map(p => p.po_id))];
|
const batchPoIds = [...new Set(productBatch.map(p => p.po_id))];
|
||||||
|
|
||||||
// Get receivings for this batch
|
// Get receivings for this batch with employee names
|
||||||
const [receivings] = await prodConnection.query(`
|
const [receivings] = await prodConnection.query(`
|
||||||
SELECT
|
SELECT
|
||||||
r.po_id,
|
r.po_id,
|
||||||
@@ -179,8 +211,9 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
rp.receiving_id,
|
rp.receiving_id,
|
||||||
rp.qty_each,
|
rp.qty_each,
|
||||||
rp.cost_each,
|
rp.cost_each,
|
||||||
DATE(NULLIF(rp.received_date, '0000-00-00 00:00:00')) as received_date,
|
COALESCE(rp.received_date, r.date_created) as received_date,
|
||||||
rp.received_by,
|
rp.received_by,
|
||||||
|
CONCAT(e.firstname, ' ', e.lastname) as received_by_name,
|
||||||
CASE
|
CASE
|
||||||
WHEN r.po_id IS NULL THEN 2 -- No PO
|
WHEN r.po_id IS NULL THEN 2 -- No PO
|
||||||
WHEN r.po_id IN (?) THEN 0 -- Original PO
|
WHEN r.po_id IN (?) THEN 0 -- Original PO
|
||||||
@@ -189,8 +222,9 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
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
|
||||||
|
LEFT JOIN employees e ON rp.received_by = e.employeeid
|
||||||
WHERE rp.pid IN (?)
|
WHERE rp.pid IN (?)
|
||||||
AND rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 2 YEAR)
|
AND rp.received_date >= DATE_SUB(CURRENT_DATE, INTERVAL 5 YEAR)
|
||||||
ORDER BY r.po_id, rp.pid, rp.received_date
|
ORDER BY r.po_id, rp.pid, rp.received_date
|
||||||
`, [batchPoIds, productPids]);
|
`, [batchPoIds, productPids]);
|
||||||
|
|
||||||
@@ -235,10 +269,6 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
);
|
);
|
||||||
const validPids = new Set(existingPids.map(p => p.pid));
|
const validPids = new Set(existingPids.map(p => p.pid));
|
||||||
|
|
||||||
// Prepare values for this sub-batch
|
|
||||||
const values = [];
|
|
||||||
let batchProcessed = 0;
|
|
||||||
|
|
||||||
// First check which PO lines already exist and get their current values
|
// First check which PO lines already exist and get their current values
|
||||||
const poLines = Array.from(poProductMap.values())
|
const poLines = Array.from(poProductMap.values())
|
||||||
.filter(p => validPids.has(p.pid))
|
.filter(p => validPids.has(p.pid))
|
||||||
@@ -254,6 +284,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
|
|
||||||
// Split into inserts and updates
|
// Split into inserts and updates
|
||||||
const insertsAndUpdates = { inserts: [], updates: [] };
|
const insertsAndUpdates = { inserts: [], updates: [] };
|
||||||
|
let batchProcessed = 0;
|
||||||
|
|
||||||
for (const po of batch) {
|
for (const po of batch) {
|
||||||
const poProducts = Array.from(poProductMap.values())
|
const poProducts = Array.from(poProductMap.values())
|
||||||
@@ -270,16 +301,29 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
...receivingHistory.map(r => ({ ...r, type: 'original' })),
|
...receivingHistory.map(r => ({ ...r, type: 'original' })),
|
||||||
...altReceivingHistory.map(r => ({ ...r, type: 'alternate' })),
|
...altReceivingHistory.map(r => ({ ...r, type: 'alternate' })),
|
||||||
...noPOReceivingHistory.map(r => ({ ...r, type: 'no_po' }))
|
...noPOReceivingHistory.map(r => ({ ...r, type: 'no_po' }))
|
||||||
].sort((a, b) => new Date(a.received_date) - new Date(b.received_date));
|
].sort((a, b) => new Date(a.received_date || '9999-12-31') - new Date(b.received_date || '9999-12-31'));
|
||||||
|
|
||||||
|
// Split receivings into original PO and others
|
||||||
|
const originalPOReceivings = allReceivings.filter(r => r.type === 'original');
|
||||||
|
const otherReceivings = allReceivings.filter(r => r.type !== 'original');
|
||||||
|
|
||||||
// Track FIFO fulfillment
|
// Track FIFO fulfillment
|
||||||
let remainingToFulfill = product.ordered;
|
let remainingToFulfill = product.ordered;
|
||||||
const fulfillmentTracking = [];
|
const fulfillmentTracking = [];
|
||||||
let totalReceived = 0;
|
let totalReceived = 0;
|
||||||
|
let actualCost = null; // Will store the cost of the first receiving that fulfills this PO
|
||||||
|
let firstFulfillmentReceiving = null;
|
||||||
|
let lastFulfillmentReceiving = null;
|
||||||
|
|
||||||
for (const receiving of allReceivings) {
|
for (const receiving of allReceivings) {
|
||||||
const qtyToApply = Math.min(remainingToFulfill, receiving.qty_each);
|
const qtyToApply = Math.min(remainingToFulfill, receiving.qty_each);
|
||||||
if (qtyToApply > 0) {
|
if (qtyToApply > 0) {
|
||||||
|
// If this is the first receiving being applied, use its cost
|
||||||
|
if (actualCost === null) {
|
||||||
|
actualCost = receiving.cost_each;
|
||||||
|
firstFulfillmentReceiving = receiving;
|
||||||
|
}
|
||||||
|
lastFulfillmentReceiving = receiving;
|
||||||
fulfillmentTracking.push({
|
fulfillmentTracking.push({
|
||||||
receiving_id: receiving.receiving_id,
|
receiving_id: receiving.receiving_id,
|
||||||
qty_applied: qtyToApply,
|
qty_applied: qtyToApply,
|
||||||
@@ -287,6 +331,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
cost: receiving.cost_each,
|
cost: receiving.cost_each,
|
||||||
date: receiving.received_date,
|
date: receiving.received_date,
|
||||||
received_by: receiving.received_by,
|
received_by: receiving.received_by,
|
||||||
|
received_by_name: receiving.received_by_name || 'Unknown',
|
||||||
type: receiving.type,
|
type: receiving.type,
|
||||||
remaining_qty: receiving.qty_each - qtyToApply
|
remaining_qty: receiving.qty_each - qtyToApply
|
||||||
});
|
});
|
||||||
@@ -300,29 +345,40 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
cost: receiving.cost_each,
|
cost: receiving.cost_each,
|
||||||
date: receiving.received_date,
|
date: receiving.received_date,
|
||||||
received_by: receiving.received_by,
|
received_by: receiving.received_by,
|
||||||
|
received_by_name: receiving.received_by_name || 'Unknown',
|
||||||
type: receiving.type,
|
type: receiving.type,
|
||||||
is_excess: true
|
is_excess: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
totalReceived += receiving.qty_each;
|
totalReceived += receiving.qty_each;
|
||||||
}
|
}
|
||||||
|
|
||||||
const receiving_status = !totalReceived ? 1 : // created
|
const receiving_status = !totalReceived ? 1 : // created
|
||||||
remainingToFulfill > 0 ? 30 : // partial
|
remainingToFulfill > 0 ? 30 : // partial
|
||||||
40; // full
|
40; // full
|
||||||
|
|
||||||
const firstReceiving = allReceivings[0] || {};
|
function formatDate(dateStr) {
|
||||||
const lastReceiving = allReceivings[allReceivings.length - 1] || {};
|
if (!dateStr) return null;
|
||||||
|
try {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
if (isNaN(date.getTime())) return null;
|
||||||
|
return date.toISOString().split('T')[0];
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rowValues = columnNames.map(col => {
|
const rowValues = columnNames.map(col => {
|
||||||
switch (col) {
|
switch (col) {
|
||||||
case 'po_id': return po.po_id;
|
case 'po_id': return po.po_id;
|
||||||
case 'vendor': return po.vendor;
|
case 'vendor': return po.vendor;
|
||||||
case 'date': return po.date;
|
case 'date': return formatDate(po.date);
|
||||||
case 'expected_date': return po.expected_date;
|
case 'expected_date': return formatDate(po.expected_date);
|
||||||
case 'pid': return product.pid;
|
case 'pid': return product.pid;
|
||||||
case 'sku': return product.sku;
|
case 'sku': return product.sku;
|
||||||
case 'cost_price': return product.cost_price;
|
case 'name': return product.name;
|
||||||
|
case 'cost_price': return actualCost || product.cost_each;
|
||||||
|
case 'po_cost_price': return product.cost_each;
|
||||||
case 'status': return po.status;
|
case 'status': return po.status;
|
||||||
case 'notes': return po.notes;
|
case 'notes': return po.notes;
|
||||||
case 'long_note': return po.long_note;
|
case 'long_note': return po.long_note;
|
||||||
@@ -330,16 +386,18 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
case 'received': return totalReceived;
|
case 'received': return totalReceived;
|
||||||
case 'unfulfilled': return remainingToFulfill;
|
case 'unfulfilled': return remainingToFulfill;
|
||||||
case 'excess_received': return Math.max(0, totalReceived - product.ordered);
|
case 'excess_received': return Math.max(0, totalReceived - product.ordered);
|
||||||
case 'received_date': return firstReceiving.received_date || null;
|
case 'received_date': return formatDate(firstFulfillmentReceiving?.received_date);
|
||||||
case 'last_received_date': return lastReceiving.received_date || null;
|
case 'last_received_date': return formatDate(lastFulfillmentReceiving?.received_date);
|
||||||
case 'received_by': return firstReceiving.received_by || null;
|
case 'received_by': return firstFulfillmentReceiving?.received_by_name || null;
|
||||||
case 'receiving_status': return receiving_status;
|
case 'receiving_status': return receiving_status;
|
||||||
case 'receiving_history': return JSON.stringify({
|
case 'receiving_history': return JSON.stringify({
|
||||||
fulfillment: fulfillmentTracking,
|
fulfillment: fulfillmentTracking,
|
||||||
ordered_qty: product.ordered,
|
ordered_qty: product.ordered,
|
||||||
total_received: totalReceived,
|
total_received: totalReceived,
|
||||||
remaining_unfulfilled: remainingToFulfill,
|
remaining_unfulfilled: remainingToFulfill,
|
||||||
excess_received: Math.max(0, totalReceived - product.ordered)
|
excess_received: Math.max(0, totalReceived - product.ordered),
|
||||||
|
po_cost: product.cost_each,
|
||||||
|
actual_cost: actualCost || product.cost_each
|
||||||
});
|
});
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user