add:新增同一个工单同一人可以多次拼单:拼单后台撤单,不影响母单状态
This commit is contained in:
@@ -709,7 +709,13 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 后台撤销单个拼单份额,释放该打手押金并把份数重新放回大厅。 */
|
/**
|
||||||
|
* 后台撤销单个拼单份额:退还该打手押金、释放这一份的占用。
|
||||||
|
*
|
||||||
|
* **不改母工单状态**:后台撤单常用于剔除错误参与者/纠错,撤掉一份不应把
|
||||||
|
* 待验收(或进行中)的母单踢回大厅;需要改母单状态时走「拼单撤单」的
|
||||||
|
* 退回大厅/取消整单显式操作。
|
||||||
|
*/
|
||||||
export async function cancelWorkOrderShare(input: {
|
export async function cancelWorkOrderShare(input: {
|
||||||
workOrderId: number
|
workOrderId: number
|
||||||
shareId: number
|
shareId: number
|
||||||
@@ -819,19 +825,16 @@ export async function cancelWorkOrderShare(input: {
|
|||||||
WHERE id = $2`,
|
WHERE id = $2`,
|
||||||
[input.now, input.shareId],
|
[input.now, input.shareId],
|
||||||
)
|
)
|
||||||
// 母单状态由剩余份额集合推导:撤掉这一份后,其余已提交份额仍可正常待验收
|
// 母单状态保持不变:这里只取消这一份份额,不再按剩余份额重推母单状态。
|
||||||
const recomputedOrder = await recomputeWorkOrderSharingStatusWithClient(
|
// (旧实现会 recompute:待验收的母单撤掉一份后 allocated < total → 变回 open,
|
||||||
client,
|
// 等于整单被踢回抢单大厅,与「后台撤单只影响该份额」的预期不符。)
|
||||||
input.workOrderId,
|
|
||||||
input.now,
|
|
||||||
)
|
|
||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: input.actorName || '',
|
actorId: input.actorName || '',
|
||||||
eventType: 'sharing_share_cancelled_by_admin',
|
eventType: 'sharing_share_cancelled_by_admin',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: recomputedOrder?.status || workOrder.status,
|
toStatus: workOrder.status,
|
||||||
payloadJson: JSON.stringify({
|
payloadJson: JSON.stringify({
|
||||||
shareId: input.shareId,
|
shareId: input.shareId,
|
||||||
workerId,
|
workerId,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ router.post(
|
|||||||
req.adminSession?.displayName || req.adminSession?.username || '',
|
req.adminSession?.displayName || req.adminSession?.username || '',
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
successMessage: '拼单参与者已撤单,份数已退回大厅',
|
successMessage: '拼单参与者已撤单,押金已退还,母单状态保持不变',
|
||||||
errorMessage: '撤销拼单参与者失败',
|
errorMessage: '撤销拼单参与者失败',
|
||||||
scope: '[admin/worker-platform/orders/:workOrderId/shares/:shareId/cancel]',
|
scope: '[admin/worker-platform/orders/:workOrderId/shares/:shareId/cancel]',
|
||||||
audit: (req, data) => ({
|
audit: (req, data) => ({
|
||||||
|
|||||||
@@ -3302,33 +3302,39 @@ test('拼单母单已验收或已取消时 recompute 不回头改状态', async
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('份额超时/撤单只改份额本身,母单状态统一交给 recompute', () => {
|
test('后台单人撤单不改母单状态;份额超时仍交给 recompute 收敛', async () => {
|
||||||
const src = fs.readFileSync(
|
const src = fs.readFileSync(
|
||||||
new URL('../../repositories/worker-platform/work-order-settlement-repo.ts', import.meta.url),
|
new URL('../../repositories/worker-platform/work-order-settlement-repo.ts', import.meta.url),
|
||||||
'utf8',
|
'utf8',
|
||||||
)
|
)
|
||||||
const blocks = [
|
const cancelBlock = src.slice(
|
||||||
[
|
src.indexOf('export async function cancelWorkOrderShare('),
|
||||||
'export async function cancelWorkOrderShare(',
|
src.indexOf('export async function settleOverdueWorkOrderShare('),
|
||||||
'export async function settleOverdueWorkOrderShare(',
|
)
|
||||||
],
|
assert.ok(cancelBlock.length > 0, 'cancelWorkOrderShare 片段必须存在')
|
||||||
[
|
assert.ok(
|
||||||
'export async function settleOverdueWorkOrderShare(',
|
!cancelBlock.includes('recomputeWorkOrderSharingStatusWithClient('),
|
||||||
'export async function cancelWorkOrderSharing(',
|
'后台单人撤单不得重算母单状态(撤一份会把待验收的母单踢回大厅)',
|
||||||
],
|
)
|
||||||
]
|
assert.ok(
|
||||||
for (const [start, end] of blocks) {
|
!/UPDATE\s+work_orders\s+SET[\s\S]{0,80}?status\s*=/i.test(cancelBlock),
|
||||||
const block = src.slice(src.indexOf(start!), src.indexOf(end!))
|
'后台单人撤单不得自己写母单 status',
|
||||||
assert.ok(block.length > 0, `${start} 片段必须存在`)
|
)
|
||||||
assert.ok(
|
assert.ok(cancelBlock.includes('toStatus: workOrder.status'), '撤单事件不得记录母单状态迁移')
|
||||||
block.includes('recomputeWorkOrderSharingStatusWithClient('),
|
|
||||||
`${start} 必须用 recompute 收敛母单状态`,
|
const timeoutBlock = src.slice(
|
||||||
)
|
src.indexOf('export async function settleOverdueWorkOrderShare('),
|
||||||
assert.ok(
|
src.indexOf('export async function cancelWorkOrderSharing('),
|
||||||
!/UPDATE\s+work_orders\s+SET[\s\S]{0,80}?status\s*=/i.test(block),
|
)
|
||||||
`${start} 不得自己写母单 status(只能由 recompute 推导)`,
|
assert.ok(timeoutBlock.length > 0, 'settleOverdueWorkOrderShare 片段必须存在')
|
||||||
)
|
assert.ok(
|
||||||
}
|
timeoutBlock.includes('recomputeWorkOrderSharingStatusWithClient('),
|
||||||
|
'份额超时必须用 recompute 收敛母单状态',
|
||||||
|
)
|
||||||
|
assert.ok(
|
||||||
|
!/UPDATE\s+work_orders\s+SET[\s\S]{0,80}?status\s*=/i.test(timeoutBlock),
|
||||||
|
'份额超时不得自己写母单 status(只能由 recompute 推导)',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('拼单每次参与都新增一条份额,并按 requestId 幂等、按参与时间固化截止时间', () => {
|
test('拼单每次参与都新增一条份额,并按 requestId 幂等、按参与时间固化截止时间', () => {
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ export default function WorkOrdersPanel() {
|
|||||||
cancelAdminWorkOrderShare(workOrderId, cancelShare.shareId, {
|
cancelAdminWorkOrderShare(workOrderId, cancelShare.shareId, {
|
||||||
reason: String(values.reason || '').trim(),
|
reason: String(values.reason || '').trim(),
|
||||||
}),
|
}),
|
||||||
'该参与者已撤单,押金和拼单份数已恢复',
|
'该参与者已撤单,押金已退还,母单状态未变更',
|
||||||
)
|
)
|
||||||
if (!succeeded) return
|
if (!succeeded) return
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
@@ -3305,7 +3305,7 @@ export default function WorkOrdersPanel() {
|
|||||||
showIcon
|
showIcon
|
||||||
type="warning"
|
type="warning"
|
||||||
message={`将撤销该打手承接的 ${cancelShare.quantity} 份拼单`}
|
message={`将撤销该打手承接的 ${cancelShare.quantity} 份拼单`}
|
||||||
description="撤单后会退还该份额冻结的押金,空出的份数重新回到接单大厅;已提交的验收资料会保留在后台记录中。"
|
description="撤单只释放这一份份额并退还该份额冻结的押金,母单状态保持不变(待验收/进行中的单不会被踢回大厅);已提交的验收资料会保留在后台记录中。"
|
||||||
style={{ marginBottom: 16 }}
|
style={{ marginBottom: 16 }}
|
||||||
/>
|
/>
|
||||||
<Form.Item label="撤单原因" name="reason">
|
<Form.Item label="撤单原因" name="reason">
|
||||||
|
|||||||
Reference in New Issue
Block a user