Files
MIS-Contro-Tower/scripts/patch-flows-reason-mirror.mjs
2026-05-06 00:36:48 +00:00

214 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Patches flows_may_4_26.json:
* - Apply settings: pass reasonCode/active in catalog; 3 outputs; trigger MySQL mirror sync
* - New nodes: Build reason catalog mirror SQL → mysql
*/
import { readFileSync, writeFileSync } from "fs";
const path = new URL("../flows_may_4_26.json", import.meta.url).pathname;
const j = JSON.parse(readFileSync(path, "utf8"));
const applyId = "abbec199700a5e29";
const gateId = "f8e0d1c2b3a40911";
const mysqlPersistId = "f8e0d1c2b3a40912";
const apply = j.find((n) => n.id === applyId);
if (!apply || apply.type !== "function") {
console.error("Apply settings node not found");
process.exit(1);
}
const oldDetails =
"const details = detailsRaw.map((d, jdx) => ({\n id: String(d.id || d.detailId || (categoryId + \"_d\" + jdx)),\n label: String(d.label || d.detailLabel || (\"Detalle \" + (jdx + 1)))\n }));";
const newDetails = `const details = detailsRaw.map((d, jdx) => {
const row = {
id: String(d.id || d.detailId || (categoryId + "_d" + jdx)),
label: String(d.label || d.detailLabel || ("Detalle " + (jdx + 1)))
};
if (d.reasonCode != null && String(d.reasonCode).trim()) {
row.reasonCode = String(d.reasonCode).trim();
} else if (d.code != null && String(d.code).trim()) {
row.reasonCode = String(d.code).trim();
}
if (d.active === false) {
row.active = false;
}
return row;
});`;
if (!apply.func.includes(oldDetails)) {
console.error("Expected normalizeCatalog details snippet not found; abort.");
process.exit(1);
}
apply.func = apply.func.replace(oldDetails, newDetails);
apply.func = apply.func.replaceAll("node.send([uiConfigMsg, null]);", "node.send([uiConfigMsg, null, null]);");
apply.func = apply.func.replaceAll("node.send([uiMoldMsg, null]);", "node.send([uiMoldMsg, null, null]);");
apply.func = apply.func.replaceAll("node.send([uiReadOnlyMsg, null]);", "node.send([uiReadOnlyMsg, null, null]);");
apply.func = apply.func.replaceAll("node.send([uiReasonCatalogMsg, null]);", "node.send([uiReasonCatalogMsg, null, null]);");
const oldReturnAck = `const ackMsg = {
topic: ackTopic,
payload: JSON.stringify({
type: "settings_ack",
orgId,
machineId,
version,
source: "node-red",
ts: new Date().toISOString()
})
};
return [null, ackMsg];
`;
const newReturnAck = `const ackMsg = {
topic: ackTopic,
payload: JSON.stringify({
type: "settings_ack",
orgId,
machineId,
version,
source: "node-red",
ts: new Date().toISOString()
})
};
const mirrorTrigger = { payload: { _syncReasonCatalog: true } };
return [null, ackMsg, mirrorTrigger];
`;
if (!apply.func.includes(oldReturnAck.trim())) {
console.error("Expected ack return block not found");
process.exit(1);
}
apply.func = apply.func.replace(oldReturnAck.trim(), newReturnAck.trim());
apply.func = apply.func.replace(
`if (!orgId || !machineId) {
return [null, null];
}`,
`if (!orgId || !machineId) {
return [null, null, null];
}`
);
apply.outputs = 3;
apply.wires = [
["2c8562b2471078ab", "dbfd127c516efa87", "9748899355370bae"],
[],
[gateId],
];
const gateFunc = `const p = msg.payload || {};
if (!p._syncReasonCatalog) {
return null;
}
const settings = global.get("settings") || {};
const cat = settings.reasonCatalog || {};
const ver = Number(cat.version || 1);
function esc(v) {
return String(v ?? "").replace(/\\\\/g, "\\\\\\\\").replace(/'/g, "''");
}
const parts = [];
function walk(kind, list) {
if (!Array.isArray(list)) {
return;
}
let sort = 0;
list.forEach((c) => {
const categoryId = esc(String(c.id || ""));
const categoryLabel = esc(String(c.label || ""));
const ch = c.children || c.details || [];
if (!Array.isArray(ch)) {
return;
}
ch.forEach((d) => {
const id = String(d.id || "").trim();
const label = String(d.label || "").trim();
const rc = String(d.reasonCode || d.code || id || "").trim();
if (!rc) {
return;
}
const active = d.active === false ? 0 : 1;
parts.push(
"('" +
kind +
"','" +
categoryId +
"','" +
categoryLabel +
"','" +
esc(rc) +
"','" +
esc(label) +
"'," +
sort +
"," +
active +
"," +
ver +
")"
);
sort += 1;
});
});
}
walk("downtime", cat.downtime || []);
walk("scrap", cat.scrap || []);
if (!parts.length) {
node.status({ fill: "yellow", shape: "ring", text: "No reason rows to mirror" });
return null;
}
const sql =
"INSERT INTO reason_catalog_row (kind,category_id,category_label,reason_code,reason_label,sort_order,active,catalog_version) VALUES " +
parts.join(",") +
" ON DUPLICATE KEY UPDATE category_id=VALUES(category_id),category_label=VALUES(category_label),reason_label=VALUES(reason_label),sort_order=VALUES(sort_order),active=VALUES(active),catalog_version=VALUES(catalog_version),updated_at=CURRENT_TIMESTAMP(3)";
node.status({ fill: "green", shape: "dot", text: "Reason mirror SQL built" });
msg.topic = sql;
msg.payload = [];
return msg;
`;
const gateNode = {
id: gateId,
type: "function",
z: "05d4cb231221b842",
g: "a1b43a9e095c10db",
name: "Build reason catalog mirror SQL",
func: gateFunc,
outputs: 1,
timeout: 0,
noerr: 0,
initialize: "",
finalize: "",
libs: [],
x: 1500,
y: 1020,
wires: [[mysqlPersistId]],
};
const mysqlNode = {
id: mysqlPersistId,
type: "mysql",
z: "05d4cb231221b842",
g: "a1b43a9e095c10db",
mydb: "fc9634aabefee16b",
name: "Persist reason catalog mirror",
x: 1820,
y: 1020,
wires: [[]],
};
if (j.some((n) => n.id === gateId)) {
console.log("Patch already applied (gate node exists). Skipping insert.");
} else {
const idx = j.findIndex((n) => n.id === applyId);
j.splice(idx + 1, 0, gateNode, mysqlNode);
}
writeFileSync(path, JSON.stringify(j, null, 4) + "\n");
console.log("Patched", path);