140 lines
11 KiB
JavaScript
140 lines
11 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const { spawn } = require("child_process");
|
|
|
|
const root = "C:\\project\\FT546WMS-SHANDOW";
|
|
const outDir = "C:\\project\\FT546WMS-shadow-export-0828";
|
|
const config = path.join(root, ".cursorrules-mcp.json");
|
|
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
|
|
function redact(value) {
|
|
return String(value)
|
|
.replace(/password\s*[:=]\s*[^\s,}]+/gi, "password=[redacted]")
|
|
.replace(/DB_PASSWORD[^\s,}]*/gi, "DB_PASSWORD=[redacted]");
|
|
}
|
|
|
|
function createClient() {
|
|
const child = spawn("powershell.exe", [
|
|
"-NoProfile",
|
|
"-ExecutionPolicy",
|
|
"Bypass",
|
|
"-File",
|
|
"C:\\project\\sqlserver-mcp-server\\start.ps1",
|
|
"-ConfigPath",
|
|
config
|
|
], { stdio: ["pipe", "pipe", "pipe"] });
|
|
let buffer = "";
|
|
let nextId = 1;
|
|
const pending = new Map();
|
|
let stderr = "";
|
|
child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
|
|
child.stdout.on("data", (chunk) => {
|
|
buffer += chunk.toString();
|
|
const lines = buffer.split(/\r?\n/);
|
|
buffer = lines.pop();
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
try {
|
|
const message = JSON.parse(line);
|
|
const resolve = pending.get(message.id);
|
|
if (resolve) {
|
|
pending.delete(message.id);
|
|
resolve(message);
|
|
}
|
|
} catch {
|
|
// Ignore non-protocol stdout, if a server emits any.
|
|
}
|
|
}
|
|
});
|
|
|
|
function call(method, params) {
|
|
return new Promise((resolve, reject) => {
|
|
const id = nextId++;
|
|
pending.set(id, (message) => {
|
|
if (message.error) reject(new Error(message.error.message || "MCP error"));
|
|
else resolve(message.result);
|
|
});
|
|
child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
|
|
});
|
|
}
|
|
|
|
return { child, call, getStderr: () => stderr };
|
|
}
|
|
|
|
function parseResult(result) {
|
|
const content = (result.content || []).find((item) => item.type === "text");
|
|
if (!content) throw new Error("MCP returned no text result");
|
|
return JSON.parse(content.text);
|
|
}
|
|
|
|
async function main() {
|
|
const client = createClient();
|
|
try {
|
|
await client.call("initialize", {
|
|
protocolVersion: "2025-06-18",
|
|
capabilities: {},
|
|
clientInfo: { name: "ft546-shadow-export", version: "1.0" }
|
|
});
|
|
client.child.stdin.write(JSON.stringify({
|
|
jsonrpc: "2.0", method: "notifications/initialized"
|
|
}) + "\n");
|
|
|
|
const callRead = async (query) => parseResult(await client.call("tools/call", {
|
|
name: "sqlserver_read_data",
|
|
arguments: { databaseName: "FT546-wms", query, response_format: "json" }
|
|
}));
|
|
const callTool = async (name, arguments_) => parseResult(await client.call("tools/call", {
|
|
name,
|
|
arguments: { ...arguments_, response_format: "json" }
|
|
}));
|
|
|
|
const databases = await callTool("sqlserver_list_databases", {});
|
|
if (!databases.databases.includes("FT546-wms")) throw new Error("Database is not in MCP allowlist");
|
|
|
|
const diagnosticQueries = [
|
|
["connection", "SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar(30)) AS product_version, DB_NAME() AS database_name, SYSUTCDATETIME() AS checked_at_utc;"],
|
|
["package_manifest", "SELECT package_id, package_version, installed_at FROM alg.shadow_package_manifest;"],
|
|
["alg_objects", "SELECT schema_name(o.schema_id) AS schema_name, o.name AS object_name, o.type_desc FROM sys.objects o WHERE schema_name(o.schema_id) = N'alg' ORDER BY schema_name(o.schema_id), o.type_desc, o.name;"],
|
|
["lane_state", "SELECT warehouse_id, COUNT(*) AS lane_state_count, SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_lane_count, SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_lane_count, MAX(rebuilt_at) AS latest_rebuild_at FROM alg.lane_state GROUP BY warehouse_id ORDER BY warehouse_id;"],
|
|
["inventory_snapshot", "SELECT warehouse_id, COUNT(*) AS inventory_item_count, COUNT(DISTINCT space_id) AS inventory_space_count, MAX(synchronized_at) AS latest_inventory_sync_at FROM alg.inventory_item_snapshot GROUP BY warehouse_id ORDER BY warehouse_id;"],
|
|
["space_state", "SELECT warehouse_id, COUNT(*) AS space_state_count, SUM(CASE WHEN inbound_available = 1 THEN 1 ELSE 0 END) AS inbound_available_count, SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_outbound_count, SUM(CASE WHEN tray_conflict = 1 OR project_conflict = 1 OR product_conflict = 1 THEN 1 ELSE 0 END) AS conflict_count FROM alg.v_space_state GROUP BY warehouse_id ORDER BY warehouse_id;"],
|
|
["legacy_checksums", "SELECT 'bas_cargo_space' AS table_name, COUNT_BIG(*) AS row_count, CHECKSUM_AGG(BINARY_CHECKSUM(cargo_space_id, warehouse_id, group_coding_no, group_coding, status, tray_code)) AS row_checksum FROM dbo.bas_cargo_space UNION ALL SELECT 'bas_stock', COUNT_BIG(*), CHECKSUM_AGG(BINARY_CHECKSUM(stock_id, cargo_space_id, tray_code, spare4, product_id, lot_no, in_date)) FROM dbo.bas_stock UNION ALL SELECT 'mydo_job', COUNT_BIG(*), CHECKSUM_AGG(BINARY_CHECKSUM(job_id, status, task_no, tray_code, cargo_space_id, target_cargo_space_id, in_or_out, warehouse_id)) FROM dbo.mydo_job;"],
|
|
["locks", "SELECT request_session_id, resource_type, request_mode, request_status, object_name(partition.object_id, lock.resource_database_id) AS object_name FROM sys.dm_tran_locks lock LEFT JOIN sys.partitions partition ON partition.hobt_id = lock.resource_associated_entity_id WHERE lock.resource_database_id = DB_ID() AND object_name(partition.object_id, lock.resource_database_id) IN (N'bas_cargo_space', N'bas_stock', N'mydo_job') ORDER BY request_session_id, object_name, request_mode;"],
|
|
["decision_log", "SELECT decision_id, warehouse_id, decision_type, input_version, result_code, reason, created_at FROM alg.decision_log WHERE decision_type IN ('INBOUND', 'OUTBOUND', 'INBOUND_COMPARE', 'OUTBOUND_COMPARE') ORDER BY created_at, decision_id;"]
|
|
];
|
|
|
|
const diagnosticResults = [];
|
|
for (const [name, query] of diagnosticQueries) diagnosticResults.push({ name, result: await callRead(query) });
|
|
|
|
const detailStart = "2026-08-18T16:00:00";
|
|
const detailEnd = new Date().toISOString().slice(0, 19);
|
|
const detailQueries = [
|
|
["connection", `SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar(30)) AS product_version, DB_NAME() AS database_name, SYSUTCDATETIME() AS checked_at_utc, '01' AS warehouse_id, CAST('${detailStart}' AS datetime2) AS export_start_utc, CAST('${detailEnd}' AS datetime2) AS export_end_utc`],
|
|
["package_manifest", "SELECT package_id, package_version, installed_at FROM alg.shadow_package_manifest;"],
|
|
["decision_summary", `SELECT decision_type, result_code, COUNT(*) AS row_count, MIN(created_at) AS first_created_at, MAX(created_at) AS last_created_at FROM alg.decision_log WHERE created_at >= CAST('${detailStart}' AS datetime2) AND created_at < CAST('${detailEnd}' AS datetime2) GROUP BY decision_type, result_code ORDER BY decision_type, result_code`],
|
|
["decision_log", `SELECT decision_id, warehouse_id, decision_type, input_version, result_code, reason, created_at FROM alg.decision_log WHERE created_at >= CAST('${detailStart}' AS datetime2) AND created_at < CAST('${detailEnd}' AS datetime2) ORDER BY created_at, decision_id`],
|
|
["outbound_jobs", `SELECT TOP (300) d.created_at AS decision_created_at, d.result_code AS shadow_compare_result, d.reason, detail.stockout_detail_id, detail.stockout_id, detail.warehouse_id, detail.spare4 AS project_no, detail.product_id, detail.quantity, detail.allot_quantity, detail.assign_num, detail.create_time AS detail_create_time FROM alg.decision_log d OUTER APPLY (SELECT LTRIM(RTRIM(SUBSTRING(d.reason, CHARINDEX('request=', d.reason) + 8, CHARINDEX(CHAR(59), d.reason + CHAR(59), CHARINDEX('request=', d.reason)) - (CHARINDEX('request=', d.reason) + 8)))) AS request_id) r LEFT JOIN dbo.out_stockout_detail detail ON detail.stockout_detail_id = r.request_id AND TRY_CONVERT(int, detail.warehouse_id) = TRY_CONVERT(int, '01') WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= CAST('${detailStart}' AS datetime2) AND d.created_at < CAST('${detailEnd}' AS datetime2) ORDER BY d.created_at, detail.create_time, detail.stockout_detail_id`],
|
|
["inventory_mapping", "SELECT TOP (1000) c.warehouse_id, s.stock_id, s.cargo_space_id, s.tray_code, s.spare4 AS legacy_project_no, s.product_id AS legacy_product_no, s.lot_no, s.in_date, s.quantity, s.out_quantity, i.space_id AS shadow_space_id, i.tray_code AS shadow_tray_code, i.project_no AS shadow_project_no, i.product_no AS shadow_product_no, i.inbound_time_raw, i.inbound_at, i.quantity AS shadow_quantity, i.out_quantity AS shadow_out_quantity FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '01' LEFT JOIN alg.v_inventory_item i ON i.stock_id = s.stock_id ORDER BY s.stock_id;"],
|
|
["jobs", "SELECT TOP (1000) job_id, task_no, tray_code, cargo_space_id, target_cargo_space_id, warehouse_id, status, in_or_out, create_time, finish_time FROM dbo.mydo_job WHERE warehouse_id = '01' ORDER BY create_time, job_id;"],
|
|
["lane_state", "SELECT warehouse_id, COUNT(*) AS lane_state_count, SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_lane_count, SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_lane_count, MAX(rebuilt_at) AS latest_rebuild_at FROM alg.lane_state WHERE warehouse_id = '01' GROUP BY warehouse_id;"],
|
|
["inventory_stats", "SELECT warehouse_id, COUNT(*) AS inventory_item_count, COUNT(DISTINCT space_id) AS inventory_space_count, MAX(synchronized_at) AS latest_inventory_sync_at FROM alg.inventory_item_snapshot WHERE warehouse_id = '01' GROUP BY warehouse_id;"],
|
|
["legacy_checksums", "SELECT 'bas_cargo_space' AS table_name, COUNT_BIG(*) AS row_count, CHECKSUM_AGG(BINARY_CHECKSUM(cargo_space_id, warehouse_id, group_coding_no, group_coding, status, tray_code)) AS row_checksum FROM dbo.bas_cargo_space WHERE warehouse_id = '01' UNION ALL SELECT 'bas_stock', COUNT_BIG(*), CHECKSUM_AGG(BINARY_CHECKSUM(s.stock_id, s.cargo_space_id, s.tray_code, s.spare4, s.product_id, s.lot_no, s.in_date)) FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id WHERE c.warehouse_id = '01' UNION ALL SELECT 'mydo_job', COUNT_BIG(*), CHECKSUM_AGG(BINARY_CHECKSUM(job_id, status, task_no, tray_code, cargo_space_id, target_cargo_space_id, in_or_out, warehouse_id)) FROM dbo.mydo_job WHERE warehouse_id = '01';"]
|
|
];
|
|
|
|
const detailResults = [];
|
|
for (const [name, query] of detailQueries) detailResults.push({ name, result: await callRead(query) });
|
|
const detailSql = detailQueries.map(([name, query]) => `-- ${name}\n${query}`).join(";\n\n") + ";\n";
|
|
fs.writeFileSync(path.join(outDir, "shadow-detail-export.sql"), detailSql, "utf8");
|
|
fs.writeFileSync(path.join(outDir, "shadow-detail-export.txt"), JSON.stringify({ export_type: "shadow-detail-export", results: detailResults }, null, 2) + "\n", "utf8");
|
|
console.log("EXPORT_OK diagnostics-shadow.txt shadow-detail-export.txt");
|
|
} finally {
|
|
client.child.kill();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error("EXPORT_FAILED " + redact(error.message));
|
|
process.exitCode = 1;
|
|
});
|