feat:拼单明细管理加一个撤份额的功能,指定数量
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
cancelWorkOrderShare,
|
||||
cancelWorkOrderSharing,
|
||||
countWorkOrderPendingSharingSubmissions,
|
||||
reduceWorkOrderShareQuantity,
|
||||
updateWorkOrder,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
@@ -189,6 +190,70 @@ export async function cancelAdminWorkOrderShare(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台按份数撤销某个打手的部分份额。
|
||||
*
|
||||
* 减少的份数会随「大厅余量 = 总份数 − Σ有效份额数量」自动回到大厅;押金保持原冻结额,
|
||||
* 母单状态与验收资料都不动(之后仍可对该份额「单独验收」)。
|
||||
*/
|
||||
export async function reduceAdminWorkOrderShareQuantity(
|
||||
workOrderId: number | string,
|
||||
shareId: number | string,
|
||||
payload: JsonObject = {},
|
||||
actorName = '',
|
||||
) {
|
||||
const reduceQuantity = Math.floor(Number(payload.reduceQuantity ?? payload.quantity ?? 0))
|
||||
if (!Number.isFinite(reduceQuantity) || reduceQuantity <= 0) {
|
||||
throw createHttpError('请填写要撤掉的份数', {
|
||||
statusCode: 400,
|
||||
errorCode: 'work_order_sharing_share_reduce_quantity_required',
|
||||
})
|
||||
}
|
||||
|
||||
const result = await reduceWorkOrderShareQuantity({
|
||||
workOrderId: Number(workOrderId),
|
||||
shareId: Number(shareId),
|
||||
reduceQuantity,
|
||||
note: String(payload.note || payload.reason || '').trim(),
|
||||
actorName,
|
||||
now: nowIso(),
|
||||
})
|
||||
if (result.failureReason === 'work_order_not_reducible') {
|
||||
throw createHttpError('当前拼单状态不能撤销份额', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_sharing_share_reduce_status_invalid',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_not_found') {
|
||||
throw createHttpError('未找到该拼单参与记录', {
|
||||
statusCode: 404,
|
||||
errorCode: 'work_order_sharing_share_not_found',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_status_not_reducible' || !result.share || !result.order) {
|
||||
throw createHttpError('只有已参与或已提交验收的拼单份额可以撤销份数', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_sharing_share_reduce_share_status_invalid',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_quantity_invalid') {
|
||||
throw createHttpError('撤掉的份数必须在 1 到「当前份数 − 1」之间;要全部撤销请用「单人撤单」', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_sharing_share_reduce_quantity_invalid',
|
||||
})
|
||||
}
|
||||
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(workOrderId),
|
||||
workerIds: await resolveWorkOrderRealtimeWorkerIds(result.order),
|
||||
hallChanged: true,
|
||||
})
|
||||
return {
|
||||
order: mapWorkOrderAdmin(result.order),
|
||||
share: mapWorkOrderShare(result.share),
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelAdminWorkOrderSharing(
|
||||
workOrderId: number | string,
|
||||
payload: JsonObject = {},
|
||||
|
||||
@@ -54,6 +54,7 @@ export {
|
||||
acceptAdminWorkOrders,
|
||||
cancelAdminWorkOrderShare,
|
||||
cancelAdminWorkOrderSharing,
|
||||
reduceAdminWorkOrderShareQuantity,
|
||||
} from './admin-work-order-acceptance-service.js'
|
||||
export * from './worker-product-match-config-service.js'
|
||||
export * from './worker-announcement-config-service.js'
|
||||
|
||||
@@ -788,6 +788,10 @@ export function mapWorkOrderShare(share: WorkOrderShareRow) {
|
||||
timeoutMinutes: Number(share.timeout_minutes || 0),
|
||||
timeoutPolicy: share.timeout_policy || '',
|
||||
cancelledReason: share.cancelled_reason || '',
|
||||
/** 后台撤单/撤份额时填写的原因(自由文本,选填) */
|
||||
cancelNote: share.cancel_note || '',
|
||||
/** 被撤掉的份数:撤份额累计,单人撤单为整份份数;0 = 未撤过 */
|
||||
cancelQuantity: Math.max(0, Number(share.cancel_quantity || 0)),
|
||||
worker: share.worker_id
|
||||
? {
|
||||
workerId: Number(share.worker_id),
|
||||
|
||||
@@ -3364,6 +3364,64 @@ test('后台单人撤单不改母单状态;份额超时仍交给 recompute 收
|
||||
)
|
||||
})
|
||||
|
||||
test('撤份额与单人撤单都把原因和份数写进份额行,列表才能显示「撤 N 份 · 原因」', () => {
|
||||
const settlementSrc = fs.readFileSync(
|
||||
new URL('../../repositories/worker-platform/work-order-settlement-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const cancelBlock = settlementSrc.slice(
|
||||
settlementSrc.indexOf('export async function cancelWorkOrderShare('),
|
||||
settlementSrc.indexOf('export async function settleOverdueWorkOrderShare('),
|
||||
)
|
||||
assert.ok(cancelBlock.length > 0, 'cancelWorkOrderShare 片段必须存在')
|
||||
assert.ok(cancelBlock.includes('cancel_note = $'), '单人撤单必须把原因写进 cancel_note')
|
||||
assert.ok(
|
||||
cancelBlock.includes("String(input.reason || '').trim()"),
|
||||
'单人撤单写入 cancel_note 的必须是运营填写的自由文本原因',
|
||||
)
|
||||
assert.ok(
|
||||
cancelBlock.includes("cancelled_reason = 'admin'"),
|
||||
'cancelled_reason 仍保留枚举口径,自由文本不得塞进去',
|
||||
)
|
||||
assert.ok(
|
||||
cancelBlock.includes('cancel_quantity = COALESCE(quantity, 0)'),
|
||||
'单人撤单要按整份数量记录撤销份数,列表才能显示「撤 N 份」',
|
||||
)
|
||||
|
||||
const shareSrc = fs.readFileSync(
|
||||
new URL('../../repositories/worker-platform/work-order-share-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
const reduceBlock = shareSrc.slice(
|
||||
shareSrc.indexOf('export async function reduceWorkOrderShareQuantity('),
|
||||
shareSrc.indexOf('export async function submitWorkOrderShareAcceptance('),
|
||||
)
|
||||
assert.ok(reduceBlock.length > 0, 'reduceWorkOrderShareQuantity 片段必须存在')
|
||||
assert.ok(reduceBlock.includes('cancel_note = $'), '撤份额必须把原因写进 cancel_note')
|
||||
assert.ok(
|
||||
reduceBlock.includes('String(share.cancel_note'),
|
||||
'后续撤份额没填原因时必须保留上一次填的说明',
|
||||
)
|
||||
assert.ok(
|
||||
reduceBlock.includes('cancel_quantity = $') &&
|
||||
reduceBlock.includes('Number(share.cancel_quantity'),
|
||||
'撤份额必须按「已有撤销份数 + 本次份数」累计写入 cancel_quantity',
|
||||
)
|
||||
|
||||
const mapperSrc = fs.readFileSync(
|
||||
new URL('./work-order-display-mappers.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(
|
||||
mapperSrc.includes('cancelNote: share.cancel_note'),
|
||||
'拼单明细接口必须下发 cancelNote,否则列表显示不出来',
|
||||
)
|
||||
assert.ok(
|
||||
mapperSrc.includes('cancelQuantity: Math.max(0, Number(share.cancel_quantity'),
|
||||
'拼单明细接口必须下发 cancelQuantity,列表才能拼「撤 N 份」前缀',
|
||||
)
|
||||
})
|
||||
|
||||
test('份额级问题单标记/处置只写份额字段,不得改母单状态或触发整单重算', () => {
|
||||
const src = fs.readFileSync(
|
||||
new URL('../../repositories/worker-platform/work-order-issue-repo.ts', import.meta.url),
|
||||
|
||||
Reference in New Issue
Block a user