diff --git a/.gitignore b/.gitignore index 024ffd5..0877396 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ hs_err_pid* **/target/ /log/ +# Local MCP credentials and connection settings. +.cursorrules-mcp.json diff --git a/opencode.jsonc b/opencode.jsonc new file mode 100644 index 0000000..6d0d5d9 --- /dev/null +++ b/opencode.jsonc @@ -0,0 +1,20 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "ft546-sqlserver": { + "type": "local", + "command": [ + "powershell.exe", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "C:\\project\\sqlserver-mcp-server\\start.ps1", + "-ConfigPath", + "C:\\project\\FT546WMS-SHANDOW\\.cursorrules-mcp.json" + ], + "enabled": true, + "timeout": 30000 + } + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/outStockout/service/impl/OutStockoutServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/out/outStockout/service/impl/OutStockoutServiceImpl.java index 91c93a9..a286d22 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/outStockout/service/impl/OutStockoutServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/outStockout/service/impl/OutStockoutServiceImpl.java @@ -127,27 +127,36 @@ public class OutStockoutServiceImpl implements IOutStockoutService @Override @Transactional(rollbackFor = Exception.class) public String saveBill(OutStockoutVo outStockoutVo) { - OutStockout outStockout = outStockoutVo.getOutStockout(); - List detailList = outStockoutVo.getDetailList(); - String stockOutId = basJobIdService.getJobId("CKD"); - outStockout.setStockoutId(stockOutId); - outStockout.setStockoutGuid(UUID.randomUUID().toString()); - outStockout.setCreateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); - String username = SecurityContextHolder.getContext().getAuthentication().getName(); - outStockout.setCreateManId(username); - outStockout.setStatus("1"); - outStockout.setStockoutSource("0"); - outStockout.setWarehouseId("01"); - for (int i = 0; i < detailList.size(); i++) { - OutStockoutDetail outStockoutDetail = detailList.get(i); - outStockoutDetail.setStockoutDetailId(stockOutId + "-" + (i + 1)); - outStockoutDetail.setStockoutGuid(outStockout.getStockoutGuid()); - outStockoutDetail.setStockoutId(stockOutId); - outStockoutDetail.setAllotQuantity(BigDecimal.ZERO); - outStockoutDetail.setAssignNum(BigDecimal.ZERO); - outStockoutDetailMapper.insertOutStockoutDetail(outStockoutDetail); - } - outStockoutMapper.insertOutStockout(outStockout); + OutStockout outStockout = outStockoutVo.getOutStockout(); + List detailList = outStockoutVo.getDetailList(); + String stockOutId = basJobIdService.getJobId("CKD"); + outStockout.setStockoutId(stockOutId); + outStockout.setStockoutGuid(UUID.randomUUID().toString()); + outStockout.setCreateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + outStockout.setCreateManId(username); + outStockout.setStatus("1"); + outStockout.setStockoutSource("0"); + String warehouseId = outStockout.getWarehouseId(); + if ("01".equals(warehouseId) || "1".equals(warehouseId)) { + warehouseId = "1"; + } else if ("02".equals(warehouseId) || "2".equals(warehouseId)) { + warehouseId = "2"; + } else { + throw new IllegalArgumentException("仓库编号只能是1(立体库)或2(平库)"); + } + outStockout.setWarehouseId(warehouseId); + for (int i = 0; i < detailList.size(); i++) { + OutStockoutDetail outStockoutDetail = detailList.get(i); + outStockoutDetail.setStockoutDetailId(stockOutId + "-" + (i + 1)); + outStockoutDetail.setStockoutGuid(outStockout.getStockoutGuid()); + outStockoutDetail.setStockoutId(stockOutId); + outStockoutDetail.setAllotQuantity(BigDecimal.ZERO); + outStockoutDetail.setAssignNum(BigDecimal.ZERO); + outStockoutDetail.setWarehouseId(warehouseId); + outStockoutDetailMapper.insertOutStockoutDetail(outStockoutDetail); + } + outStockoutMapper.insertOutStockout(outStockout); return stockOutId; } @@ -177,6 +186,9 @@ public class OutStockoutServiceImpl implements IOutStockoutService if ("1".equals(outStockout.getStatus())){ return AjaxResult.warn("单据未审核,请审核无误后再分配!"); } + if (outStockout.getWarehouseId().equals("2")){ + return AjaxResult.warn("平库单据请使用手持机操作!"); + } HashMap map = new HashMap<>(); StringBuilder stringBuilder = new StringBuilder(); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/controller/ViewProductAndStockController.java b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/controller/ViewProductAndStockController.java index 90f41d4..d983487 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/controller/ViewProductAndStockController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/controller/ViewProductAndStockController.java @@ -42,7 +42,9 @@ public class ViewProductAndStockController extends BaseController public TableDataInfo list(ViewProductAndStock viewProductAndStock) { startPage(); - List list = viewProductAndStockService.selectViewProductAndStockList(viewProductAndStock); + List list = "2".equals(viewProductAndStock.getWarehouseId()) + ? viewProductAndStockService.selectFlatProductAndStockList(viewProductAndStock) + : viewProductAndStockService.selectViewProductAndStockList(viewProductAndStock); return getDataTable(list); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/mapper/ViewProductAndStockMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/mapper/ViewProductAndStockMapper.java index 34ee239..768a831 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/mapper/ViewProductAndStockMapper.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/mapper/ViewProductAndStockMapper.java @@ -60,4 +60,6 @@ public interface ViewProductAndStockMapper public int deleteViewProductAndStockByOnlyIdentitys(Long[] onlyIdentitys); List selectViewProductAndStockPdList(ViewProductAndStock viewProductAndStock); + + List selectFlatProductAndStockList(ViewProductAndStock viewProductAndStock); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/IViewProductAndStockService.java b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/IViewProductAndStockService.java index 0b0a07d..3881093 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/IViewProductAndStockService.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/IViewProductAndStockService.java @@ -67,4 +67,5 @@ public interface IViewProductAndStockService */ public int deleteViewProductAndStockByOnlyIdentity(Long onlyIdentity); + List selectFlatProductAndStockList(ViewProductAndStock viewProductAndStock); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/impl/ViewProductAndStockServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/impl/ViewProductAndStockServiceImpl.java index ad2708b..21bb07c 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/impl/ViewProductAndStockServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/productAndStock/service/impl/ViewProductAndStockServiceImpl.java @@ -102,4 +102,9 @@ public class ViewProductAndStockServiceImpl implements IViewProductAndStockServi { return viewProductAndStockMapper.deleteViewProductAndStockByOnlyIdentity(onlyIdentity); } + + @Override + public List selectFlatProductAndStockList(ViewProductAndStock viewProductAndStock) { + return viewProductAndStockMapper.selectFlatProductAndStockList(viewProductAndStock); + } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaServiceImp.java b/ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaServiceImp.java index 0af0753..dd0b2eb 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaServiceImp.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/pda/service/PdaServiceImp.java @@ -132,7 +132,7 @@ public class PdaServiceImp implements PdaService { pdaMapper.bindTray(map); if (!"10".equals(map.get("v_result").toString())) { errorbulider.append(mydoJobDetail.getStockinDetailId() + map.get("v_resultmsg").toString()); - throw new RuntimeException(map.get("v_result").toString()); + throw new RuntimeException(map.get("v_resultmsg").toString()); } } HashMap map = new HashMap<>(); @@ -140,7 +140,7 @@ public class PdaServiceImp implements PdaService { map.put("v_is_full", trayCodeMakeSure.getIsFull()); pdaMapper.addInJob(map); if (!"10".equals(map.get("v_result").toString())) { - throw new RuntimeException(map.get("v_result").toString()); + throw new RuntimeException(map.get("v_resultmsg").toString()); } return AjaxResult.success(); } diff --git a/ruoyi-admin/src/main/resources/mapper/productAndStock/ViewProductAndStockMapper.xml b/ruoyi-admin/src/main/resources/mapper/productAndStock/ViewProductAndStockMapper.xml index d9378e5..a0d77e3 100644 --- a/ruoyi-admin/src/main/resources/mapper/productAndStock/ViewProductAndStockMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/productAndStock/ViewProductAndStockMapper.xml @@ -81,6 +81,49 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + insert into view_product_and_stock diff --git a/ruoyi-admin/src/main/resources/mapper/stock/BasStockMapper.xml b/ruoyi-admin/src/main/resources/mapper/stock/BasStockMapper.xml index ffa78de..f2c2ed0 100644 --- a/ruoyi-admin/src/main/resources/mapper/stock/BasStockMapper.xml +++ b/ruoyi-admin/src/main/resources/mapper/stock/BasStockMapper.xml @@ -507,7 +507,6 @@ - and bs.lot_no like concat('%', #{out.lotNo}, '%') and bs.tray_code like concat('%', #{trayCode}, '%') diff --git a/scripts/shadow-export-mcp.js b/scripts/shadow-export-mcp.js new file mode 100644 index 0000000..677a0cc --- /dev/null +++ b/scripts/shadow-export-mcp.js @@ -0,0 +1,139 @@ +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, j.job_id, j.task_no, j.tray_code AS job_tray_code, j.cargo_space_id AS job_source_space_id, j.target_cargo_space_id, j.warehouse_id, j.status AS job_status, j.in_or_out, j.create_time, j.finish_time FROM alg.decision_log d LEFT JOIN dbo.mydo_job j ON d.reason LIKE '%request=' + CONVERT(varchar(255), j.task_no) + '%' AND j.warehouse_id = '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, j.create_time, j.task_no`], + ["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; +}); diff --git a/scripts/shadow-followup-mcp.js b/scripts/shadow-followup-mcp.js new file mode 100644 index 0000000..1f60523 --- /dev/null +++ b/scripts/shadow-followup-mcp.js @@ -0,0 +1,117 @@ +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"); +const database = "FT546-wms"; + +fs.mkdirSync(outDir, { recursive: true }); + +function redact(value) { + return String(value).replace(/password\s*[:=]\s*[^\s,}]+/gi, "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(); + 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 { + // The MCP protocol stream is line-delimited JSON. + } + } + }); + function call(method, params) { + return new Promise((resolve, reject) => { + const id = nextId++; + pending.set(id, (message) => message.error + ? reject(new Error(message.error.message || "MCP error")) + : resolve(message.result)); + child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); + }); + } + return { child, call }; +} + +function parseResult(result) { + const item = (result.content || []).find((entry) => entry.type === "text"); + if (!item) throw new Error("MCP returned no result text"); + try { + return JSON.parse(item.text); + } catch { + throw new Error(item.text.replace(/password\s*[:=]\s*[^\s,}]+/gi, "password=[redacted]").replace(/\r?\n/g, " ")); + } +} + +async function main() { + const client = createClient(); + try { + await client.call("initialize", { + protocolVersion: "2025-06-18", + capabilities: {}, + clientInfo: { name: "ft546-shadow-followup", version: "1.0" } + }); + client.child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n"); + + const start = "2026-08-18T00:00:00"; + const end = new Date().toISOString().slice(0, 19); + const warehouse = "01"; + const callRead = async (name, query) => ({ + name, + result: parseResult(await client.call("tools/call", { + name: "sqlserver_read_data", + arguments: { databaseName: database, query, response_format: "json" } + })) + }); + + const queries = [ + ["inventory_counts", `SELECT 'legacy_bas_stock' AS source_name, COUNT_BIG(*) AS row_count 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 = '${warehouse}' UNION ALL SELECT 'shadow_inventory_item_snapshot', COUNT_BIG(*) FROM alg.inventory_item_snapshot WHERE warehouse_id = '${warehouse}'`], + ["legacy_missing_from_shadow", `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 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 = '${warehouse}' LEFT JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id WHERE i.stock_id IS NULL ORDER BY s.stock_id`], + ["shadow_missing_from_legacy", `SELECT TOP (1000) i.warehouse_id, i.stock_id, i.space_id, i.tray_code, i.project_no, i.product_no, i.lot_no, i.inbound_time_raw, i.quantity, i.out_quantity FROM alg.inventory_item_snapshot i LEFT JOIN dbo.bas_stock s ON s.stock_id = i.stock_id WHERE i.warehouse_id = '${warehouse}' AND s.stock_id IS NULL ORDER BY i.stock_id`], + ["matched_field_difference_counts", `SELECT COUNT_BIG(*) AS matched_stock_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.cargo_space_id)), '') <> ISNULL(LTRIM(RTRIM(i.space_id)), '') THEN 1 ELSE 0 END) AS space_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.tray_code)), '') <> ISNULL(LTRIM(RTRIM(i.tray_code)), '') THEN 1 ELSE 0 END) AS tray_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.spare4)), '') <> ISNULL(LTRIM(RTRIM(i.project_no)), '') THEN 1 ELSE 0 END) AS project_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.product_id)), '') <> ISNULL(LTRIM(RTRIM(i.product_no)), '') THEN 1 ELSE 0 END) AS product_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.lot_no)), '') <> ISNULL(LTRIM(RTRIM(i.lot_no)), '') THEN 1 ELSE 0 END) AS lot_mismatch_count, SUM(CASE WHEN ISNULL(s.quantity, 0) <> ISNULL(i.quantity, 0) THEN 1 ELSE 0 END) AS quantity_mismatch_count, SUM(CASE WHEN ISNULL(s.out_quantity, 0) <> ISNULL(i.out_quantity, 0) THEN 1 ELSE 0 END) AS out_quantity_mismatch_count 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 = '${warehouse}' INNER JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id`], + ["field_difference_rows", `SELECT TOP (1000) s.stock_id, s.cargo_space_id AS legacy_space_id, i.space_id AS shadow_space_id, s.tray_code AS legacy_tray_code, i.tray_code AS shadow_tray_code, s.spare4 AS legacy_project_no, i.project_no AS shadow_project_no, s.product_id AS legacy_product_no, i.product_no AS shadow_product_no, s.lot_no AS legacy_lot_no, i.lot_no AS shadow_lot_no, s.quantity AS legacy_quantity, i.quantity AS shadow_quantity, s.out_quantity AS legacy_out_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 = '${warehouse}' INNER JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id WHERE ISNULL(LTRIM(RTRIM(s.cargo_space_id)), '') <> ISNULL(LTRIM(RTRIM(i.space_id)), '') OR ISNULL(LTRIM(RTRIM(s.tray_code)), '') <> ISNULL(LTRIM(RTRIM(i.tray_code)), '') OR ISNULL(LTRIM(RTRIM(s.spare4)), '') <> ISNULL(LTRIM(RTRIM(i.project_no)), '') OR ISNULL(LTRIM(RTRIM(s.product_id)), '') <> ISNULL(LTRIM(RTRIM(i.product_no)), '') OR ISNULL(LTRIM(RTRIM(s.lot_no)), '') <> ISNULL(LTRIM(RTRIM(i.lot_no)), '') OR ISNULL(s.quantity, 0) <> ISNULL(i.quantity, 0) OR ISNULL(s.out_quantity, 0) <> ISNULL(i.out_quantity, 0) ORDER BY s.stock_id`], + ["request_match_counts", `SELECT COUNT_BIG(*) AS compare_count, SUM(CASE WHEN j_by_id.job_id IS NOT NULL THEN 1 ELSE 0 END) AS matched_by_job_id, SUM(CASE WHEN j_by_id.job_id IS NULL AND j_by_task.job_id IS NOT NULL THEN 1 ELSE 0 END) AS matched_by_task_no, SUM(CASE WHEN j_by_id.job_id IS NULL AND j_by_task.job_id IS NULL THEN 1 ELSE 0 END) AS unmatched_count 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.mydo_job j_by_id ON j_by_id.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_id.job_id) = r.request_id LEFT JOIN dbo.mydo_job j_by_task ON j_by_task.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_task.task_no) = r.request_id WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= '${start}' AND d.created_at < '${end}'`], + ["request_match_details", `SELECT TOP (300) d.created_at, d.result_code, r.request_id, j_by_id.job_id AS job_id_match, j_by_id.task_no AS job_id_match_task_no, j_by_id.tray_code AS job_id_match_tray_code, j_by_id.status AS job_id_match_status, j_by_id.in_or_out AS job_id_match_in_or_out, j_by_task.job_id AS task_no_match_job_id, j_by_task.task_no AS task_no_match_task_no, j_by_task.tray_code AS task_no_match_tray_code, j_by_task.status AS task_no_match_status, d.reason 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.mydo_job j_by_id ON j_by_id.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_id.job_id) = r.request_id LEFT JOIN dbo.mydo_job j_by_task ON j_by_task.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_task.task_no) = r.request_id WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= '${start}' AND d.created_at < '${end}' ORDER BY d.created_at`] + ]; + + const results = []; + for (const [name, query] of queries) results.push(await callRead(name, query)); + const output = { + export_type: "shadow-followup", + database, + warehouse_id: warehouse, + export_start_utc: start, + export_end_utc: end, + results + }; + fs.writeFileSync(path.join(outDir, "shadow-followup.sql"), queries.map(([name, query]) => `-- ${name}\n${query}`).join(";\n\n") + ";\n", "utf8"); + fs.writeFileSync(path.join(outDir, "shadow-followup.txt"), JSON.stringify(output, null, 2) + "\n", "utf8"); + console.log("FOLLOWUP_EXPORT_OK shadow-followup.txt result_sets=" + results.length); + } finally { + client.child.kill(); + } +} + +main().catch((error) => { + console.error("FOLLOWUP_EXPORT_FAILED " + redact(error.message)); + process.exitCode = 1; +}); diff --git a/scripts/stockout-detail-check-mcp.js b/scripts/stockout-detail-check-mcp.js new file mode 100644 index 0000000..f558c1e --- /dev/null +++ b/scripts/stockout-detail-check-mcp.js @@ -0,0 +1,102 @@ +const { spawn } = require("child_process"); + +const config = "C:\\project\\FT546WMS-SHANDOW\\.cursorrules-mcp.json"; +const database = "FT546-wms"; + +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(); + 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 output. + } + } + }); + function call(method, params) { + return new Promise((resolve, reject) => { + const id = nextId++; + pending.set(id, (message) => message.error + ? reject(new Error(message.error.message || "MCP error")) + : resolve(message.result)); + child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); + }); + } + return { child, call }; +} + +function parseResult(result) { + const item = (result.content || []).find((entry) => entry.type === "text"); + if (!item) throw new Error("MCP returned no result text"); + try { + return JSON.parse(item.text); + } catch { + throw new Error(item.text.replace(/password\s*[:=]\s*[^\s,}]+/gi, "password=[redacted]").replace(/\r?\n/g, " ")); + } +} + +function quote(value) { + return String(value ?? "").replaceAll("'", "''"); +} + +async function main() { + const client = createClient(); + try { + await client.call("initialize", { + protocolVersion: "2025-06-18", + capabilities: {}, + clientInfo: { name: "ft546-stockout-detail-check", version: "1.0" } + }); + client.child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n"); + + async function read(query) { + return parseResult(await client.call("tools/call", { + name: "sqlserver_read_data", + arguments: { databaseName: database, query, response_format: "json" } + })); + } + + const detail = await read("SELECT stockout_detail_id, warehouse_id, spare4 AS project_no, product_id FROM dbo.out_stockout_detail WHERE stockout_detail_id = 'CKD202608180003-1'"); + const row = detail.rows?.[0]; + const projectNo = row?.project_no == null ? null : String(row.project_no).trim(); + const productNo = row?.product_id == null ? null : String(row.product_id).trim(); + const projectLiteral = projectNo == null || projectNo === "" ? "NULL" : `'${quote(projectNo)}'`; + const productConditionLegacy = productNo == null || productNo === "" ? "1 = 1" : `product_id = '${quote(productNo)}'`; + const productConditionShadow = productNo == null || productNo === "" ? "1 = 1" : `product_no = '${quote(productNo)}'`; + + const legacy = await read(`SELECT stock_id, warehouse_id, cargo_space_id, tray_code, spare4 AS project_no, product_id, quantity, out_quantity, stock_status, product_status FROM dbo.bas_stock WHERE spare4 = ${projectLiteral} AND ${productConditionLegacy}`); + const shadow = await read(`SELECT stock_id, space_id, tray_code, project_no, product_no, quantity, out_quantity FROM alg.v_inventory_item WHERE project_no = ${projectLiteral} AND ${productConditionShadow}`); + const sync = await read("SELECT MAX(synchronized_at) AS latest_shadow_inventory_sync FROM alg.inventory_item_snapshot WHERE warehouse_id = '01'"); + + console.log(JSON.stringify({ + detail: detail.rows || [], + legacy_stock: legacy.rows || [], + shadow_inventory: shadow.rows || [], + latest_sync: sync.rows || [] + }, null, 2)); + } finally { + client.child.kill(); + } +} + +main().catch((error) => { + console.error("READONLY_QUERY_FAILED " + error.message); + process.exitCode = 1; +});