Auth fixes, show correct cost each value on pos

This commit is contained in:
2026-05-28 14:15:13 -04:00
parent 421b3d5922
commit 8c707e28ea
21 changed files with 564 additions and 82 deletions
@@ -0,0 +1,77 @@
/**
* One-off backfill: populate products.notions_cost_each and supplier_cost_each
* from MySQL supplier_item_data. Idempotent — safe to re-run.
*
* Usage (on the server, where the SSH tunnel and env are configured):
* cd /var/www/inventory && node scripts/backfill-supplier-costs.js
*
* After this lands, the daily products import (via syncSupplierCosts in
* scripts/import/products.js) keeps the columns up to date.
*/
const dotenv = require("dotenv");
const path = require("path");
dotenv.config({ path: path.join(__dirname, "../.env") });
const { setupConnections, closeConnections } = require("./import/utils");
const { syncSupplierCosts } = require("./import/products");
const sshConfig = {
ssh: {
host: process.env.PROD_SSH_HOST,
port: process.env.PROD_SSH_PORT || 22,
username: process.env.PROD_SSH_USER,
privateKey: process.env.PROD_SSH_KEY_PATH
? require("fs").readFileSync(process.env.PROD_SSH_KEY_PATH)
: undefined,
compress: true,
},
prodDbConfig: {
host: process.env.PROD_DB_HOST || "localhost",
user: process.env.PROD_DB_USER,
password: process.env.PROD_DB_PASSWORD,
database: process.env.PROD_DB_NAME,
port: process.env.PROD_DB_PORT || 3306,
timezone: "-05:00",
},
localDbConfig: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT || 5432,
ssl: process.env.DB_SSL === "true",
connectionTimeoutMillis: 60000,
idleTimeoutMillis: 30000,
max: 4,
},
};
(async () => {
let connections;
const start = Date.now();
try {
console.log("Setting up connections...");
connections = await setupConnections(sshConfig);
const { prodConnection, localConnection } = connections;
console.log("Starting transaction...");
await localConnection.beginTransaction();
const result = await syncSupplierCosts(prodConnection, localConnection);
await localConnection.commit();
console.log(`Done. Updated ${result.updated} rows in ${(Date.now() - start) / 1000}s`);
} catch (err) {
console.error("Backfill failed:", err);
if (connections?.localConnection?._transactionActive) {
try { await connections.localConnection.rollback(); } catch (e) {}
}
process.exitCode = 1;
} finally {
if (connections) {
try { await closeConnections(connections); } catch (e) { console.error("Close error:", e); }
}
process.exit();
}
})();
+76 -1
View File
@@ -922,6 +922,11 @@ async function importProducts(prodConnection, localConnection, incrementalUpdate
// Cleanup temporary tables
await cleanupTemporaryTables(localConnection);
// Sync supplier-quoted cost fields (notions_cost_each / supplier_cost_each).
// These feed the Create-PO page so the displayed cost matches what the
// legacy PHP backend will stamp onto the PO line item.
await syncSupplierCosts(prodConnection, localConnection);
// Commit the transaction
await localConnection.commit();
@@ -954,10 +959,80 @@ async function importProducts(prodConnection, localConnection, incrementalUpdate
}
}
// Bulk-sync supplier_item_data.notions_cost_each / supplier_cost_each into
// products.{notions_cost_each, supplier_cost_each}. These mirror the supplier-
// quoted "cost each" values the legacy PHP backend writes onto a PO when
// _product_add() runs (see po.class.php:189-209). Kept as a separate, idempotent
// pass so the main 49-column import paths don't need to know about it.
async function syncSupplierCosts(prodConnection, localConnection) {
outputProgress({
status: "running",
operation: "Products import",
message: "Syncing supplier costs from supplier_item_data"
});
const [rows] = await prodConnection.query(`
SELECT pid, notions_cost_each, supplier_cost_each
FROM supplier_item_data
`);
if (!rows || rows.length === 0) {
return { updated: 0 };
}
// Stage into a temp table, then UPDATE in a single SQL statement.
await localConnection.query(`
CREATE TEMP TABLE temp_supplier_costs (
pid BIGINT PRIMARY KEY,
notions_cost_each NUMERIC(10,3),
supplier_cost_each NUMERIC(10,3)
) ON COMMIT DROP
`);
const CHUNK = 5000;
for (let i = 0; i < rows.length; i += CHUNK) {
const batch = rows.slice(i, i + CHUNK);
const placeholders = batch
.map((_, idx) => `($${idx * 3 + 1}, $${idx * 3 + 2}, $${idx * 3 + 3})`)
.join(',');
const values = batch.flatMap(r => [
r.pid,
r.notions_cost_each,
r.supplier_cost_each
]);
await localConnection.query(
`INSERT INTO temp_supplier_costs (pid, notions_cost_each, supplier_cost_each)
VALUES ${placeholders}
ON CONFLICT (pid) DO NOTHING`,
values
);
}
const [result] = await localConnection.query(`
UPDATE products p
SET notions_cost_each = t.notions_cost_each,
supplier_cost_each = t.supplier_cost_each
FROM temp_supplier_costs t
WHERE p.pid = t.pid
AND (p.notions_cost_each IS DISTINCT FROM t.notions_cost_each
OR p.supplier_cost_each IS DISTINCT FROM t.supplier_cost_each)
`);
const updated = result.rowCount || 0;
outputProgress({
status: "running",
operation: "Products import",
message: `Supplier costs synced for ${updated} products`
});
return { updated };
}
module.exports = {
importProducts,
importMissingProducts,
setupTemporaryTables,
cleanupTemporaryTables,
materializeCalculations
materializeCalculations,
syncSupplierCosts
};