feat(consignment): 停用客服手动入库,寄卖货源只走平台提报

背景:手动入库是唯一会造出「没有提报单的现货」的入口,这类货没有寄卖单号,
统一状态机/凭证链/搜索/卖家端单号锚定都串不起来,只能在界面回退显示内部仓库号 INV,
正是"一件货两个号"的残留。业务定调:寄卖货源只走「打手平台提报 → 客服审核入库」。

- 前端寄售仓库的「手动入库」入口整块删除(按钮 + 入库弹窗 + 提交逻辑 + 服务函数)
- 后端同时挡住,防旧页面缓存或直接调接口绕过:
  POST /worker-platform/consignment/inventories 返回 409 consignment_manual_inbound_disabled;
  死代码一并清掉(服务层原实现、仓库层 createConsignmentInventory)
- 提报审核入库路径不受影响,照旧生成内部仓库号 INV 并写 source_submission_id,
  即每件货都必然挂着寄卖单号,INV 只作为内部编号存在、界面不展示

真实库验证:接口被拒(409),且「没有提报单的现货行数」前后均为 0。
This commit is contained in:
yml2213
2026-09-17 11:41:46 +08:00
parent 47e09d72ce
commit 0c10f0e135
6 changed files with 50 additions and 454 deletions
@@ -3,7 +3,6 @@ import {
type ConsignmentInventoryRow,
type ConsignmentSubmissionRow,
approveConsignmentSubmissionWithInbound,
createConsignmentInventory,
getConsignmentInventoryById,
getConsignmentInventoryStats,
getConsignmentSubmissionById,
@@ -279,46 +278,19 @@ export async function getAdminConsignmentInventory(id: number | string) {
return mapConsignmentInventory(item)
}
export async function createAdminConsignmentInventory(payload: JsonObject, actorName = '') {
const itemName = String(payload.itemName || '').trim()
if (!itemName) {
throw createHttpError('物品名称不能为空', { statusCode: 400, errorCode: 'invalid_item_name' })
}
const now = nowIso()
const inventoryNo = generateConsignmentInventoryNo()
const costPriceFen = Math.max(0, Math.round(Number(payload.costPriceFen || 0)))
const row = await createConsignmentInventory({
inventoryNo,
gameName: String(payload.gameName || '和平精英').trim(),
categoryId: payload.categoryId ? Number(payload.categoryId) : null,
itemName,
system: String(payload.system || '').trim(),
serverZone: String(payload.serverZone || '').trim(),
series: String(payload.series || '').trim(),
diamondLevel: Math.max(1, Math.min(7, Number(payload.diamondLevel || 1))),
brillianceCount: Math.max(0, Math.min(7, Number(payload.brillianceCount || 0))),
costPriceFen,
buyerName: String(payload.buyerName || actorName || '').trim(),
inboundBatch: String(payload.inboundBatch || '').trim(),
sellerContact: String(payload.sellerContact || '').trim(),
sellerGameId: String(payload.sellerGameId || '').trim(),
sellerPaymentInfo: String(payload.sellerPaymentInfo || '').trim(),
sellerPhone: String(payload.sellerPhone || '').trim(),
credentialsJson: pickCredentialsJson(payload) ?? {},
screenshotsJson: pickScreenshotsJson(payload) ?? [],
remark: String(payload.remark || '').trim(),
allowMatching: payload.allowMatching !== false,
sourceType: 'manual',
workerId: payload.workerId ? Number(payload.workerId) : null,
workerName: String(payload.workerName || '').trim(),
workerPhone: String(payload.workerPhone || '').trim(),
depositAmountFen: payload.depositAmountFen ? Number(payload.depositAmountFen) : 0,
now,
/**
* 客服手动入库——**业务已停用**。
*
* 寄卖的货源只允许走「打手在平台提报 → 客服审核入库」这一条路:手动入库的货没有提报单,
* 也就没有寄卖单号,全流程(状态机/凭证链/搜索/卖家端)都无法用同一个号锚定。
* 这里保留一个明确的拒绝而不是静默删除,是为了挡住旧页面缓存或直接调接口的写入,
* 避免又冒出没有提报单的货;将来要恢复也只需回退这个守卫。
*/
export async function createAdminConsignmentInventory(_payload: JsonObject, _actorName = '') {
throw createHttpError('客服手动入库已停用:寄卖货源请统一走打手在平台提报后审核入库', {
statusCode: 409,
errorCode: 'consignment_manual_inbound_disabled',
})
return mapConsignmentInventory(row)
}
export async function updateAdminConsignmentInventory(
@@ -144,3 +144,17 @@ test('提报行同样带出赠送凭证(审核界面要看这件货有没有
assert.equal(mapped.giftEvidenceReviewStatus, 'accepted')
assert.equal(mapped.giftWorkOrderNo, 'WO1')
})
test('客服手动入库已停用:接口必须明确拒绝,不能再造出没有提报单的货', async () => {
const { createAdminConsignmentInventory } = await import('./consignment-admin-service.js')
// 手动入库的货没有提报单 → 没有寄卖单号 → 全流程无法用一个号锚定,业务已改为只允许平台提报
await assert.rejects(
() => createAdminConsignmentInventory({ itemName: '不该被创建的货' }, 'admin'),
(error: unknown) => {
const err = error as { errorCode?: string; code?: string; statusCode?: number }
assert.equal(err.errorCode || err.code, 'consignment_manual_inbound_disabled')
assert.equal(err.statusCode, 409)
return true
},
)
})