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 恢复后,打开两个页面确认按钮渲染与点击效果。 - 后端改动需重启服务后才生效。
This commit is contained in:
parent
bc8c46d5be
commit
be52bdd0b5
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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 }
|
||||
);
|
||||
|
||||
@ -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 }
|
||||
);
|
||||
|
||||
@ -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 (
|
||||
<SycmPage className="mkh-page">
|
||||
<header className="mkh-header">
|
||||
@ -772,6 +790,9 @@ const MkhBomData = () => {
|
||||
/>
|
||||
<Button onClick={resetFilters}>重置</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={load} loading={loading}>刷新数据</Button>
|
||||
<Tooltip title="立即重新拉取副本库最新数据(清服务端缓存后重查),可随时点击,不必等凌晨的定时刷新">
|
||||
<Button type="primary" icon={<SyncOutlined />} onClick={handleSync} loading={syncing}>同步最新数据</Button>
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
disabled={exporting}
|
||||
|
||||
@ -3,9 +3,9 @@ import {
|
||||
Alert, Button, Card, Col, Dropdown, Image, Input, Row, Select,
|
||||
Space, Spin, Statistic, Switch, Table, Tabs, Tag, Tooltip, Typography, message,
|
||||
} from 'antd';
|
||||
import { DownloadOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { DownloadOutlined, ReloadOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { SycmPage } from '../../components/Common';
|
||||
import { getModularStockList, getModularStockFilters } from '../../api/modularStockMonitor';
|
||||
import { getModularStockList, getModularStockFilters, syncModularStock } from '../../api/modularStockMonitor';
|
||||
import { exportTableFile } from '../../utils/tableExport';
|
||||
import './style.css';
|
||||
|
||||
@ -38,6 +38,7 @@ const AccessoryStockPanel = () => {
|
||||
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 = () => {
|
||||
</Space>
|
||||
<Button onClick={resetFilters}>重置</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={load} loading={loading}>刷新数据</Button>
|
||||
<Tooltip title="立即重新拉取副本库最新数据(清服务端缓存后重查),可随时点击,不必等凌晨的定时刷新">
|
||||
<Button type="primary" icon={<SyncOutlined />} onClick={handleSync} loading={syncing}>同步最新数据</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user