Compare commits
2 Commits
4ed734e5c0
...
4cb41a7e4c
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cb41a7e4c | |||
| d05d27494d |
@@ -46,8 +46,10 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let poRecordsAdded = 0;
|
let poRecordsAdded = 0;
|
||||||
let poRecordsUpdated = 0;
|
let poRecordsUpdated = 0;
|
||||||
|
let poRecordsDeleted = 0;
|
||||||
let receivingRecordsAdded = 0;
|
let receivingRecordsAdded = 0;
|
||||||
let receivingRecordsUpdated = 0;
|
let receivingRecordsUpdated = 0;
|
||||||
|
let receivingRecordsDeleted = 0;
|
||||||
let totalProcessed = 0;
|
let totalProcessed = 0;
|
||||||
|
|
||||||
// Batch size constants
|
// Batch size constants
|
||||||
@@ -257,148 +259,153 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const totalPOs = poCount[0].total;
|
const totalPOs = poCount[0].total;
|
||||||
console.log(`Found ${totalPOs} relevant purchase orders`);
|
console.log(`Found ${totalPOs} relevant purchase orders`);
|
||||||
|
|
||||||
// Fetch and process POs in batches
|
// Skip processing if no POs to process
|
||||||
let offset = 0;
|
if (totalPOs === 0) {
|
||||||
let allPOsProcessed = false;
|
console.log('No purchase orders to process, skipping PO import step');
|
||||||
|
} else {
|
||||||
|
// Fetch and process POs in batches
|
||||||
|
let offset = 0;
|
||||||
|
let allPOsProcessed = false;
|
||||||
|
|
||||||
while (!allPOsProcessed) {
|
while (!allPOsProcessed) {
|
||||||
const [poList] = await prodConnection.query(`
|
const [poList] = await prodConnection.query(`
|
||||||
SELECT
|
SELECT
|
||||||
p.po_id,
|
p.po_id,
|
||||||
p.supplier_id,
|
p.supplier_id,
|
||||||
s.companyname AS vendor,
|
s.companyname AS vendor,
|
||||||
p.status,
|
p.status,
|
||||||
p.notes AS long_note,
|
p.notes AS long_note,
|
||||||
p.short_note AS notes,
|
p.short_note AS notes,
|
||||||
p.date_created,
|
p.date_created,
|
||||||
p.date_ordered,
|
p.date_ordered,
|
||||||
p.date_estin
|
p.date_estin
|
||||||
FROM po p
|
FROM po p
|
||||||
LEFT JOIN suppliers s ON p.supplier_id = s.supplierid
|
LEFT JOIN suppliers s ON p.supplier_id = s.supplierid
|
||||||
WHERE p.date_created >= DATE_SUB(CURRENT_DATE, INTERVAL ${yearInterval} YEAR)
|
WHERE p.date_created >= DATE_SUB(CURRENT_DATE, INTERVAL ${yearInterval} YEAR)
|
||||||
${incrementalUpdate ? `
|
${incrementalUpdate ? `
|
||||||
AND (
|
AND (
|
||||||
p.date_updated > ?
|
p.date_updated > ?
|
||||||
OR p.date_ordered > ?
|
OR p.date_ordered > ?
|
||||||
OR p.date_estin > ?
|
OR p.date_estin > ?
|
||||||
|
)
|
||||||
|
` : ''}
|
||||||
|
ORDER BY p.po_id
|
||||||
|
LIMIT ${PO_BATCH_SIZE} OFFSET ${offset}
|
||||||
|
`, incrementalUpdate ? [lastSyncTime, lastSyncTime, lastSyncTime] : []);
|
||||||
|
|
||||||
|
if (poList.length === 0) {
|
||||||
|
allPOsProcessed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get products for these POs
|
||||||
|
const poIds = poList.map(po => po.po_id);
|
||||||
|
|
||||||
|
const [poProducts] = await prodConnection.query(`
|
||||||
|
SELECT
|
||||||
|
pp.po_id,
|
||||||
|
pp.pid,
|
||||||
|
pp.qty_each,
|
||||||
|
pp.cost_each,
|
||||||
|
COALESCE(p.itemnumber, 'NO-SKU') AS sku,
|
||||||
|
COALESCE(p.description, 'Unknown Product') AS name
|
||||||
|
FROM po_products pp
|
||||||
|
LEFT JOIN products p ON pp.pid = p.pid
|
||||||
|
WHERE pp.po_id IN (?)
|
||||||
|
`, [poIds]);
|
||||||
|
|
||||||
|
// Build complete PO records
|
||||||
|
const completePOs = [];
|
||||||
|
for (const product of poProducts) {
|
||||||
|
const po = poList.find(p => p.po_id == product.po_id);
|
||||||
|
if (!po) continue;
|
||||||
|
|
||||||
|
completePOs.push({
|
||||||
|
po_id: po.po_id.toString(),
|
||||||
|
pid: product.pid,
|
||||||
|
sku: product.sku,
|
||||||
|
name: product.name,
|
||||||
|
vendor: po.vendor || 'Unknown Vendor',
|
||||||
|
date: validateDate(po.date_ordered) || validateDate(po.date_created),
|
||||||
|
expected_date: validateDate(po.date_estin),
|
||||||
|
status: poStatusMap[po.status] || 'created',
|
||||||
|
notes: po.notes || '',
|
||||||
|
long_note: po.long_note || '',
|
||||||
|
ordered: product.qty_each,
|
||||||
|
po_cost_price: product.cost_each,
|
||||||
|
supplier_id: po.supplier_id,
|
||||||
|
date_created: validateDate(po.date_created),
|
||||||
|
date_ordered: validateDate(po.date_ordered)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert PO data in batches
|
||||||
|
for (let i = 0; i < completePOs.length; i += INSERT_BATCH_SIZE) {
|
||||||
|
const batch = completePOs.slice(i, i + INSERT_BATCH_SIZE);
|
||||||
|
|
||||||
|
const placeholders = batch.map((_, idx) => {
|
||||||
|
const base = idx * 15;
|
||||||
|
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}, $${base + 6}, $${base + 7}, $${base + 8}, $${base + 9}, $${base + 10}, $${base + 11}, $${base + 12}, $${base + 13}, $${base + 14}, $${base + 15})`;
|
||||||
|
}).join(',');
|
||||||
|
|
||||||
|
const values = batch.flatMap(po => [
|
||||||
|
po.po_id,
|
||||||
|
po.pid,
|
||||||
|
po.sku,
|
||||||
|
po.name,
|
||||||
|
po.vendor,
|
||||||
|
po.date,
|
||||||
|
po.expected_date,
|
||||||
|
po.status,
|
||||||
|
po.notes,
|
||||||
|
po.long_note,
|
||||||
|
po.ordered,
|
||||||
|
po.po_cost_price,
|
||||||
|
po.supplier_id,
|
||||||
|
po.date_created,
|
||||||
|
po.date_ordered
|
||||||
|
]);
|
||||||
|
|
||||||
|
await localConnection.query(`
|
||||||
|
INSERT INTO temp_purchase_orders (
|
||||||
|
po_id, pid, sku, name, vendor, date, expected_date, status, notes, long_note,
|
||||||
|
ordered, po_cost_price, supplier_id, date_created, date_ordered
|
||||||
)
|
)
|
||||||
` : ''}
|
VALUES ${placeholders}
|
||||||
ORDER BY p.po_id
|
ON CONFLICT (po_id, pid) DO UPDATE SET
|
||||||
LIMIT ${PO_BATCH_SIZE} OFFSET ${offset}
|
sku = EXCLUDED.sku,
|
||||||
`, incrementalUpdate ? [lastSyncTime, lastSyncTime, lastSyncTime] : []);
|
name = EXCLUDED.name,
|
||||||
|
vendor = EXCLUDED.vendor,
|
||||||
|
date = EXCLUDED.date,
|
||||||
|
expected_date = EXCLUDED.expected_date,
|
||||||
|
status = EXCLUDED.status,
|
||||||
|
notes = EXCLUDED.notes,
|
||||||
|
long_note = EXCLUDED.long_note,
|
||||||
|
ordered = EXCLUDED.ordered,
|
||||||
|
po_cost_price = EXCLUDED.po_cost_price,
|
||||||
|
supplier_id = EXCLUDED.supplier_id,
|
||||||
|
date_created = EXCLUDED.date_created,
|
||||||
|
date_ordered = EXCLUDED.date_ordered
|
||||||
|
`, values);
|
||||||
|
}
|
||||||
|
|
||||||
if (poList.length === 0) {
|
offset += poList.length;
|
||||||
allPOsProcessed = true;
|
totalProcessed += completePOs.length;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get products for these POs
|
outputProgress({
|
||||||
const poIds = poList.map(po => po.po_id);
|
status: "running",
|
||||||
|
operation: "Purchase orders import",
|
||||||
const [poProducts] = await prodConnection.query(`
|
message: `Processed ${offset} of ${totalPOs} purchase orders (${totalProcessed} line items)`,
|
||||||
SELECT
|
current: offset,
|
||||||
pp.po_id,
|
total: totalPOs,
|
||||||
pp.pid,
|
elapsed: formatElapsedTime((Date.now() - startTime) / 1000),
|
||||||
pp.qty_each,
|
remaining: estimateRemaining(startTime, offset, totalPOs),
|
||||||
pp.cost_each,
|
rate: calculateRate(startTime, offset)
|
||||||
COALESCE(p.itemnumber, 'NO-SKU') AS sku,
|
|
||||||
COALESCE(p.description, 'Unknown Product') AS name
|
|
||||||
FROM po_products pp
|
|
||||||
LEFT JOIN products p ON pp.pid = p.pid
|
|
||||||
WHERE pp.po_id IN (?)
|
|
||||||
`, [poIds]);
|
|
||||||
|
|
||||||
// Build complete PO records
|
|
||||||
const completePOs = [];
|
|
||||||
for (const product of poProducts) {
|
|
||||||
const po = poList.find(p => p.po_id == product.po_id);
|
|
||||||
if (!po) continue;
|
|
||||||
|
|
||||||
completePOs.push({
|
|
||||||
po_id: po.po_id.toString(),
|
|
||||||
pid: product.pid,
|
|
||||||
sku: product.sku,
|
|
||||||
name: product.name,
|
|
||||||
vendor: po.vendor || 'Unknown Vendor',
|
|
||||||
date: validateDate(po.date_ordered) || validateDate(po.date_created),
|
|
||||||
expected_date: validateDate(po.date_estin),
|
|
||||||
status: poStatusMap[po.status] || 'created',
|
|
||||||
notes: po.notes || '',
|
|
||||||
long_note: po.long_note || '',
|
|
||||||
ordered: product.qty_each,
|
|
||||||
po_cost_price: product.cost_each,
|
|
||||||
supplier_id: po.supplier_id,
|
|
||||||
date_created: validateDate(po.date_created),
|
|
||||||
date_ordered: validateDate(po.date_ordered)
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Insert PO data in batches
|
if (poList.length < PO_BATCH_SIZE) {
|
||||||
for (let i = 0; i < completePOs.length; i += INSERT_BATCH_SIZE) {
|
allPOsProcessed = true;
|
||||||
const batch = completePOs.slice(i, i + INSERT_BATCH_SIZE);
|
}
|
||||||
|
|
||||||
const placeholders = batch.map((_, idx) => {
|
|
||||||
const base = idx * 15;
|
|
||||||
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}, $${base + 6}, $${base + 7}, $${base + 8}, $${base + 9}, $${base + 10}, $${base + 11}, $${base + 12}, $${base + 13}, $${base + 14}, $${base + 15})`;
|
|
||||||
}).join(',');
|
|
||||||
|
|
||||||
const values = batch.flatMap(po => [
|
|
||||||
po.po_id,
|
|
||||||
po.pid,
|
|
||||||
po.sku,
|
|
||||||
po.name,
|
|
||||||
po.vendor,
|
|
||||||
po.date,
|
|
||||||
po.expected_date,
|
|
||||||
po.status,
|
|
||||||
po.notes,
|
|
||||||
po.long_note,
|
|
||||||
po.ordered,
|
|
||||||
po.po_cost_price,
|
|
||||||
po.supplier_id,
|
|
||||||
po.date_created,
|
|
||||||
po.date_ordered
|
|
||||||
]);
|
|
||||||
|
|
||||||
await localConnection.query(`
|
|
||||||
INSERT INTO temp_purchase_orders (
|
|
||||||
po_id, pid, sku, name, vendor, date, expected_date, status, notes, long_note,
|
|
||||||
ordered, po_cost_price, supplier_id, date_created, date_ordered
|
|
||||||
)
|
|
||||||
VALUES ${placeholders}
|
|
||||||
ON CONFLICT (po_id, pid) DO UPDATE SET
|
|
||||||
sku = EXCLUDED.sku,
|
|
||||||
name = EXCLUDED.name,
|
|
||||||
vendor = EXCLUDED.vendor,
|
|
||||||
date = EXCLUDED.date,
|
|
||||||
expected_date = EXCLUDED.expected_date,
|
|
||||||
status = EXCLUDED.status,
|
|
||||||
notes = EXCLUDED.notes,
|
|
||||||
long_note = EXCLUDED.long_note,
|
|
||||||
ordered = EXCLUDED.ordered,
|
|
||||||
po_cost_price = EXCLUDED.po_cost_price,
|
|
||||||
supplier_id = EXCLUDED.supplier_id,
|
|
||||||
date_created = EXCLUDED.date_created,
|
|
||||||
date_ordered = EXCLUDED.date_ordered
|
|
||||||
`, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += poList.length;
|
|
||||||
totalProcessed += completePOs.length;
|
|
||||||
|
|
||||||
outputProgress({
|
|
||||||
status: "running",
|
|
||||||
operation: "Purchase orders import",
|
|
||||||
message: `Processed ${offset} of ${totalPOs} purchase orders (${totalProcessed} line items)`,
|
|
||||||
current: offset,
|
|
||||||
total: totalPOs,
|
|
||||||
elapsed: formatElapsedTime((Date.now() - startTime) / 1000),
|
|
||||||
remaining: estimateRemaining(startTime, offset, totalPOs),
|
|
||||||
rate: calculateRate(startTime, offset)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (poList.length < PO_BATCH_SIZE) {
|
|
||||||
allPOsProcessed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,183 +431,188 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
const totalReceivings = receivingCount[0].total;
|
const totalReceivings = receivingCount[0].total;
|
||||||
console.log(`Found ${totalReceivings} relevant receivings`);
|
console.log(`Found ${totalReceivings} relevant receivings`);
|
||||||
|
|
||||||
// Fetch and process receivings in batches
|
// Skip processing if no receivings to process
|
||||||
offset = 0; // Reset offset for receivings
|
if (totalReceivings === 0) {
|
||||||
let allReceivingsProcessed = false;
|
console.log('No receivings to process, skipping receivings import step');
|
||||||
|
} else {
|
||||||
|
// Fetch and process receivings in batches
|
||||||
|
offset = 0; // Reset offset for receivings
|
||||||
|
let allReceivingsProcessed = false;
|
||||||
|
|
||||||
while (!allReceivingsProcessed) {
|
while (!allReceivingsProcessed) {
|
||||||
const [receivingList] = await prodConnection.query(`
|
const [receivingList] = await prodConnection.query(`
|
||||||
SELECT
|
SELECT
|
||||||
r.receiving_id,
|
r.receiving_id,
|
||||||
r.supplier_id,
|
r.supplier_id,
|
||||||
r.status,
|
r.status,
|
||||||
r.notes,
|
r.notes,
|
||||||
r.shipping,
|
r.shipping,
|
||||||
r.total_amount,
|
r.total_amount,
|
||||||
r.hold,
|
r.hold,
|
||||||
r.for_storefront,
|
r.for_storefront,
|
||||||
r.date_created,
|
r.date_created,
|
||||||
r.date_paid,
|
r.date_paid,
|
||||||
r.date_checked
|
r.date_checked
|
||||||
FROM receivings r
|
FROM receivings r
|
||||||
WHERE r.date_created >= DATE_SUB(CURRENT_DATE, INTERVAL ${yearInterval} YEAR)
|
WHERE r.date_created >= DATE_SUB(CURRENT_DATE, INTERVAL ${yearInterval} YEAR)
|
||||||
${incrementalUpdate ? `
|
${incrementalUpdate ? `
|
||||||
AND (
|
AND (
|
||||||
r.date_updated > ?
|
r.date_updated > ?
|
||||||
OR r.date_created > ?
|
OR r.date_created > ?
|
||||||
|
)
|
||||||
|
` : ''}
|
||||||
|
ORDER BY r.receiving_id
|
||||||
|
LIMIT ${PO_BATCH_SIZE} OFFSET ${offset}
|
||||||
|
`, incrementalUpdate ? [lastSyncTime, lastSyncTime] : []);
|
||||||
|
|
||||||
|
if (receivingList.length === 0) {
|
||||||
|
allReceivingsProcessed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get products for these receivings
|
||||||
|
const receivingIds = receivingList.map(r => r.receiving_id);
|
||||||
|
|
||||||
|
const [receivingProducts] = await prodConnection.query(`
|
||||||
|
SELECT
|
||||||
|
rp.receiving_id,
|
||||||
|
rp.pid,
|
||||||
|
rp.qty_each,
|
||||||
|
rp.qty_each_orig,
|
||||||
|
rp.cost_each,
|
||||||
|
rp.cost_each_orig,
|
||||||
|
rp.received_by,
|
||||||
|
rp.received_date,
|
||||||
|
r.date_created as receiving_created_date,
|
||||||
|
COALESCE(p.itemnumber, 'NO-SKU') AS sku,
|
||||||
|
COALESCE(p.description, 'Unknown Product') AS name
|
||||||
|
FROM receivings_products rp
|
||||||
|
JOIN receivings r ON rp.receiving_id = r.receiving_id
|
||||||
|
LEFT JOIN products p ON rp.pid = p.pid
|
||||||
|
WHERE rp.receiving_id IN (?)
|
||||||
|
`, [receivingIds]);
|
||||||
|
|
||||||
|
// Build complete receiving records
|
||||||
|
const completeReceivings = [];
|
||||||
|
for (const product of receivingProducts) {
|
||||||
|
const receiving = receivingList.find(r => r.receiving_id == product.receiving_id);
|
||||||
|
if (!receiving) continue;
|
||||||
|
|
||||||
|
// Get employee name if available
|
||||||
|
let receivedByName = null;
|
||||||
|
if (product.received_by) {
|
||||||
|
const [employeeResult] = await localConnection.query(`
|
||||||
|
SELECT CONCAT(firstname, ' ', lastname) as full_name
|
||||||
|
FROM employee_names
|
||||||
|
WHERE employeeid = $1
|
||||||
|
`, [product.received_by]);
|
||||||
|
|
||||||
|
if (employeeResult.rows.length > 0) {
|
||||||
|
receivedByName = employeeResult.rows[0].full_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get vendor name if available
|
||||||
|
let vendorName = 'Unknown Vendor';
|
||||||
|
if (receiving.supplier_id) {
|
||||||
|
const [vendorResult] = await localConnection.query(`
|
||||||
|
SELECT company_name
|
||||||
|
FROM temp_supplier_names
|
||||||
|
WHERE supplier_id = $1
|
||||||
|
`, [receiving.supplier_id]);
|
||||||
|
|
||||||
|
if (vendorResult.rows.length > 0) {
|
||||||
|
vendorName = vendorResult.rows[0].company_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completeReceivings.push({
|
||||||
|
receiving_id: receiving.receiving_id.toString(),
|
||||||
|
pid: product.pid,
|
||||||
|
sku: product.sku,
|
||||||
|
name: product.name,
|
||||||
|
vendor: vendorName,
|
||||||
|
qty_each: product.qty_each,
|
||||||
|
qty_each_orig: product.qty_each_orig,
|
||||||
|
cost_each: product.cost_each,
|
||||||
|
cost_each_orig: product.cost_each_orig,
|
||||||
|
received_by: product.received_by,
|
||||||
|
received_by_name: receivedByName,
|
||||||
|
received_date: validateDate(product.received_date) || validateDate(product.receiving_created_date),
|
||||||
|
receiving_created_date: validateDate(product.receiving_created_date),
|
||||||
|
supplier_id: receiving.supplier_id,
|
||||||
|
status: receivingStatusMap[receiving.status] || 'created'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert receiving data in batches
|
||||||
|
for (let i = 0; i < completeReceivings.length; i += INSERT_BATCH_SIZE) {
|
||||||
|
const batch = completeReceivings.slice(i, i + INSERT_BATCH_SIZE);
|
||||||
|
|
||||||
|
const placeholders = batch.map((_, idx) => {
|
||||||
|
const base = idx * 15;
|
||||||
|
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}, $${base + 6}, $${base + 7}, $${base + 8}, $${base + 9}, $${base + 10}, $${base + 11}, $${base + 12}, $${base + 13}, $${base + 14}, $${base + 15})`;
|
||||||
|
}).join(',');
|
||||||
|
|
||||||
|
const values = batch.flatMap(r => [
|
||||||
|
r.receiving_id,
|
||||||
|
r.pid,
|
||||||
|
r.sku,
|
||||||
|
r.name,
|
||||||
|
r.vendor,
|
||||||
|
r.qty_each,
|
||||||
|
r.qty_each_orig,
|
||||||
|
r.cost_each,
|
||||||
|
r.cost_each_orig,
|
||||||
|
r.received_by,
|
||||||
|
r.received_by_name,
|
||||||
|
r.received_date,
|
||||||
|
r.receiving_created_date,
|
||||||
|
r.supplier_id,
|
||||||
|
r.status
|
||||||
|
]);
|
||||||
|
|
||||||
|
await localConnection.query(`
|
||||||
|
INSERT INTO temp_receivings (
|
||||||
|
receiving_id, pid, sku, name, vendor, qty_each, qty_each_orig,
|
||||||
|
cost_each, cost_each_orig, received_by, received_by_name,
|
||||||
|
received_date, receiving_created_date, supplier_id, status
|
||||||
)
|
)
|
||||||
` : ''}
|
VALUES ${placeholders}
|
||||||
ORDER BY r.receiving_id
|
ON CONFLICT (receiving_id, pid) DO UPDATE SET
|
||||||
LIMIT ${PO_BATCH_SIZE} OFFSET ${offset}
|
sku = EXCLUDED.sku,
|
||||||
`, incrementalUpdate ? [lastSyncTime, lastSyncTime] : []);
|
name = EXCLUDED.name,
|
||||||
|
vendor = EXCLUDED.vendor,
|
||||||
if (receivingList.length === 0) {
|
qty_each = EXCLUDED.qty_each,
|
||||||
allReceivingsProcessed = true;
|
qty_each_orig = EXCLUDED.qty_each_orig,
|
||||||
break;
|
cost_each = EXCLUDED.cost_each,
|
||||||
}
|
cost_each_orig = EXCLUDED.cost_each_orig,
|
||||||
|
received_by = EXCLUDED.received_by,
|
||||||
// Get products for these receivings
|
received_by_name = EXCLUDED.received_by_name,
|
||||||
const receivingIds = receivingList.map(r => r.receiving_id);
|
received_date = EXCLUDED.received_date,
|
||||||
|
receiving_created_date = EXCLUDED.receiving_created_date,
|
||||||
const [receivingProducts] = await prodConnection.query(`
|
supplier_id = EXCLUDED.supplier_id,
|
||||||
SELECT
|
status = EXCLUDED.status
|
||||||
rp.receiving_id,
|
`, values);
|
||||||
rp.pid,
|
|
||||||
rp.qty_each,
|
|
||||||
rp.qty_each_orig,
|
|
||||||
rp.cost_each,
|
|
||||||
rp.cost_each_orig,
|
|
||||||
rp.received_by,
|
|
||||||
rp.received_date,
|
|
||||||
r.date_created as receiving_created_date,
|
|
||||||
COALESCE(p.itemnumber, 'NO-SKU') AS sku,
|
|
||||||
COALESCE(p.description, 'Unknown Product') AS name
|
|
||||||
FROM receivings_products rp
|
|
||||||
JOIN receivings r ON rp.receiving_id = r.receiving_id
|
|
||||||
LEFT JOIN products p ON rp.pid = p.pid
|
|
||||||
WHERE rp.receiving_id IN (?)
|
|
||||||
`, [receivingIds]);
|
|
||||||
|
|
||||||
// Build complete receiving records
|
|
||||||
const completeReceivings = [];
|
|
||||||
for (const product of receivingProducts) {
|
|
||||||
const receiving = receivingList.find(r => r.receiving_id == product.receiving_id);
|
|
||||||
if (!receiving) continue;
|
|
||||||
|
|
||||||
// Get employee name if available
|
|
||||||
let receivedByName = null;
|
|
||||||
if (product.received_by) {
|
|
||||||
const [employeeResult] = await localConnection.query(`
|
|
||||||
SELECT CONCAT(firstname, ' ', lastname) as full_name
|
|
||||||
FROM employee_names
|
|
||||||
WHERE employeeid = $1
|
|
||||||
`, [product.received_by]);
|
|
||||||
|
|
||||||
if (employeeResult.rows.length > 0) {
|
|
||||||
receivedByName = employeeResult.rows[0].full_name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get vendor name if available
|
offset += receivingList.length;
|
||||||
let vendorName = 'Unknown Vendor';
|
totalProcessed += completeReceivings.length;
|
||||||
if (receiving.supplier_id) {
|
|
||||||
const [vendorResult] = await localConnection.query(`
|
|
||||||
SELECT company_name
|
|
||||||
FROM temp_supplier_names
|
|
||||||
WHERE supplier_id = $1
|
|
||||||
`, [receiving.supplier_id]);
|
|
||||||
|
|
||||||
if (vendorResult.rows.length > 0) {
|
outputProgress({
|
||||||
vendorName = vendorResult.rows[0].company_name;
|
status: "running",
|
||||||
}
|
operation: "Purchase orders import",
|
||||||
}
|
message: `Processed ${offset} of ${totalReceivings} receivings (${totalProcessed} line items total)`,
|
||||||
|
current: offset,
|
||||||
completeReceivings.push({
|
total: totalReceivings,
|
||||||
receiving_id: receiving.receiving_id.toString(),
|
elapsed: formatElapsedTime((Date.now() - startTime) / 1000),
|
||||||
pid: product.pid,
|
remaining: estimateRemaining(startTime, offset, totalReceivings),
|
||||||
sku: product.sku,
|
rate: calculateRate(startTime, offset)
|
||||||
name: product.name,
|
|
||||||
vendor: vendorName,
|
|
||||||
qty_each: product.qty_each,
|
|
||||||
qty_each_orig: product.qty_each_orig,
|
|
||||||
cost_each: product.cost_each,
|
|
||||||
cost_each_orig: product.cost_each_orig,
|
|
||||||
received_by: product.received_by,
|
|
||||||
received_by_name: receivedByName,
|
|
||||||
received_date: validateDate(product.received_date) || validateDate(product.receiving_created_date),
|
|
||||||
receiving_created_date: validateDate(product.receiving_created_date),
|
|
||||||
supplier_id: receiving.supplier_id,
|
|
||||||
status: receivingStatusMap[receiving.status] || 'created'
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Insert receiving data in batches
|
if (receivingList.length < PO_BATCH_SIZE) {
|
||||||
for (let i = 0; i < completeReceivings.length; i += INSERT_BATCH_SIZE) {
|
allReceivingsProcessed = true;
|
||||||
const batch = completeReceivings.slice(i, i + INSERT_BATCH_SIZE);
|
}
|
||||||
|
|
||||||
const placeholders = batch.map((_, idx) => {
|
|
||||||
const base = idx * 15;
|
|
||||||
return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}, $${base + 6}, $${base + 7}, $${base + 8}, $${base + 9}, $${base + 10}, $${base + 11}, $${base + 12}, $${base + 13}, $${base + 14}, $${base + 15})`;
|
|
||||||
}).join(',');
|
|
||||||
|
|
||||||
const values = batch.flatMap(r => [
|
|
||||||
r.receiving_id,
|
|
||||||
r.pid,
|
|
||||||
r.sku,
|
|
||||||
r.name,
|
|
||||||
r.vendor,
|
|
||||||
r.qty_each,
|
|
||||||
r.qty_each_orig,
|
|
||||||
r.cost_each,
|
|
||||||
r.cost_each_orig,
|
|
||||||
r.received_by,
|
|
||||||
r.received_by_name,
|
|
||||||
r.received_date,
|
|
||||||
r.receiving_created_date,
|
|
||||||
r.supplier_id,
|
|
||||||
r.status
|
|
||||||
]);
|
|
||||||
|
|
||||||
await localConnection.query(`
|
|
||||||
INSERT INTO temp_receivings (
|
|
||||||
receiving_id, pid, sku, name, vendor, qty_each, qty_each_orig,
|
|
||||||
cost_each, cost_each_orig, received_by, received_by_name,
|
|
||||||
received_date, receiving_created_date, supplier_id, status
|
|
||||||
)
|
|
||||||
VALUES ${placeholders}
|
|
||||||
ON CONFLICT (receiving_id, pid) DO UPDATE SET
|
|
||||||
sku = EXCLUDED.sku,
|
|
||||||
name = EXCLUDED.name,
|
|
||||||
vendor = EXCLUDED.vendor,
|
|
||||||
qty_each = EXCLUDED.qty_each,
|
|
||||||
qty_each_orig = EXCLUDED.qty_each_orig,
|
|
||||||
cost_each = EXCLUDED.cost_each,
|
|
||||||
cost_each_orig = EXCLUDED.cost_each_orig,
|
|
||||||
received_by = EXCLUDED.received_by,
|
|
||||||
received_by_name = EXCLUDED.received_by_name,
|
|
||||||
received_date = EXCLUDED.received_date,
|
|
||||||
receiving_created_date = EXCLUDED.receiving_created_date,
|
|
||||||
supplier_id = EXCLUDED.supplier_id,
|
|
||||||
status = EXCLUDED.status
|
|
||||||
`, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += receivingList.length;
|
|
||||||
totalProcessed += completeReceivings.length;
|
|
||||||
|
|
||||||
outputProgress({
|
|
||||||
status: "running",
|
|
||||||
operation: "Purchase orders import",
|
|
||||||
message: `Processed ${offset} of ${totalReceivings} receivings (${totalProcessed} line items total)`,
|
|
||||||
current: offset,
|
|
||||||
total: totalReceivings,
|
|
||||||
elapsed: formatElapsedTime((Date.now() - startTime) / 1000),
|
|
||||||
remaining: estimateRemaining(startTime, offset, totalReceivings),
|
|
||||||
rate: calculateRate(startTime, offset)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (receivingList.length < PO_BATCH_SIZE) {
|
|
||||||
allReceivingsProcessed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,6 +667,31 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
message: "Inserting final purchase order records"
|
message: "Inserting final purchase order records"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create a temp table to track PO IDs being processed
|
||||||
|
await localConnection.query(`
|
||||||
|
DROP TABLE IF EXISTS processed_po_ids;
|
||||||
|
CREATE TEMP TABLE processed_po_ids AS (
|
||||||
|
SELECT DISTINCT po_id FROM temp_purchase_orders
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Delete products that were removed from POs and count them
|
||||||
|
const [poDeletedResult] = await localConnection.query(`
|
||||||
|
WITH deleted AS (
|
||||||
|
DELETE FROM purchase_orders
|
||||||
|
WHERE po_id IN (SELECT po_id FROM processed_po_ids)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM temp_purchase_orders tp
|
||||||
|
WHERE purchase_orders.po_id = tp.po_id AND purchase_orders.pid = tp.pid
|
||||||
|
)
|
||||||
|
RETURNING po_id, pid
|
||||||
|
)
|
||||||
|
SELECT COUNT(*) as count FROM deleted
|
||||||
|
`);
|
||||||
|
|
||||||
|
poRecordsDeleted = poDeletedResult.rows[0]?.count || 0;
|
||||||
|
console.log(`Deleted ${poRecordsDeleted} products that were removed from purchase orders`);
|
||||||
|
|
||||||
const [poResult] = await localConnection.query(`
|
const [poResult] = await localConnection.query(`
|
||||||
INSERT INTO purchase_orders (
|
INSERT INTO purchase_orders (
|
||||||
po_id, vendor, date, expected_date, pid, sku, name,
|
po_id, vendor, date, expected_date, pid, sku, name,
|
||||||
@@ -706,6 +743,31 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
message: "Inserting final receiving records"
|
message: "Inserting final receiving records"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create a temp table to track receiving IDs being processed
|
||||||
|
await localConnection.query(`
|
||||||
|
DROP TABLE IF EXISTS processed_receiving_ids;
|
||||||
|
CREATE TEMP TABLE processed_receiving_ids AS (
|
||||||
|
SELECT DISTINCT receiving_id FROM temp_receivings
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Delete products that were removed from receivings and count them
|
||||||
|
const [receivingDeletedResult] = await localConnection.query(`
|
||||||
|
WITH deleted AS (
|
||||||
|
DELETE FROM receivings
|
||||||
|
WHERE receiving_id IN (SELECT receiving_id FROM processed_receiving_ids)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM temp_receivings tr
|
||||||
|
WHERE receivings.receiving_id = tr.receiving_id AND receivings.pid = tr.pid
|
||||||
|
)
|
||||||
|
RETURNING receiving_id, pid
|
||||||
|
)
|
||||||
|
SELECT COUNT(*) as count FROM deleted
|
||||||
|
`);
|
||||||
|
|
||||||
|
receivingRecordsDeleted = receivingDeletedResult.rows[0]?.count || 0;
|
||||||
|
console.log(`Deleted ${receivingRecordsDeleted} products that were removed from receivings`);
|
||||||
|
|
||||||
const [receivingsResult] = await localConnection.query(`
|
const [receivingsResult] = await localConnection.query(`
|
||||||
INSERT INTO receivings (
|
INSERT INTO receivings (
|
||||||
receiving_id, pid, sku, name, vendor, qty_each, qty_each_orig,
|
receiving_id, pid, sku, name, vendor, qty_each, qty_each_orig,
|
||||||
@@ -765,6 +827,8 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
DROP TABLE IF EXISTS employee_names;
|
DROP TABLE IF EXISTS employee_names;
|
||||||
DROP TABLE IF EXISTS temp_supplier_names;
|
DROP TABLE IF EXISTS temp_supplier_names;
|
||||||
DROP TABLE IF EXISTS temp_invalid_pids;
|
DROP TABLE IF EXISTS temp_invalid_pids;
|
||||||
|
DROP TABLE IF EXISTS processed_po_ids;
|
||||||
|
DROP TABLE IF EXISTS processed_receiving_ids;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
@@ -774,10 +838,13 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
status: "complete",
|
status: "complete",
|
||||||
recordsAdded: poRecordsAdded + receivingRecordsAdded,
|
recordsAdded: poRecordsAdded + receivingRecordsAdded,
|
||||||
recordsUpdated: poRecordsUpdated + receivingRecordsUpdated,
|
recordsUpdated: poRecordsUpdated + receivingRecordsUpdated,
|
||||||
|
recordsDeleted: poRecordsDeleted + receivingRecordsDeleted,
|
||||||
poRecordsAdded,
|
poRecordsAdded,
|
||||||
poRecordsUpdated,
|
poRecordsUpdated,
|
||||||
|
poRecordsDeleted,
|
||||||
receivingRecordsAdded,
|
receivingRecordsAdded,
|
||||||
receivingRecordsUpdated,
|
receivingRecordsUpdated,
|
||||||
|
receivingRecordsDeleted,
|
||||||
totalRecords: totalProcessed
|
totalRecords: totalProcessed
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -795,6 +862,7 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
|||||||
error: error.message,
|
error: error.message,
|
||||||
recordsAdded: 0,
|
recordsAdded: 0,
|
||||||
recordsUpdated: 0,
|
recordsUpdated: 0,
|
||||||
|
recordsDeleted: 0,
|
||||||
totalRecords: 0
|
totalRecords: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,13 +115,13 @@ export default function PurchaseOrderAccordion({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-h-[350px] overflow-y-auto bg-gray-50 rounded-md p-2">
|
<div className="max-h-[600px] overflow-y-auto bg-gray-50 rounded-md p-2">
|
||||||
<Table className="w-full">
|
<Table className="w-full">
|
||||||
<TableHeader className="bg-white sticky top-0 z-10">
|
<TableHeader className="bg-white sticky top-0 z-10">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[100px]">SKU</TableHead>
|
<TableHead className="w-[100px]">Item Number</TableHead>
|
||||||
<TableHead className="w-auto">Product</TableHead>
|
<TableHead className="w-auto">Product</TableHead>
|
||||||
<TableHead className="w-[100px] text-right">UPC</TableHead>
|
<TableHead className="w-[100px]">UPC</TableHead>
|
||||||
<TableHead className="w-[80px] text-right">Ordered</TableHead>
|
<TableHead className="w-[80px] text-right">Ordered</TableHead>
|
||||||
<TableHead className="w-[80px] text-right">Received</TableHead>
|
<TableHead className="w-[80px] text-right">Received</TableHead>
|
||||||
<TableHead className="w-[100px] text-right">Unit Cost</TableHead>
|
<TableHead className="w-[100px] text-right">Unit Cost</TableHead>
|
||||||
@@ -151,9 +151,39 @@ export default function PurchaseOrderAccordion({
|
|||||||
) : (
|
) : (
|
||||||
items.map((item) => (
|
items.map((item) => (
|
||||||
<TableRow key={item.id} className="hover:bg-gray-100">
|
<TableRow key={item.id} className="hover:bg-gray-100">
|
||||||
<TableCell className="font-mono text-xs">{item.sku}</TableCell>
|
<TableCell className="">
|
||||||
<TableCell className="font-medium">{item.product_name}</TableCell>
|
<a
|
||||||
<TableCell className="text-right font-mono text-xs">{item.upc}</TableCell>
|
href={`https://backend.acherryontop.com/product/${item.pid}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{item.sku}
|
||||||
|
</a>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="font-medium">
|
||||||
|
<a
|
||||||
|
href={`https://backend.acherryontop.com/product/${item.pid}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{item.product_name}
|
||||||
|
</a>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="">
|
||||||
|
<a
|
||||||
|
href={`https://backend.acherryontop.com/product/${item.pid}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{item.upc}
|
||||||
|
</a>
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{item.ordered}
|
{item.ordered}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default function PurchaseOrdersTable({
|
|||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-center border-green-500 text-green-700 bg-green-50 px-0 tracking-tight w-[85px]"
|
className="flex items-center justify-center border-green-500 text-green-700 bg-green-50 px-0 text-xs w-[85px]"
|
||||||
>
|
>
|
||||||
Received PO
|
Received PO
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -78,7 +78,7 @@ export default function PurchaseOrdersTable({
|
|||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-center border-blue-500 text-blue-700 bg-blue-50 px-0 tracking-tight w-[85px]"
|
className="flex items-center justify-center border-blue-500 text-blue-700 bg-blue-50 px-0 text-xs w-[85px]"
|
||||||
>
|
>
|
||||||
PO
|
PO
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -87,7 +87,7 @@ export default function PurchaseOrdersTable({
|
|||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-center border-amber-500 text-amber-700 bg-amber-50 px-0 tracking-tight w-[85px]"
|
className="flex items-center justify-center border-amber-500 text-amber-700 bg-amber-50 px-0 text-xs w-[85px]"
|
||||||
>
|
>
|
||||||
Receiving
|
Receiving
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -96,7 +96,7 @@ export default function PurchaseOrdersTable({
|
|||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-center border-gray-500 text-gray-700 bg-gray-50 px-0 tracking-tight w-[85px]"
|
className="flex items-center justify-center border-gray-500 text-gray-700 bg-gray-50 px-0 text-xs w-[85px]"
|
||||||
>
|
>
|
||||||
{recordType || "Unknown"}
|
{recordType || "Unknown"}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -108,7 +108,7 @@ export default function PurchaseOrdersTable({
|
|||||||
if (recordType === "receiving_only") {
|
if (recordType === "receiving_only") {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
className="w-[100px] flex items-center justify-center px-0 tracking-tight"
|
className="w-[115px] flex items-center text-xs justify-center px-1"
|
||||||
variant={getReceivingStatusVariant(status)}
|
variant={getReceivingStatusVariant(status)}
|
||||||
>
|
>
|
||||||
{getReceivingStatusLabel(status)}
|
{getReceivingStatusLabel(status)}
|
||||||
@@ -118,7 +118,7 @@ export default function PurchaseOrdersTable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
className="w-[100px] flex items-center justify-center px-0 tracking-tight"
|
className="w-[115px] flex items-center text-xs justify-center px-1"
|
||||||
variant={getPurchaseOrderStatusVariant(status)}
|
variant={getPurchaseOrderStatusVariant(status)}
|
||||||
>
|
>
|
||||||
{getPurchaseOrderStatusLabel(status)}
|
{getPurchaseOrderStatusLabel(status)}
|
||||||
@@ -342,7 +342,18 @@ export default function PurchaseOrdersTable({
|
|||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{getRecordTypeIndicator(po.record_type)}
|
{getRecordTypeIndicator(po.record_type)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-semibold text-center">{po.id}</TableCell>
|
<TableCell className="font-semibold text-center">
|
||||||
|
<a
|
||||||
|
href={po.record_type === "po_only"
|
||||||
|
? `https://backend.acherryontop.com/po/edit/${po.id}`
|
||||||
|
: `https://backend.acherryontop.com/receiving/edit/${po.id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
{po.id}
|
||||||
|
</a>
|
||||||
|
</TableCell>
|
||||||
<TableCell>{po.vendor_name}</TableCell>
|
<TableCell>{po.vendor_name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{getStatusBadge(po.status, po.record_type)}
|
{getStatusBadge(po.status, po.record_type)}
|
||||||
@@ -378,7 +389,7 @@ export default function PurchaseOrdersTable({
|
|||||||
year: "numeric",
|
year: "numeric",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: ""}
|
: "-"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{po.receiving_date
|
{po.receiving_date
|
||||||
@@ -390,18 +401,18 @@ export default function PurchaseOrdersTable({
|
|||||||
year: "numeric",
|
year: "numeric",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: ""}
|
: "-"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{po.total_quantity.toLocaleString()}
|
{po.record_type === "receiving_only" ? "-" : po.total_quantity.toLocaleString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{po.total_received.toLocaleString()}
|
{po.record_type === "po_only" ? "-" : po.total_received.toLocaleString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right" >
|
<TableCell className="text-center" >
|
||||||
{po.fulfillment_rate === null
|
{po.record_type === "po_with_receiving"
|
||||||
? "N/A"
|
? (po.fulfillment_rate === null ? "N/A" : formatPercent(po.fulfillment_rate))
|
||||||
: formatPercent(po.fulfillment_rate)}
|
: "-"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</PurchaseOrderAccordion>
|
</PurchaseOrderAccordion>
|
||||||
|
|||||||
Reference in New Issue
Block a user