From be52bdd0b50f3a85721b7413693063c300b0b476 Mon Sep 17 00:00:00 2001 From: yanhui <2353496031@qq.com> Date: Thu, 17 Sep 2026 18:14:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=85=8D=E4=BB=B6=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E7=9B=91=E6=8E=A7/MKH-BOM):=20=E4=B8=A4=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8C=E5=90=8C=E6=AD=A5=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E3=80=8D=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 需求:配件库存监控、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 恢复后,打开两个页面确认按钮渲染与点击效果。 - 后端改动需重启服务后才生效。 --- backend/src/api/mkhBom.js | 11 +++++++ backend/src/api/modularStockMonitor.js | 10 +++++++ backend/src/services/mkhBomService.js | 29 +++++++++++++++++++ .../services/modularStockMonitorService.js | 12 ++++++++ frontend/src/api/mkhBom.js | 8 +++++ frontend/src/api/modularStockMonitor.js | 7 +++++ frontend/src/pages/MkhBomData/index.jsx | 23 ++++++++++++++- .../src/pages/ModularStockMonitor/index.jsx | 23 +++++++++++++-- 8 files changed, 120 insertions(+), 3 deletions(-) diff --git a/backend/src/api/mkhBom.js b/backend/src/api/mkhBom.js index afe8956..9614490 100644 --- a/backend/src/api/mkhBom.js +++ b/backend/src/api/mkhBom.js @@ -5,6 +5,7 @@ const { getMkhBomList, getMkhBomDetail, listMkhBomExportRows, + clearMkhBomCache, } = require('../services/mkhBomService'); const { getMkhReplenishAlertAdminConfig, @@ -101,4 +102,14 @@ router.put('/notification-center', async (req, res) => { } }); +// 手动同步:清空本页服务端缓存(BOM / 补货索引 / 加工单 / 待发货 / 配件库存 / 仓位), +// 前端「同步」按钮调用后再拉一次列表即可直连副本库取最新快照 +router.post('/sync', async (_req, res) => { + try { + res.json({ success: true, data: clearMkhBomCache() }); + } catch (error) { + sendError(res, error, '同步 MKH-BOM 数据失败'); + } +}); + module.exports = router; diff --git a/backend/src/api/modularStockMonitor.js b/backend/src/api/modularStockMonitor.js index ce34dd1..6139f06 100644 --- a/backend/src/api/modularStockMonitor.js +++ b/backend/src/api/modularStockMonitor.js @@ -4,6 +4,7 @@ const express = require('express'); const { getModularStockList, getModularStockFilters, + syncModularStock, } = require('../services/modularStockMonitorService'); const router = express.Router(); @@ -36,4 +37,13 @@ router.get('/filters', async (_req, res) => { } }); +// 手动同步:重新取最新监控日,前端「同步」按钮调用后再拉一次列表即拿到最新快照 +router.post('/sync', async (_req, res) => { + try { + res.json({ success: true, data: await syncModularStock() }); + } catch (error) { + sendError(res, error, '同步配件库存监控数据失败'); + } +}); + module.exports = router; diff --git a/backend/src/services/mkhBomService.js b/backend/src/services/mkhBomService.js index ed4a607..d74260b 100644 --- a/backend/src/services/mkhBomService.js +++ b/backend/src/services/mkhBomService.js @@ -1947,8 +1947,37 @@ async function listMkhBomExportRows(query = {}) { }; } +// 手动同步:清空本页全部服务端缓存(BOM / 补货索引 / 加工单 / 待发货 / 配件库存 / 配件仓位)。 +// 页面「同步」按钮调用——清掉后下一次查询直连副本库,拿到的是上游最新快照, +// 而不是 2~15 分钟前的缓存结果。 +// 注意:副本库本身由聚水潭侧同步,应用层无法触发上游拉取,本函数只保证「不读旧缓存」。 +function clearMkhBomCache() { + businessBomCache = null; + officialBomIndexCache = null; + productBomCache = null; + mkhProductCache = null; + replenishIndexCache = null; + workOrderIndexCache = null; + pendingOrderIndexCache = null; + + const cleared = [ + ['officialBomDetailCache', officialBomDetailCache.size], + ['componentLocationCache', componentLocationCache.size], + ['componentStockCache', componentStockCache.size], + ]; + officialBomDetailCache.clear(); + componentLocationCache.clear(); + componentStockCache.clear(); + + return { + syncedAt: new Date().toISOString(), + cleared: cleared.map(([name, entries]) => ({ name, entries })), + }; +} + module.exports = { getMkhBomList, + clearMkhBomCache, getMkhBomDetail, listReplenishAlertRows, listMkhBomExportRows, diff --git a/backend/src/services/modularStockMonitorService.js b/backend/src/services/modularStockMonitorService.js index 5b3d0da..b436771 100644 --- a/backend/src/services/modularStockMonitorService.js +++ b/backend/src/services/modularStockMonitorService.js @@ -445,6 +445,17 @@ async function getModularStockFilters() { }; } +// 手动同步:本页无进程内缓存(每次请求直连副本库),同步动作 = 重新解析监控日并返回, +// 前端据此刷新列表到最新快照。 +// 注意:副本库由聚水潭侧同步,应用层无法触发上游拉取,这里只保证取到最新监控日。 +async function syncModularStock() { + const monitorDate = await resolveMonitorDate(); + return { + syncedAt: new Date().toISOString(), + monitorDate, + }; +} + module.exports = { WAREHOUSE_NAME, WAREHOUSE_WMS_CO_ID, @@ -452,4 +463,5 @@ module.exports = { DEFAULT_SAFETY_DAYS, getModularStockList, getModularStockFilters, + syncModularStock, }; diff --git a/frontend/src/api/mkhBom.js b/frontend/src/api/mkhBom.js index fdd3341..4d61416 100644 --- a/frontend/src/api/mkhBom.js +++ b/frontend/src/api/mkhBom.js @@ -45,3 +45,11 @@ export const saveMkhNotificationCenter = (payload = {}) => request.put( '/api/mkh-bom/notification-center', payload ); + +// 手动同步:清空服务端缓存(BOM / 补货索引 / 加工单 / 待发货 / 配件库存 / 仓位), +// 随后重新拉列表即直连副本库取最新快照,可随时点击 +export const syncMkhBom = () => request.post( + '/api/mkh-bom/sync', + null, + { timeout: LIST_TIMEOUT_MS } +); diff --git a/frontend/src/api/modularStockMonitor.js b/frontend/src/api/modularStockMonitor.js index 19257e7..ec44585 100644 --- a/frontend/src/api/modularStockMonitor.js +++ b/frontend/src/api/modularStockMonitor.js @@ -14,3 +14,10 @@ export const getModularStockFilters = () => request.get( '/api/modular-stock-monitor/filters', { timeout: META_TIMEOUT_MS } ); + +// 手动同步:后端重新取最新监控日,前端随后再拉一次列表即拿到最新快照 +export const syncModularStock = () => request.post( + '/api/modular-stock-monitor/sync', + null, + { timeout: META_TIMEOUT_MS } +); diff --git a/frontend/src/pages/MkhBomData/index.jsx b/frontend/src/pages/MkhBomData/index.jsx index 0fd2e30..33968da 100644 --- a/frontend/src/pages/MkhBomData/index.jsx +++ b/frontend/src/pages/MkhBomData/index.jsx @@ -4,7 +4,7 @@ import { Table, Tooltip, Typography, message, } from 'antd'; import { - BellOutlined, DownloadOutlined, PrinterOutlined, ReloadOutlined, SearchOutlined, + BellOutlined, DownloadOutlined, PrinterOutlined, ReloadOutlined, SearchOutlined, SyncOutlined, } from '@ant-design/icons'; import { SycmPage } from '../../components/Common'; import { @@ -15,6 +15,7 @@ import { saveMkhReplenishAlertConfig, saveMkhNotificationCenter, checkMkhReplenishAlert, + syncMkhBom, } from '../../api/mkhBom'; import { exportTableFile } from '../../utils/tableExport'; import './style.css'; @@ -233,6 +234,7 @@ const MkhBomData = () => { // 导出(当前筛选条件下的全部数据) const [exporting, setExporting] = useState(false); + const [syncing, setSyncing] = useState(false); // 补货预警推送设置 const [alertOpen, setAlertOpen] = useState(false); @@ -718,6 +720,22 @@ const MkhBomData = () => { ); }; + // 手动同步:清掉服务端缓存(BOM / 补货索引 / 加工单 / 待发货 / 配件库存 / 仓位), + // 再拉一次列表即可直连副本库取最新快照;可随时点击,不必等凌晨的定时刷新 + const handleSync = async () => { + setSyncing(true); + try { + await syncMkhBom(); + setDetailCache({}); + await load(); + message.success('已同步最新数据'); + } catch (syncError) { + message.error(syncError?.response?.data?.error || syncError?.message || '同步失败'); + } finally { + setSyncing(false); + } + }; + return (
@@ -772,6 +790,9 @@ const MkhBomData = () => { /> + + + { const [data, setData] = useState({ rows: [], total: 0, page: 1, pageSize: 50, summary: {}, monitorDate: null }); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); + const [syncing, setSyncing] = useState(false); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(50); const [sorter, setSorter] = useState({ field: 'realtimeStock', order: 'DESC' }); @@ -282,6 +283,21 @@ const AccessoryStockPanel = () => { } }; + // 手动同步:后端重新取最新监控日(清服务端缓存)后再拉一次列表, + // 拿到的是副本库当前最新快照;可随时点击,不受 24 点定时任务限制 + const handleSync = async () => { + setSyncing(true); + try { + await syncModularStock(); + await load(); + message.success('已同步最新数据'); + } catch (syncError) { + message.error(syncError?.response?.data?.error || syncError?.message || '同步失败'); + } finally { + setSyncing(false); + } + }; + const summary = data.summary || {}; return ( @@ -327,6 +343,9 @@ const AccessoryStockPanel = () => { + + +