本次将此前被列入退役名单的运营分析相关模块恢复为可用,并新增「模块化库存监控」
一级菜单及其两个子页面。
主要变更:
1. 恢复启用:商品监控、激励看板、营销活动、推广模块、商品分析
- retiredFeatures 移出对应退役名单与清表语句,改为正常保留
- 补齐后端路由、前端页面与 API 封装、侧边栏配置、桌面客户端能力项
- 同步更新 retiredFeatures / retiredRoutes / retiredNavigation / desktopSessionService 测试
2. 新增「模块化库存监控」一级菜单
- 配件库存监控:仅取「简尚-崔桥工厂半成品包装项目仓」SKU 的实时库存、
当日入库/出库、近 7 日日均发货量、库存可用天数与预警(库存可用天数 ≤ 7 预警)
- MKH-BOM资料:按商品编码展示配件明细、数量、仓位、库存,支持 A4 纵向打印
3. 新增产品资料系统数据库(PostgreSQL)连接模块:读取系统库 database_configs 中
name = product_information_database 的配置,凭据只从配置读取,不落代码
4. 新增系统设置页配置项:商品标签配置、部门商品标签配置、部门SKU负责人配置
5. 旧 /api/dashboard 接口迁移至 /api/productAnalysis,仪表盘预览页改用新接口,
相应移除 dashboard 路由模块及其测试
6. 修复 retiredRoutes 测试在 Windows 下挂起超时的问题:该测试用
parent.filename.endsWith('/api/planningUpload.js') 判断是否注入 mock,
而 Windows 的 filename 为反斜杠路径,导致判断恒为 false、mock 全部失效、
真实模块去连中央系统库而挂起(Linux CI 正常)。改为平台无关的路径匹配。
7. .gitignore 忽略本地验证产物(outputs/、.git-status.txt)
验证:
- 前端:lint / test(16 项)/ build(含 planning entry 校验)全部通过
- 后端:语法检查 149 个文件通过;测试 38/39 通过
- 已知未通过项:resilientSecretStore.test.js 断言密钥备份文件权限为 0o600,
Windows 下 chmod 无法表达该权限(实际为 0o666),属既有的平台相关失败,
与本次改动无关,本次未修改该文件
- 说明:本机 frontend/ 下存在未纳入版本库的旧依赖备份目录 node_modules_old_*,
会污染 ESLint 扫描(十余万条第三方报错),故 lint 步骤以
--ignore-pattern node_modules_old_*/** 排除后执行,其余步骤与仓库 check 一致
未提交内容:本地验证截图 outputs/ 与 .git-status.txt 已被 .gitignore 排除。
68 lines
3.1 KiB
JavaScript
68 lines
3.1 KiB
JavaScript
const RETIRED_PAGE_KEYS = [
|
|
'planning_upload', 'market_insight', 'agent', 'ai_skill_tags',
|
|
'store_checkup', 'review_analysis', 'customer_service_checkup',
|
|
'pricing_calculator', 'desktop_client_download', 'ai_buyer_show', 'ai_seeding_video',
|
|
];
|
|
const RETIRED_ENTRY_KEYS = new Set([
|
|
'market-insight', 'agent', 'ai-skill-tags', 'platform-switch',
|
|
'store-management', 'store-checkup', 'review-analysis', 'customer-service-checkup',
|
|
'operation-tools', 'pricing-calculator', 'desktop-client-download', 'ai-buyer-show', 'ai-seeding-video',
|
|
'desktop-client-releases',
|
|
]);
|
|
const RETIRED_CONFIG_KEYS = [
|
|
'openclaw_chat_config', 'openclaw_chat_skill_tags',
|
|
'selection_similar_products_config', 'alibaba_1688_image_search_config',
|
|
'cs_quality_ai_config', 'desktop_client_releases',
|
|
'ai_buyer_show_ark_api_key', 'ai_buyer_show_ark_base_url',
|
|
'ai_buyer_show_vision_model', 'ai_buyer_show_review_model',
|
|
];
|
|
const RETIRED_DATABASE_CONFIG_NAMES = ['ai_buyer_show_database'];
|
|
|
|
function isRetiredSystemConfigKey(key) {
|
|
return RETIRED_CONFIG_KEYS.includes(String(key || '').trim());
|
|
}
|
|
|
|
function isRetiredDatabaseConfigName(name) {
|
|
return RETIRED_DATABASE_CONFIG_NAMES.includes(String(name || '').trim());
|
|
}
|
|
|
|
function pruneRetiredSidebarConfig(value) {
|
|
try {
|
|
const config = typeof value === 'string' ? JSON.parse(value) : value;
|
|
if (!config || !Array.isArray(config.entries)) return value;
|
|
const prune = (entries) => entries
|
|
.filter((entry) => entry && !RETIRED_ENTRY_KEYS.has(entry.key) && !RETIRED_ENTRY_KEYS.has(entry.routeKey))
|
|
.map((entry) => ({ ...entry, ...(Array.isArray(entry.children) ? { children: prune(entry.children) } : {}) }));
|
|
const result = { ...config, entries: prune(config.entries) };
|
|
return typeof value === 'string' ? JSON.stringify(result) : result;
|
|
} catch {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
async function cleanupRetiredFeatureSettings(db) {
|
|
for (const table of ['permissions', 'department_page_permissions', 'user_page_permissions']) {
|
|
await db.query(`DELETE FROM ${table} WHERE page_key IN (${RETIRED_PAGE_KEYS.map(() => '?').join(',')})`, RETIRED_PAGE_KEYS);
|
|
}
|
|
await db.query(`DELETE FROM system_config WHERE config_key IN (${RETIRED_CONFIG_KEYS.map(() => '?').join(',')})`, RETIRED_CONFIG_KEYS);
|
|
await db.query(`DELETE FROM database_configs WHERE name IN (${RETIRED_DATABASE_CONFIG_NAMES.map(() => '?').join(',')})`, RETIRED_DATABASE_CONFIG_NAMES);
|
|
await db.query('DROP TABLE IF EXISTS pricing_calculator_history');
|
|
await db.query('DROP TABLE IF EXISTS pricing_calculator_user_prefs');
|
|
const rows = await db.query("SELECT config_value FROM system_config WHERE config_key = ?", ['sidebar_navigation']);
|
|
if (rows.length) {
|
|
const current = rows[0].config_value;
|
|
const next = pruneRetiredSidebarConfig(current);
|
|
if (current !== next) await db.query('UPDATE system_config SET config_value = ? WHERE config_key = ?', [next, 'sidebar_navigation']);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
RETIRED_PAGE_KEYS,
|
|
RETIRED_CONFIG_KEYS,
|
|
RETIRED_DATABASE_CONFIG_NAMES,
|
|
isRetiredSystemConfigKey,
|
|
isRetiredDatabaseConfigName,
|
|
pruneRetiredSidebarConfig,
|
|
cleanupRetiredFeatureSettings,
|
|
};
|