按 2026-09-25 业务口径,第一张子看板由「横林二仓客退数据监控」改为 「崔桥工厂仓客退数据监控」,取数仓位同步改为聚水潭「崔桥工厂客退临时上架仓」。 - 仓库配置:简尚-崔桥工厂仓 wms_co_id=15038426,客退位 customize_qty_1 - key 由 henglin2 改为 cuiqiao,同步改路由 / 侧边栏 / 路由映射 / 权限条目 - 旧路径 returns-inventory/henlin2* 保留为重定向,避免历史链接 404 - 核销看板(returnMonitorService)仍指向横林二仓,前端入口已下线,本次未动 副本取证:该仓客退位当前只有 2 编码 / 2 件(退货单有量:ERP 3115 条 + 奇门 1812 条),属「客退上架库存未同步进副本」,页面需靠导入功能补数据。
133 lines
4.6 KiB
JavaScript
133 lines
4.6 KiB
JavaScript
'use strict';
|
||
|
||
const express = require('express');
|
||
const multer = require('multer');
|
||
const {
|
||
buildExportColumns,
|
||
getReturnStockWarningExportRows,
|
||
getReturnStockWarningList,
|
||
resolveWarehouse,
|
||
resolveWarehouseKey,
|
||
} = require('../services/returnStockWarningService');
|
||
const {
|
||
clearStockWarningImport,
|
||
importStockWarning,
|
||
} = require('../services/returnStockWarningImport');
|
||
const { sendWorkbookDownload } = require('../services/exportWorkbook');
|
||
|
||
const router = express.Router();
|
||
const upload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: { fileSize: 20 * 1024 * 1024 },
|
||
});
|
||
|
||
const resolveScope = (value) => (['imported', 'replica'].includes(String(value)) ? String(value) : 'all');
|
||
|
||
/**
|
||
* GET /api/return-stock-warning/list —— 退货库存预警列表(客退数据监控,按仓库出数)
|
||
* warehouse: 仓库标识 cuiqiao / subei / suining / shandong / henan(缺省 = 崔桥工厂仓)
|
||
*/
|
||
router.get('/list', async (req, res, next) => {
|
||
try {
|
||
const payload = await getReturnStockWarningList({
|
||
warehouse: req.query.warehouse,
|
||
page: req.query.page,
|
||
pageSize: req.query.pageSize,
|
||
keyword: req.query.keyword,
|
||
supplier: req.query.supplier,
|
||
sortBy: req.query.sortBy,
|
||
sortOrder: req.query.sortOrder,
|
||
qtyBasis: req.query.qtyBasis,
|
||
createdAtBasis: req.query.createdAtBasis,
|
||
scope: resolveScope(req.query.scope),
|
||
focusDays: req.query.focusDays,
|
||
alertDays: req.query.alertDays,
|
||
warningLevel: req.query.warningLevel,
|
||
priceFocusMin: req.query.priceFocusMin,
|
||
priceHighMin: req.query.priceHighMin,
|
||
priceLevel: req.query.priceLevel,
|
||
returnLimitDays: req.query.returnLimitDays,
|
||
refresh: req.query.refresh,
|
||
});
|
||
res.json({ success: true, data: payload });
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
/** GET /api/return-stock-warning/export —— 服务端全量导出(xlsx / csv) */
|
||
router.get('/export', async (req, res, next) => {
|
||
try {
|
||
const format = String(req.query.format || 'xlsx').toLowerCase() === 'csv' ? 'csv' : 'xlsx';
|
||
const keyword = String(req.query.keyword || '').trim();
|
||
const warehouse = resolveWarehouse(req.query.warehouse);
|
||
const rows = await getReturnStockWarningExportRows({
|
||
warehouse: req.query.warehouse,
|
||
keyword,
|
||
supplier: req.query.supplier,
|
||
qtyBasis: req.query.qtyBasis,
|
||
createdAtBasis: req.query.createdAtBasis,
|
||
scope: resolveScope(req.query.scope),
|
||
focusDays: req.query.focusDays,
|
||
alertDays: req.query.alertDays,
|
||
warningLevel: req.query.warningLevel,
|
||
priceFocusMin: req.query.priceFocusMin,
|
||
priceHighMin: req.query.priceHighMin,
|
||
priceLevel: req.query.priceLevel,
|
||
returnLimitDays: req.query.returnLimitDays,
|
||
refresh: req.query.refresh,
|
||
});
|
||
|
||
const exportColumns = buildExportColumns(warehouse);
|
||
sendWorkbookDownload(res, {
|
||
fileName: `${warehouse.shortName}客退数据监控_${keyword || '全部'}`,
|
||
format,
|
||
sheets: [{
|
||
name: `${warehouse.shortName}客退数据监控`,
|
||
header: exportColumns.map((column) => column.title),
|
||
rows: rows.map((row) => exportColumns.map((column) => row[column.key])),
|
||
}],
|
||
});
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
/** POST /api/return-stock-warning/import —— 导入聚水潭「箱及仓位库存」导出文件 */
|
||
router.post('/import', upload.single('file'), async (req, res, next) => {
|
||
try {
|
||
if (!req.file || !req.file.buffer) {
|
||
res.status(400).json({ success: false, message: '请选择要导入的文件(xlsx / xls / csv)' });
|
||
return;
|
||
}
|
||
// multer 按 latin1 解析 multipart 文件名,中文会变成乱码,这里转回 UTF-8
|
||
const rawName = req.file.originalname || '';
|
||
const fileName = /[\u00c0-\u00ff]/.test(rawName)
|
||
? Buffer.from(rawName, 'latin1').toString('utf8')
|
||
: rawName;
|
||
|
||
const result = await importStockWarning({
|
||
buffer: req.file.buffer,
|
||
fileName,
|
||
importedBy: req.user?.username || req.user?.name || null,
|
||
// 导入落在哪个仓库下:优先取表单字段,其次 query,最后回落默认仓
|
||
warehouseKey: resolveWarehouseKey(req.body?.warehouse || req.query.warehouse),
|
||
});
|
||
res.json({ success: true, data: result });
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
/** DELETE /api/return-stock-warning/import —— 清除当前仓库的导入,回到系统推算口径 */
|
||
router.delete('/import', async (req, res, next) => {
|
||
try {
|
||
const result = await clearStockWarningImport(resolveWarehouseKey(req.query.warehouse));
|
||
res.json({ success: true, data: result });
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|