Fix (probably) discrepancies and errors in import/calculate scripts
This commit is contained in:
@@ -317,14 +317,25 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
|
||||
for (let i = 0; i < orderIds.length; i += 5000) {
|
||||
const batchIds = orderIds.slice(i, i + 5000);
|
||||
const [costs] = await prodConnection.query(`
|
||||
SELECT orderid as order_id, pid, costeach
|
||||
FROM order_costs
|
||||
WHERE orderid IN (?)
|
||||
SELECT
|
||||
oc.orderid as order_id,
|
||||
oc.pid,
|
||||
COALESCE(
|
||||
oc.costeach,
|
||||
(SELECT pi.costeach
|
||||
FROM product_inventory pi
|
||||
WHERE pi.pid = oc.pid
|
||||
AND pi.daterec <= o.date_placed
|
||||
ORDER BY pi.daterec DESC LIMIT 1)
|
||||
) as costeach
|
||||
FROM order_costs oc
|
||||
JOIN _order o ON oc.orderid = o.order_id
|
||||
WHERE oc.orderid IN (?)
|
||||
`, [batchIds]);
|
||||
|
||||
if (costs.length > 0) {
|
||||
const placeholders = costs.map(() => '(?, ?, ?)').join(",");
|
||||
const values = costs.flatMap(c => [c.order_id, c.pid, c.costeach]);
|
||||
const values = costs.flatMap(c => [c.order_id, c.pid, c.costeach || 0]);
|
||||
await localConnection.query(`
|
||||
INSERT INTO temp_order_costs (order_id, pid, costeach)
|
||||
VALUES ${placeholders}
|
||||
@@ -355,7 +366,13 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
|
||||
om.date,
|
||||
oi.price,
|
||||
oi.quantity,
|
||||
oi.base_discount + COALESCE(od.discount, 0) as discount,
|
||||
oi.base_discount + COALESCE(od.discount, 0) +
|
||||
CASE
|
||||
WHEN o.summary_discount > 0 THEN
|
||||
ROUND((o.summary_discount * (oi.price * oi.quantity)) /
|
||||
NULLIF(o.summary_subtotal, 0), 2)
|
||||
ELSE 0
|
||||
END as discount,
|
||||
COALESCE(ot.tax, 0) as tax,
|
||||
0 as tax_included,
|
||||
0 as shipping,
|
||||
@@ -366,6 +383,7 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
|
||||
COALESCE(tc.costeach, 0) as costeach
|
||||
FROM temp_order_items oi
|
||||
JOIN temp_order_meta om ON oi.order_id = om.order_id
|
||||
LEFT JOIN _order o ON oi.order_id = o.order_id
|
||||
LEFT JOIN temp_order_discounts od ON oi.order_id = od.order_id AND oi.pid = od.pid
|
||||
LEFT JOIN temp_order_taxes ot ON oi.order_id = ot.order_id AND oi.pid = ot.pid
|
||||
LEFT JOIN temp_order_costs tc ON oi.order_id = tc.order_id AND oi.pid = tc.pid
|
||||
@@ -455,7 +473,13 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
|
||||
om.date,
|
||||
oi.price,
|
||||
oi.quantity,
|
||||
oi.base_discount + COALESCE(od.discount, 0) as discount,
|
||||
oi.base_discount + COALESCE(od.discount, 0) +
|
||||
CASE
|
||||
WHEN o.summary_discount > 0 THEN
|
||||
ROUND((o.summary_discount * (oi.price * oi.quantity)) /
|
||||
NULLIF(o.summary_subtotal, 0), 2)
|
||||
ELSE 0
|
||||
END as discount,
|
||||
COALESCE(ot.tax, 0) as tax,
|
||||
0 as tax_included,
|
||||
0 as shipping,
|
||||
@@ -466,6 +490,7 @@ async function importOrders(prodConnection, localConnection, incrementalUpdate =
|
||||
COALESCE(tc.costeach, 0) as costeach
|
||||
FROM temp_order_items oi
|
||||
JOIN temp_order_meta om ON oi.order_id = om.order_id
|
||||
LEFT JOIN _order o ON oi.order_id = o.order_id
|
||||
LEFT JOIN temp_order_discounts od ON oi.order_id = od.order_id AND oi.pid = od.pid
|
||||
LEFT JOIN temp_order_taxes ot ON oi.order_id = ot.order_id AND oi.pid = ot.pid
|
||||
LEFT JOIN temp_order_costs tc ON oi.order_id = tc.order_id AND oi.pid = tc.pid
|
||||
|
||||
@@ -470,43 +470,100 @@ async function importProducts(prodConnection, localConnection, incrementalUpdate
|
||||
|
||||
// Process category relationships
|
||||
if (batch.some(p => p.category_ids)) {
|
||||
const categoryRelationships = batch
|
||||
.filter(p => p.category_ids)
|
||||
.flatMap(product =>
|
||||
product.category_ids
|
||||
.split(',')
|
||||
.map(id => id.trim())
|
||||
.filter(id => id)
|
||||
.map(Number)
|
||||
.filter(id => !isNaN(id))
|
||||
.map(catId => [catId, product.pid])
|
||||
);
|
||||
// First get all valid categories
|
||||
const allCategoryIds = [...new Set(
|
||||
batch
|
||||
.filter(p => p.category_ids)
|
||||
.flatMap(product =>
|
||||
product.category_ids
|
||||
.split(',')
|
||||
.map(id => id.trim())
|
||||
.filter(id => id)
|
||||
.map(Number)
|
||||
.filter(id => !isNaN(id))
|
||||
)
|
||||
)];
|
||||
|
||||
if (categoryRelationships.length > 0) {
|
||||
// Verify categories exist before inserting relationships
|
||||
const uniqueCatIds = [...new Set(categoryRelationships.map(([catId]) => catId))];
|
||||
const [existingCats] = await localConnection.query(
|
||||
"SELECT cat_id FROM categories WHERE cat_id IN (?)",
|
||||
[uniqueCatIds]
|
||||
);
|
||||
const existingCatIds = new Set(existingCats.map(c => c.cat_id));
|
||||
// Verify categories exist and get their hierarchy
|
||||
const [categories] = await localConnection.query(`
|
||||
WITH RECURSIVE category_hierarchy AS (
|
||||
SELECT
|
||||
cat_id,
|
||||
parent_id,
|
||||
type,
|
||||
1 as level,
|
||||
CAST(cat_id AS CHAR(200)) as path
|
||||
FROM categories
|
||||
WHERE cat_id IN (?)
|
||||
UNION ALL
|
||||
SELECT
|
||||
c.cat_id,
|
||||
c.parent_id,
|
||||
c.type,
|
||||
ch.level + 1,
|
||||
CONCAT(ch.path, ',', c.cat_id)
|
||||
FROM categories c
|
||||
JOIN category_hierarchy ch ON c.parent_id = ch.cat_id
|
||||
WHERE ch.level < 10 -- Prevent infinite recursion
|
||||
)
|
||||
SELECT DISTINCT
|
||||
cat_id,
|
||||
parent_id,
|
||||
type,
|
||||
path
|
||||
FROM category_hierarchy
|
||||
WHERE cat_id IN (?)
|
||||
ORDER BY level DESC
|
||||
`, [allCategoryIds, allCategoryIds]);
|
||||
|
||||
// Filter relationships to only include existing categories
|
||||
const validRelationships = categoryRelationships.filter(([catId]) =>
|
||||
existingCatIds.has(catId)
|
||||
);
|
||||
const validCategories = new Map(categories.map(c => [c.cat_id, c]));
|
||||
const validCategoryIds = new Set(categories.map(c => c.cat_id));
|
||||
|
||||
if (validRelationships.length > 0) {
|
||||
const catPlaceholders = validRelationships
|
||||
.map(() => "(?, ?)")
|
||||
.join(",");
|
||||
await localConnection.query(
|
||||
`INSERT IGNORE INTO product_categories (cat_id, pid)
|
||||
VALUES ${catPlaceholders}`,
|
||||
validRelationships.flat()
|
||||
);
|
||||
// Build category relationships ensuring proper hierarchy
|
||||
const categoryRelationships = [];
|
||||
batch
|
||||
.filter(p => p.category_ids)
|
||||
.forEach(product => {
|
||||
const productCategories = product.category_ids
|
||||
.split(',')
|
||||
.map(id => id.trim())
|
||||
.filter(id => id)
|
||||
.map(Number)
|
||||
.filter(id => !isNaN(id))
|
||||
.filter(id => validCategoryIds.has(id))
|
||||
.map(id => validCategories.get(id))
|
||||
.sort((a, b) => a.type - b.type); // Sort by type to ensure proper hierarchy
|
||||
|
||||
// Only add relationships that maintain proper hierarchy
|
||||
productCategories.forEach(category => {
|
||||
if (category.path.split(',').every(parentId =>
|
||||
validCategoryIds.has(Number(parentId))
|
||||
)) {
|
||||
categoryRelationships.push([category.cat_id, product.pid]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (categoryRelationships.length > 0) {
|
||||
// First remove any existing relationships that will be replaced
|
||||
await localConnection.query(`
|
||||
DELETE FROM product_categories
|
||||
WHERE pid IN (?) AND cat_id IN (?)
|
||||
`, [
|
||||
[...new Set(categoryRelationships.map(([_, pid]) => pid))],
|
||||
[...new Set(categoryRelationships.map(([catId, _]) => catId))]
|
||||
]);
|
||||
|
||||
// Then insert the new relationships
|
||||
const placeholders = categoryRelationships
|
||||
.map(() => "(?, ?)")
|
||||
.join(",");
|
||||
|
||||
await localConnection.query(`
|
||||
INSERT INTO product_categories (cat_id, pid)
|
||||
VALUES ${placeholders}
|
||||
`, categoryRelationships.flat());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -321,41 +321,47 @@ async function importPurchaseOrders(prodConnection, localConnection, incremental
|
||||
let lastFulfillmentReceiving = null;
|
||||
|
||||
for (const receiving of allReceivings) {
|
||||
const qtyToApply = Math.min(remainingToFulfill, receiving.qty_each);
|
||||
if (qtyToApply > 0) {
|
||||
// If this is the first receiving being applied, use its cost
|
||||
if (actualCost === null) {
|
||||
actualCost = receiving.cost_each;
|
||||
firstFulfillmentReceiving = receiving;
|
||||
// Convert quantities to base units using supplier data
|
||||
const baseQtyReceived = receiving.qty_each * (
|
||||
receiving.type === 'original' ? 1 :
|
||||
Math.max(1, product.supplier_qty_per_unit || 1)
|
||||
);
|
||||
const qtyToApply = Math.min(remainingToFulfill, baseQtyReceived);
|
||||
|
||||
if (qtyToApply > 0) {
|
||||
// If this is the first receiving being applied, use its cost
|
||||
if (actualCost === null && receiving.cost_each > 0) {
|
||||
actualCost = receiving.cost_each;
|
||||
firstFulfillmentReceiving = receiving;
|
||||
}
|
||||
lastFulfillmentReceiving = receiving;
|
||||
fulfillmentTracking.push({
|
||||
receiving_id: receiving.receiving_id,
|
||||
qty_applied: qtyToApply,
|
||||
qty_total: baseQtyReceived,
|
||||
cost: receiving.cost_each || actualCost || product.cost_each,
|
||||
date: receiving.received_date,
|
||||
received_by: receiving.received_by,
|
||||
received_by_name: receiving.received_by_name || 'Unknown',
|
||||
type: receiving.type,
|
||||
remaining_qty: baseQtyReceived - qtyToApply
|
||||
});
|
||||
remainingToFulfill -= qtyToApply;
|
||||
} else {
|
||||
// Track excess receivings
|
||||
fulfillmentTracking.push({
|
||||
receiving_id: receiving.receiving_id,
|
||||
qty_applied: 0,
|
||||
qty_total: baseQtyReceived,
|
||||
cost: receiving.cost_each || actualCost || product.cost_each,
|
||||
date: receiving.received_date,
|
||||
received_by: receiving.received_by,
|
||||
received_by_name: receiving.received_by_name || 'Unknown',
|
||||
type: receiving.type,
|
||||
is_excess: true
|
||||
});
|
||||
}
|
||||
lastFulfillmentReceiving = receiving;
|
||||
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,
|
||||
received_by_name: receiving.received_by_name || 'Unknown',
|
||||
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,
|
||||
received_by_name: receiving.received_by_name || 'Unknown',
|
||||
type: receiving.type,
|
||||
is_excess: true
|
||||
});
|
||||
}
|
||||
totalReceived += receiving.qty_each;
|
||||
totalReceived += baseQtyReceived;
|
||||
}
|
||||
|
||||
const receiving_status = !totalReceived ? 1 : // created
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
// Split into inserts and updates
|
||||
const insertsAndUpdates = batch.reduce((acc, po) => {
|
||||
const key = `${po.po_id}-${po.pid}`;
|
||||
if (existingPOMap.has(key)) {
|
||||
const existing = existingPOMap.get(key);
|
||||
// Check if any values are different
|
||||
const hasChanges = columnNames.some(col => {
|
||||
const newVal = po[col] ?? null;
|
||||
const oldVal = existing[col] ?? null;
|
||||
// Special handling for numbers to avoid type coercion issues
|
||||
if (typeof newVal === 'number' && typeof oldVal === 'number') {
|
||||
return Math.abs(newVal - oldVal) > 0.00001; // Allow for tiny floating point differences
|
||||
}
|
||||
// Special handling for receiving_history JSON
|
||||
if (col === 'receiving_history') {
|
||||
return JSON.stringify(newVal) !== JSON.stringify(oldVal);
|
||||
}
|
||||
return newVal !== oldVal;
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
console.log(`PO line changed: ${key}`, {
|
||||
po_id: po.po_id,
|
||||
pid: po.pid,
|
||||
changes: columnNames.filter(col => {
|
||||
const newVal = po[col] ?? null;
|
||||
const oldVal = existing[col] ?? null;
|
||||
if (typeof newVal === 'number' && typeof oldVal === 'number') {
|
||||
return Math.abs(newVal - oldVal) > 0.00001;
|
||||
}
|
||||
if (col === 'receiving_history') {
|
||||
return JSON.stringify(newVal) !== JSON.stringify(oldVal);
|
||||
}
|
||||
return newVal !== oldVal;
|
||||
})
|
||||
});
|
||||
acc.updates.push({
|
||||
po_id: po.po_id,
|
||||
pid: po.pid,
|
||||
values: columnNames.map(col => po[col] ?? null)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`New PO line: ${key}`);
|
||||
acc.inserts.push({
|
||||
po_id: po.po_id,
|
||||
pid: po.pid,
|
||||
values: columnNames.map(col => po[col] ?? null)
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, { inserts: [], updates: [] });
|
||||
|
||||
// Handle inserts
|
||||
if (insertsAndUpdates.inserts.length > 0) {
|
||||
const insertPlaceholders = Array(insertsAndUpdates.inserts.length).fill(placeholderGroup).join(",");
|
||||
|
||||
const insertResult = await localConnection.query(`
|
||||
INSERT INTO purchase_orders (${columnNames.join(",")})
|
||||
VALUES ${insertPlaceholders}
|
||||
`, insertsAndUpdates.inserts.map(i => i.values).flat());
|
||||
|
||||
recordsAdded += insertResult[0].affectedRows;
|
||||
}
|
||||
|
||||
// Handle updates
|
||||
if (insertsAndUpdates.updates.length > 0) {
|
||||
const updatePlaceholders = Array(insertsAndUpdates.updates.length).fill(placeholderGroup).join(",");
|
||||
|
||||
const updateResult = await localConnection.query(`
|
||||
INSERT INTO purchase_orders (${columnNames.join(",")})
|
||||
VALUES ${updatePlaceholders}
|
||||
ON DUPLICATE KEY UPDATE
|
||||
${columnNames
|
||||
.filter(col => col !== "po_id" && col !== "pid")
|
||||
.map(col => `${col} = VALUES(${col})`)
|
||||
.join(",")};
|
||||
`, insertsAndUpdates.updates.map(u => u.values).flat());
|
||||
|
||||
// Each update affects 2 rows in affectedRows, so we divide by 2 to get actual count
|
||||
recordsUpdated += insertsAndUpdates.updates.length;
|
||||
}
|
||||
Reference in New Issue
Block a user