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:
@@ -321,88 +321,6 @@ export async function getConsignmentInventoryById(
|
||||
return res.rows[0] || null
|
||||
}
|
||||
|
||||
export async function createConsignmentInventory(input: {
|
||||
inventoryNo: string
|
||||
gameName?: string
|
||||
categoryId?: number | null
|
||||
itemName: string
|
||||
system?: string
|
||||
serverZone?: string
|
||||
series?: string
|
||||
diamondLevel?: number
|
||||
brillianceCount?: number
|
||||
costPriceFen?: number
|
||||
buyerName?: string
|
||||
inboundBatch?: string
|
||||
sellerContact?: string
|
||||
sellerGameId?: string
|
||||
sellerPaymentInfo?: string
|
||||
sellerPhone?: string
|
||||
credentialsJson?: Record<string, unknown>
|
||||
screenshotsJson?: unknown[]
|
||||
remark?: string
|
||||
allowMatching?: boolean
|
||||
sourceType?: 'manual' | 'worker_portal' | 'recycle'
|
||||
sourceSubmissionId?: number | null
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
now: string
|
||||
}): Promise<ConsignmentInventoryRow> {
|
||||
const sql = `
|
||||
INSERT INTO consignment_inventories (
|
||||
inventory_no, game_name, category_id, item_name, system, server_zone,
|
||||
series, diamond_level, brilliance_count, cost_price_fen, buyer_name,
|
||||
inbound_batch, seller_contact, seller_game_id, seller_payment_info,
|
||||
seller_phone, credentials_json, screenshots_json, remark,
|
||||
allow_matching, status, allocation_status, source_type,
|
||||
source_submission_id, worker_id, worker_name, worker_phone, deposit_amount_fen,
|
||||
inbound_at, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15,
|
||||
$16, $17::jsonb, $18::jsonb, $19,
|
||||
$20, 'in_stock', '待安排', $21,
|
||||
$22, $23, $24, $25, $26,
|
||||
$27, $27, $27
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
const values = [
|
||||
input.inventoryNo,
|
||||
input.gameName || '和平精英',
|
||||
input.categoryId || null,
|
||||
input.itemName,
|
||||
input.system || '',
|
||||
input.serverZone || '',
|
||||
input.series || '',
|
||||
input.diamondLevel || 1,
|
||||
input.brillianceCount || 0,
|
||||
input.costPriceFen || 0,
|
||||
input.buyerName || '',
|
||||
input.inboundBatch || '',
|
||||
input.sellerContact || '',
|
||||
input.sellerGameId || '',
|
||||
input.sellerPaymentInfo || '',
|
||||
input.sellerPhone || '',
|
||||
toJsonString(input.credentialsJson || {}),
|
||||
toJsonString(input.screenshotsJson || []),
|
||||
input.remark || '',
|
||||
input.allowMatching !== false,
|
||||
input.sourceType || 'manual',
|
||||
input.sourceSubmissionId || null,
|
||||
input.workerId || null,
|
||||
input.workerName || '',
|
||||
input.workerPhone || '',
|
||||
input.depositAmountFen || 0,
|
||||
input.now,
|
||||
]
|
||||
const res = await query<ConsignmentInventoryRow>(sql, values)
|
||||
return res.rows[0]!
|
||||
}
|
||||
|
||||
export async function updateConsignmentInventory(
|
||||
id: number | string,
|
||||
input: {
|
||||
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user