fix(consignment): 售后立案回落到供货卖家,修内部现货单立不了案
补货款时排查"只认接单人"的地方,发现同类第三处:售后立案的责任人取 work_orders.assigned_worker_id,取不到就报 worker_missing(界面 409「请指定已验收的责任 打手或拼单份额」)。内部现货履约的寄卖单没有接单人,于是这类单子连售后都立不了案; 而货款修好后钱已结算给供货卖家,担责人本来就该是他。 - 抽出共享查询 getConsignmentSupplierByWorkOrderWithClient() (consignment-supplier-repo.ts):工单 material_json.consignment.inventoryId 或库存 bound_work_order_id → 库存 → 提报单,货款结算与售后立案共用同一份口径 - 售后在找不到接单人时回落到供货卖家,并在验收快照里标记 consignmentSupplier 与提报单号留痕 - 加测试钉住:售后必须先认寄卖关系再判定 worker_missing;取卖家的 SQL 只有一处实现 真实库验证(内部现货履约,接单人为空):反查供货卖家 workerId=3 正确; 售后立案成功且责任人 worker_id=3。临时数据残留 0。
This commit is contained in:
@@ -3,6 +3,7 @@ import type { PoolClient } from 'pg'
|
||||
import { query, withTransaction } from '../../db/client.js'
|
||||
import { resolveAfterSalesRecoveryAllocation } from './after-sales-recovery.js'
|
||||
import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './shared.js'
|
||||
import { getConsignmentSupplierByWorkOrderWithClient } from './consignment-supplier-repo.js'
|
||||
import type {
|
||||
WorkerAfterSalesCaseEventRow,
|
||||
WorkerAfterSalesCaseRow,
|
||||
@@ -89,6 +90,19 @@ export async function createAfterSalesCase(input: {
|
||||
shareQuantity: Number(share.quantity || 0),
|
||||
}
|
||||
}
|
||||
if (workerId <= 0) {
|
||||
// 内部现货履约的寄卖单没有接单打手,但货是供货卖家交付的、货款也结算给了他,
|
||||
// 售后要担责的人就是他;不认寄卖关系的话这类单子连案都立不了。
|
||||
const supplier = await getConsignmentSupplierByWorkOrderWithClient(client, input.workOrderId)
|
||||
workerId = Number(supplier?.workerId || 0)
|
||||
if (workerId > 0) {
|
||||
acceptanceSnapshot = {
|
||||
...acceptanceSnapshot,
|
||||
consignmentSupplier: true,
|
||||
consignmentSubmissionNo: supplier?.submissionNo || '',
|
||||
}
|
||||
}
|
||||
}
|
||||
if (workerId <= 0) {
|
||||
return { case: null, failureReason: 'worker_missing' }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ensureWorkerWalletWithClient, getWorkerWalletWithClient, toJsonString }
|
||||
import { offsetWorkerAfterSalesDebtsWithClient } from './after-sales-repo.js'
|
||||
import { enqueueDepositUnfreezeWithClient } from './work-order-deposit-repo.js'
|
||||
import { maybeUpgradeWorkerLevelWithClient } from './worker-ranking-repo.js'
|
||||
import { getConsignmentSupplierByWorkOrderWithClient } from './consignment-supplier-repo.js'
|
||||
|
||||
/**
|
||||
* 查询寄卖提报押金尚未释放的金额(分)
|
||||
@@ -448,53 +449,11 @@ export async function settleConsignmentSupplierPayoutByWorkOrderWithClient(
|
||||
now: string
|
||||
},
|
||||
): Promise<{ workerId: number; amountFen: number } | null> {
|
||||
const res = await client.query<{
|
||||
inventory_id: number
|
||||
inventory_worker_id: number | null
|
||||
cost_price_fen: number
|
||||
submission_worker_id: number | null
|
||||
asking_price_fen: number
|
||||
submission_no: string | null
|
||||
}>(
|
||||
`
|
||||
SELECT
|
||||
inv.id AS inventory_id,
|
||||
inv.worker_id AS inventory_worker_id,
|
||||
inv.cost_price_fen,
|
||||
cs.worker_id AS submission_worker_id,
|
||||
cs.asking_price_fen,
|
||||
cs.submission_no
|
||||
FROM work_orders wo
|
||||
JOIN consignment_inventories inv
|
||||
ON inv.id = CASE
|
||||
WHEN (wo.material_json->'consignment'->>'inventoryId') ~ '^[0-9]+$'
|
||||
THEN (wo.material_json->'consignment'->>'inventoryId')::int
|
||||
END
|
||||
LEFT JOIN consignment_submissions cs ON cs.id = inv.source_submission_id
|
||||
WHERE wo.id = $1
|
||||
UNION ALL
|
||||
SELECT
|
||||
inv.id,
|
||||
inv.worker_id,
|
||||
inv.cost_price_fen,
|
||||
cs.worker_id,
|
||||
cs.asking_price_fen,
|
||||
cs.submission_no
|
||||
FROM consignment_inventories inv
|
||||
LEFT JOIN consignment_submissions cs ON cs.id = inv.source_submission_id
|
||||
WHERE inv.bound_work_order_id = $1
|
||||
LIMIT 1
|
||||
`,
|
||||
[input.workOrderId],
|
||||
)
|
||||
const target = res.rows[0]
|
||||
if (!target) return null
|
||||
const supplier = await getConsignmentSupplierByWorkOrderWithClient(client, input.workOrderId)
|
||||
if (!supplier) return null
|
||||
|
||||
const supplierId =
|
||||
Number(target.inventory_worker_id || 0) || Number(target.submission_worker_id || 0)
|
||||
const amountFen =
|
||||
Math.round(Number(target.cost_price_fen || 0)) ||
|
||||
Math.round(Number(target.asking_price_fen || 0))
|
||||
const supplierId = supplier.workerId
|
||||
const amountFen = supplier.costPriceFen || supplier.askingPriceFen
|
||||
if (supplierId <= 0 || amountFen <= 0) return null
|
||||
// 接单人就是供货卖家时,货款已经随工单报酬结算过,不重复付款
|
||||
if (Number(input.assignedWorkerId || 0) === supplierId) return null
|
||||
@@ -556,8 +515,8 @@ export async function settleConsignmentSupplierPayoutByWorkOrderWithClient(
|
||||
: `寄卖货款结算 #${input.workOrderId}`,
|
||||
toJsonString({
|
||||
workOrderId: Number(input.workOrderId),
|
||||
inventoryId: Number(target.inventory_id),
|
||||
submissionNo: target.submission_no || '',
|
||||
inventoryId: supplier.inventoryId,
|
||||
submissionNo: supplier.submissionNo,
|
||||
costPriceFen: amountFen,
|
||||
...(shouldDelayUnfreeze ? { unfreezeDays: input.unfreezeDays } : {}),
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { PoolClient } from 'pg'
|
||||
|
||||
/**
|
||||
* 从工单反查寄卖的**供货卖家**(交付这件货、该收货款的人)。
|
||||
*
|
||||
* 为什么需要它:寄卖单在"内部现货履约"时是客服/内部仓代交付,工单上**没有接单打手**
|
||||
* (`assigned_worker_id` 为空),于是所有"只认接单人"的逻辑都会落空——
|
||||
* 货款没人收、售后找不到责任人都属于这一类。供货卖家只能从寄卖关系里查:
|
||||
* 工单(`material_json.consignment.inventoryId` 或库存的 `bound_work_order_id`)
|
||||
* → 库存 → 提报单。
|
||||
*/
|
||||
export type ConsignmentSupplier = {
|
||||
workerId: number
|
||||
inventoryId: number
|
||||
costPriceFen: number
|
||||
askingPriceFen: number
|
||||
submissionNo: string
|
||||
}
|
||||
|
||||
export async function getConsignmentSupplierByWorkOrderWithClient(
|
||||
client: PoolClient,
|
||||
workOrderId: number | string,
|
||||
): Promise<ConsignmentSupplier | null> {
|
||||
const res = await client.query<{
|
||||
inventory_id: number
|
||||
inventory_worker_id: number | null
|
||||
cost_price_fen: string | number | null
|
||||
submission_worker_id: number | null
|
||||
asking_price_fen: string | number | null
|
||||
submission_no: string | null
|
||||
}>(
|
||||
`
|
||||
SELECT
|
||||
inv.id AS inventory_id,
|
||||
inv.worker_id AS inventory_worker_id,
|
||||
inv.cost_price_fen,
|
||||
cs.worker_id AS submission_worker_id,
|
||||
cs.asking_price_fen,
|
||||
cs.submission_no
|
||||
FROM work_orders wo
|
||||
JOIN consignment_inventories inv
|
||||
ON inv.id = CASE
|
||||
WHEN (wo.material_json->'consignment'->>'inventoryId') ~ '^[0-9]+$'
|
||||
THEN (wo.material_json->'consignment'->>'inventoryId')::int
|
||||
END
|
||||
LEFT JOIN consignment_submissions cs ON cs.id = inv.source_submission_id
|
||||
WHERE wo.id = $1
|
||||
UNION ALL
|
||||
SELECT
|
||||
inv.id,
|
||||
inv.worker_id,
|
||||
inv.cost_price_fen,
|
||||
cs.worker_id,
|
||||
cs.asking_price_fen,
|
||||
cs.submission_no
|
||||
FROM consignment_inventories inv
|
||||
LEFT JOIN consignment_submissions cs ON cs.id = inv.source_submission_id
|
||||
WHERE inv.bound_work_order_id = $1
|
||||
LIMIT 1
|
||||
`,
|
||||
[workOrderId],
|
||||
)
|
||||
const row = res.rows[0]
|
||||
if (!row) return null
|
||||
|
||||
return {
|
||||
workerId: Number(row.inventory_worker_id || 0) || Number(row.submission_worker_id || 0),
|
||||
inventoryId: Number(row.inventory_id),
|
||||
costPriceFen: Math.round(Number(row.cost_price_fen || 0)),
|
||||
askingPriceFen: Math.round(Number(row.asking_price_fen || 0)),
|
||||
submissionNo: String(row.submission_no || ''),
|
||||
}
|
||||
}
|
||||
@@ -43,3 +43,31 @@ test('寄卖货款:接单人就是供货卖家时不重复付款,金额取
|
||||
assert.ok(source.includes('offsetWorkerAfterSalesDebtsWithClient('))
|
||||
assert.ok(source.includes('enqueueDepositUnfreezeWithClient('))
|
||||
})
|
||||
|
||||
test('售后立案:内部现货履约(无接单打手)时要落到供货卖家,否则连案都立不了', async () => {
|
||||
const afterSalesSource = await readFile(
|
||||
new URL('../../repositories/worker-platform/after-sales-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(afterSalesSource.includes('getConsignmentSupplierByWorkOrderWithClient('))
|
||||
assert.ok(afterSalesSource.includes('worker_missing'))
|
||||
assert.ok(
|
||||
afterSalesSource.indexOf('getConsignmentSupplierByWorkOrderWithClient(') <
|
||||
afterSalesSource.lastIndexOf("'worker_missing'"),
|
||||
'必须先尝试认寄卖关系,再判定找不到责任人',
|
||||
)
|
||||
})
|
||||
|
||||
test('"从工单找供货卖家"只有一处实现,货款与售后共用', async () => {
|
||||
const supplierRepo = await readFile(
|
||||
new URL('../../repositories/worker-platform/consignment-supplier-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
supplierRepo.includes('export async function getConsignmentSupplierByWorkOrderWithClient'),
|
||||
)
|
||||
const depositSource = await readFile(DEPOSIT_REPO, 'utf8')
|
||||
assert.ok(depositSource.includes('getConsignmentSupplierByWorkOrderWithClient('))
|
||||
// 取卖家那段 SQL(含 UNION ALL 兜底)只应存在于共享查询里,别处不要再抄一份
|
||||
assert.equal(depositSource.includes('UNION ALL'), false)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user