cangku/backend/src/api/mkhBom.js
yanhui be52bdd0b5 feat(配件库存监控/MKH-BOM): 两页面新增「同步最新数据」按钮
需求:配件库存监控、MKH-BOM 资料两个页面各加一个同步按钮,任何时候点击都能
立即拉取最新数据,不必等凌晨 00:00 的定时刷新。

后端:
- mkhBomService 新增 clearMkhBomCache():清空本页全部服务端缓存
  (businessBom / officialBomIndex / officialBomDetail / productBom /
  mkhProduct / replenishIndex / workOrderIndex / pendingOrderIndex /
  componentStock / componentLocation),返回 syncedAt + 各缓存清除条目数。
- modularStockMonitorService 新增 syncModularStock():本页无进程内缓存
  (每次请求直连副本库),同步动作 = 重新解析并返回最新监控日,前端据此重拉列表。
- 新增接口 POST /api/mkh-bom/sync、POST /api/modular-stock-monitor/sync。

前端:
- api/mkhBom.js 新增 syncMkhBom;api/modularStockMonitor.js 新增 syncModularStock。
- 两个页面工具栏各加「同步最新数据」主按钮(SyncOutlined,带 loading 态),
  点击 → 调后端 sync → 成功后重新拉列表(MKH-BOM 页同时清空展开明细缓存
  detailCache,避免看到旧的配件明细)→ 提示「已同步最新数据」;
  失败时展示后端返回的错误信息。

口径说明(重要):
- 两页数据来自副本库 JSReportReplica,由上游聚水潭同步;应用层无法触发上游拉取。
  本按钮的作用是「清服务端缓存 + 直连副本库重取最新」,保证看到的不是
  2~15 分钟前的缓存结果,而不是重新跑一遍上游同步。
- 后端已内置的 dailyRefresh(cron 0 0 * * *,Asia/Shanghai)刷的是
  summary_daily_* / summary_monthly_* 汇总表,不覆盖这两页数据源,
  与本按钮既不重复也不冲突,可并存。

验证:
- 后端 4 个改动文件 node --check 全部通过。
- 前端 4 个改动文件(含 2 个 jsx)用 @babel/parser 解析全部通过。
- 实测:syncModularStock 返回 monitorDate=2026-09-16;
  clearMkhBomCache 返回 syncedAt 与各缓存清除条目数。
- 单测 mkhBomService.test.js 40/40 通过,无回归。
- 前端 lint/build 未运行:frontend/node_modules 仍是之前被中断 npm ci 破坏的
  坏目录(frontend/node_modules_broken_0917/,未纳入提交)。
  合并前需在本机 npm ci 恢复后,打开两个页面确认按钮渲染与点击效果。
- 后端改动需重启服务后才生效。
2026-09-17 18:14:57 +08:00

116 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const express = require('express');
const {
getMkhBomList,
getMkhBomDetail,
listMkhBomExportRows,
clearMkhBomCache,
} = require('../services/mkhBomService');
const {
getMkhReplenishAlertAdminConfig,
resetMkhReplenishAlertState,
saveMkhReplenishAlertConfig,
} = require('../services/mkhReplenishAlertSettings');
const { checkMkhReplenishAlerts } = require('../jobs/mkhReplenishAlertJob');
const {
saveYydNotificationCenterConfig,
} = require('../services/yydNotificationCenterSettings');
const router = express.Router();
function sendError(res, error, fallback) {
const status = Number(error?.statusCode) || 500;
console.error('[MkhBom]', {
message: error?.message || fallback,
code: error?.code || null,
status,
});
res.status(status).json({ success: false, error: error.message || fallback });
}
// MKH-BOM 资料列表(模块化组合商品 + 配件汇总)
router.get('/list', async (req, res) => {
try {
res.json({ success: true, data: await getMkhBomList(req.query || {}) });
} catch (error) {
sendError(res, error, '加载 MKH-BOM 资料列表失败');
}
});
// 单个 MKH 组合商品的 BOM 配件明细(打印用)
router.get('/detail', async (req, res) => {
try {
const skuId = req.query?.skuId || req.query?.comboSkuId;
res.json({ success: true, data: await getMkhBomDetail(skuId) });
} catch (error) {
sendError(res, error, '加载 MKH-BOM 明细失败');
}
});
// 导出:当前筛选条件下的全部数据(不分页)
// mode=products(默认)→ 商品清单;mode=components → 配件明细摊平
router.get('/export', async (req, res) => {
try {
res.json({ success: true, data: await listMkhBomExportRows(req.query || {}) });
} catch (error) {
sendError(res, error, '导出 MKH-BOM 资料失败');
}
});
// 补货预警推送设置(接收人 / 开关 / 推送范围)
router.get('/alert-config', async (req, res) => {
try {
res.json({ success: true, data: await getMkhReplenishAlertAdminConfig() });
} catch (error) {
sendError(res, error, '加载补货预警推送设置失败');
}
});
// 保存补货预警推送设置
router.put('/alert-config', async (req, res) => {
try {
const data = await saveMkhReplenishAlertConfig(req.body || {});
res.json({ success: true, data });
} catch (error) {
sendError(res, error, '保存补货预警推送设置失败');
}
});
// 立即检查并推送(dryRun: 只预览不发送;force: 忽略「只推新增」推全量;reset: 先清空已推送记录)
router.post('/alert-check', async (req, res) => {
try {
const body = req.body || {};
if (body.reset === true) await resetMkhReplenishAlertState();
const result = await checkMkhReplenishAlerts({
dryRun: body.dryRun === true,
force: body.force === true,
});
res.json({ success: true, data: result });
} catch (error) {
sendError(res, error, '执行补货预警推送失败');
}
});
// 保存统一通知中心凭据(补货预警的推送通道)
router.put('/notification-center', async (req, res) => {
try {
await saveYydNotificationCenterConfig(req.body || {});
res.json({ success: true, data: await getMkhReplenishAlertAdminConfig() });
} catch (error) {
sendError(res, error, '保存统一通知中心配置失败');
}
});
// 手动同步:清空本页服务端缓存(BOM / 补货索引 / 加工单 / 待发货 / 配件库存 / 仓位),
// 前端「同步」按钮调用后再拉一次列表即可直连副本库取最新快照
router.post('/sync', async (_req, res) => {
try {
res.json({ success: true, data: clearMkhBomCache() });
} catch (error) {
sendError(res, error, '同步 MKH-BOM 数据失败');
}
});
module.exports = router;