import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import test from 'node:test' function readSource(relativePath: string): string { return readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), 'utf8') } test('寄卖配置:每个规格字段同时提供自动/手动两套匹配方式', () => { const source = readSource('../src/pages/admin/panels/ConsignmentConfigPanel.tsx') assert.ok(source.includes('自动匹配方式'), '必须保留自动匹配方式下拉') assert.ok(source.includes('手动匹配方式'), '必须新增手动匹配方式下拉') assert.ok(source.includes("manualMatchMode: 'exact'"), '新增字段与模板生成的字段默认手动精确相等') assert.ok( source.includes("value={spec.manualMatchMode ?? 'exact'}"), '历史配置没有手动口径时按精确相等展示', ) const typeSource = readSource('../src/types/consignment.ts') assert.ok(typeSource.includes('manualMatchMode?'), '前端类型必须包含 manualMatchMode') }) test('寄卖配置:两套匹配方式与通配值/进物品名/支持筛选必须在同一行', () => { const source = readSource('../src/pages/admin/panels/ConsignmentConfigPanel.tsx') const css = readSource('../src/styles/admin.css') assert.ok( source.includes('order-service-field-extra is-match-modes'), '字段行必须使用 5 列栅格类', ) assert.ok(css.includes('.order-service-field-extra.is-match-modes')) assert.ok( css.includes('minmax(90px, 0.72fr)'), '手动匹配方式必须比自动匹配方式窄,避免把通配值挤没', ) assert.ok( css.includes('.order-service-field-extra.is-match-modes,'), '窄屏媒体查询必须包含该 5 列布局,保证折行', ) }) test('后端:规格字段归一化补 manualMatchMode,历史配置缺省精确相等', () => { const source = readSource( '../../backend/src/services/worker-platform/consignment-spec-service.ts', ) assert.ok(source.includes('manualMatchMode?: ConsignmentSpecMatchMode')) assert.ok(source.includes('source.manualMatchMode ?? source.manual_match_mode')) assert.ok( source.includes( "return specs.map((spec) => ({ ...spec, matchMode: spec.manualMatchMode ?? 'exact' }))", ), '手动口径必须把 matchMode 替换成 manualMatchMode(缺省 exact)', ) }) test('后端:手动候选、手动复检、待匹配看板都使用手动口径', () => { const engine = readSource( '../../backend/src/services/worker-platform/consignment-match-engine-service.ts', ) const pending = readSource( '../../backend/src/services/worker-platform/consignment-pending-match-service.ts', ) assert.equal( (engine.match(/matchScope: 'manual'/g) || []).length, 2, '手动候选与手动复检必须传 manual 口径', ) assert.ok(engine.includes("matchScope: 'auto'"), '自动匹配候选必须显式使用 auto 口径') assert.ok(pending.includes("matchScope: 'manual'"), '一键查询待匹配订单必须按手动口径') }) test('内部库存匹配弹窗:默认展示全部候选,按打分降序,并显示寄卖单号', () => { const source = readSource('../src/components/admin/ConsignmentWorkOrderMatchModal.tsx') assert.ok(source.includes('默认按匹配打分从高到低展示全部候选'), '默认展示口径必须在界面上说明') assert.ok( !source.includes("if (!searchable) return (item.matchType || 'exact') === 'exact'"), '不能再默认只过滤精确匹配', ) assert.ok( source.includes('computeConsignmentMatchScore(right) - computeConsignmentMatchScore(left)'), '列表必须按匹配打分降序排列', ) assert.ok( source.includes('buildCandidateHaystack(item).includes(normalizedKeyword)'), '关键词必须覆盖全部匹配状态', ) assert.ok(source.includes("title: '寄卖单号'"), '必须新增寄卖单号列') assert.ok( source.includes('item.consignmentNo || item.inventoryNo'), '寄卖单号优先展示提报单号,无提报号回退库存号', ) assert.ok(source.includes("item.consignmentNo || ''"), '搜索必须能命中寄卖单号') assert.ok(!source.includes('显示不匹配项'), '「显示不匹配项」勾选框必须删除') assert.ok(!source.includes('includeConflicts'), '不再保留 includeConflicts 状态') assert.ok(source.includes('initialInventoryIds'), '必须支持外部预选库存') assert.ok(source.includes('if (presetInventoryKey)'), '预选库存必须优先于默认自动勾选') }) test('手动匹配弹窗:状态不允许时仍能搜索,但禁止提交', () => { const source = readSource('../src/components/admin/ConsignmentWorkOrderMatchModal.tsx') assert.ok(source.includes('const matchable = data?.matchable !== false')) assert.ok(source.includes('当前仍可搜索和核对库存'), '只读状态必须给出明确提示') assert.ok(source.includes('disabled={!canSubmit}')) assert.ok( /matchable &&\s+selectedIds\.length > 0/.test(source), 'canSubmit 必须受 matchable 约束', ) const searchIndex = source.indexOf('搜索寄卖单号 / 库存号 / 物品 / 规格 / 打手') const optionsBranchIndex = source.indexOf('visibleOptions.length > 0 ? (') assert.ok(searchIndex > 0) assert.ok( searchIndex < optionsBranchIndex, '搜索框必须常驻在候选分支之外,空列表 / 只读状态也能使用', ) const typeSource = readSource('../src/types/consignment.ts') assert.ok(typeSource.includes('matchable: boolean'), '候选响应类型必须带 matchable') assert.ok(typeSource.includes('blockedReason: string')) })