feat(consignment): 客服手动指定现货匹配的界面入口
后端批次已提供判定/差异/强制留痕与双向读取接口,这里补界面,让客服真的能用上。 - 新增共享弹窗 ConsignmentManualMatchModal:订单侧(匹配弹窗「手动指定现货匹配」)与 库存侧(寄售仓库行内「手动匹配订单」)共用同一套交互与落地逻辑,避免两套 UI 漂移 - 交互:搜索 → 选中(精确/非精确/规格冲突三档标签 + 字段级差异说明)→ 选交付方式 (内部现货履约 / 指派供货打手)→ 非精确需勾选确认、冲突需勾选「确认规格不一致」并必填原因 → 落地;并在落地前提示"现货立刻被占用、点错需撤销寄售履约" - 判定规则收敛成纯函数 consignment-manual-match.ts(需不需要确认/填原因、能否提交、 关键词过滤、标签文案)并加 4 例单测 - 服务与类型补齐:fetchWorkOrderConsignmentMatchOptions / fetchInventoryMatchableOrders, fulfill/assign 入参增加 allowSpecMismatch 与 matchNote - 凭证核销失败的提示沿用既有规则(核销失败不能报成功) 验证:前端 typecheck/lint/format/44 例测试/build 全过;两个新接口未登录访问均 401 (路由已挂载、权限位生效)。
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 「手动指定现货」的判定辅助(纯函数,便于单测)。
|
||||
*
|
||||
* 业务规则(用户口径):
|
||||
* - 精确匹配:直接落地;
|
||||
* - 非精确匹配(订单规格没写全 / 高配充低配):必须勾选确认;
|
||||
* - 规格冲突(系统/区服/系列/钻级不符):必须勾选「确认规格不一致」,且**必填原因**,
|
||||
* 原因会写进工单事件与 material 留痕,方便事后核对是谁指定了这件货。
|
||||
*/
|
||||
|
||||
export type ManualMatchType = 'exact' | 'partial' | 'conflict'
|
||||
|
||||
export type ManualMatchRequirement = {
|
||||
needsPartialConfirm: boolean
|
||||
needsMismatchConfirm: boolean
|
||||
needsNote: boolean
|
||||
}
|
||||
|
||||
export function resolveManualMatchRequirement(matchType: ManualMatchType): ManualMatchRequirement {
|
||||
return {
|
||||
needsPartialConfirm: matchType === 'partial',
|
||||
needsMismatchConfirm: matchType === 'conflict',
|
||||
needsNote: matchType === 'conflict',
|
||||
}
|
||||
}
|
||||
|
||||
export function describeManualMatchType(matchType: ManualMatchType): {
|
||||
label: string
|
||||
color: string
|
||||
tone: 'ok' | 'warning' | 'danger'
|
||||
} {
|
||||
if (matchType === 'exact') {
|
||||
return { label: '精确匹配', color: 'green', tone: 'ok' }
|
||||
}
|
||||
if (matchType === 'partial') {
|
||||
return { label: '非精确匹配', color: 'orange', tone: 'warning' }
|
||||
}
|
||||
return { label: '规格冲突', color: 'red', tone: 'danger' }
|
||||
}
|
||||
|
||||
/** 提交按钮是否可用:该确认的都要确认,冲突还要填原因 */
|
||||
export function canSubmitManualMatch(input: {
|
||||
matchType: ManualMatchType
|
||||
confirmedPartial?: boolean
|
||||
confirmedMismatch?: boolean
|
||||
matchNote?: string
|
||||
}): boolean {
|
||||
const requirement = resolveManualMatchRequirement(input.matchType)
|
||||
if (requirement.needsPartialConfirm && input.confirmedPartial !== true) return false
|
||||
if (requirement.needsMismatchConfirm && input.confirmedMismatch !== true) return false
|
||||
if (requirement.needsNote && !String(input.matchNote || '').trim()) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/** 关键词过滤:现货按物品名/寄卖单号/仓库号搜,订单按单号/商品名/买家搜 */
|
||||
export function filterManualMatchOptions<T>(
|
||||
list: T[],
|
||||
keyword: string,
|
||||
pickFields: (item: T) => Array<string | number | null | undefined>,
|
||||
): T[] {
|
||||
const trimmed = String(keyword || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (!trimmed) return list
|
||||
return list.filter((item) =>
|
||||
pickFields(item).some((value) =>
|
||||
String(value ?? '')
|
||||
.toLowerCase()
|
||||
.includes(trimmed),
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user