feat:拼单订单新增一个取消部分的按钮功能,点击之后,打手进行中和待接单的都会取消订单
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import {
|
||||
resolveSharingPartCancelPlan,
|
||||
resolveSharingPartCancelRefund,
|
||||
} from './work-order-sharing-part-cancel.js'
|
||||
|
||||
test('取消部分:保留已验收份数,取消进行中 + 待接单份数', () => {
|
||||
const plan = resolveSharingPartCancelPlan({
|
||||
workOrderStatus: 'open',
|
||||
totalQuantity: 10,
|
||||
acceptedQuantity: 8,
|
||||
})
|
||||
assert.deepEqual(plan, { nextTotalQuantity: 8, cancelledQuantity: 2, failureReason: null })
|
||||
})
|
||||
|
||||
test('取消部分:一份都没验收时整单取消(总量归零)', () => {
|
||||
const plan = resolveSharingPartCancelPlan({
|
||||
workOrderStatus: 'pending_acceptance',
|
||||
totalQuantity: 10,
|
||||
acceptedQuantity: 0,
|
||||
})
|
||||
assert.deepEqual(plan, { nextTotalQuantity: 0, cancelledQuantity: 10, failureReason: null })
|
||||
})
|
||||
|
||||
test('取消部分:全部份额已验收时没有可取消份数', () => {
|
||||
const plan = resolveSharingPartCancelPlan({
|
||||
workOrderStatus: 'accepted',
|
||||
totalQuantity: 8,
|
||||
acceptedQuantity: 8,
|
||||
})
|
||||
// accepted 不在允许状态里,优先报状态非法
|
||||
assert.equal(plan.failureReason, 'status_invalid')
|
||||
|
||||
const openPlan = resolveSharingPartCancelPlan({
|
||||
workOrderStatus: 'open',
|
||||
totalQuantity: 8,
|
||||
acceptedQuantity: 8,
|
||||
})
|
||||
assert.deepEqual(openPlan, {
|
||||
nextTotalQuantity: 8,
|
||||
cancelledQuantity: 0,
|
||||
failureReason: 'nothing_to_cancel',
|
||||
})
|
||||
})
|
||||
|
||||
test('取消部分:问题单允许操作,已取消/已验收单不允许', () => {
|
||||
assert.equal(
|
||||
resolveSharingPartCancelPlan({
|
||||
workOrderStatus: 'problem',
|
||||
totalQuantity: 3,
|
||||
acceptedQuantity: 1,
|
||||
}).failureReason,
|
||||
null,
|
||||
)
|
||||
for (const status of ['cancelled', 'accepted', 'unassigned']) {
|
||||
assert.equal(
|
||||
resolveSharingPartCancelPlan({
|
||||
workOrderStatus: status,
|
||||
totalQuantity: 3,
|
||||
acceptedQuantity: 0,
|
||||
}).failureReason,
|
||||
'status_invalid',
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('取消部分:已验收份数异常时按总量截断', () => {
|
||||
const plan = resolveSharingPartCancelPlan({
|
||||
workOrderStatus: 'open',
|
||||
totalQuantity: 5,
|
||||
acceptedQuantity: 9,
|
||||
})
|
||||
assert.deepEqual(plan, {
|
||||
nextTotalQuantity: 5,
|
||||
cancelledQuantity: 0,
|
||||
failureReason: 'nothing_to_cancel',
|
||||
})
|
||||
})
|
||||
|
||||
test('取消部分退款:按实付金额比例计算并四舍五入到分', () => {
|
||||
assert.equal(
|
||||
resolveSharingPartCancelRefund({
|
||||
paidAmountFen: 20000,
|
||||
totalQuantity: 10,
|
||||
cancelledQuantity: 9,
|
||||
}),
|
||||
18000,
|
||||
)
|
||||
assert.equal(
|
||||
resolveSharingPartCancelRefund({ paidAmountFen: 999, totalQuantity: 3, cancelledQuantity: 1 }),
|
||||
333,
|
||||
)
|
||||
assert.equal(
|
||||
resolveSharingPartCancelRefund({ paidAmountFen: 0, totalQuantity: 10, cancelledQuantity: 10 }),
|
||||
0,
|
||||
)
|
||||
// 取消份数超过总量时按总量截断
|
||||
assert.equal(
|
||||
resolveSharingPartCancelRefund({ paidAmountFen: 5000, totalQuantity: 5, cancelledQuantity: 8 }),
|
||||
5000,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 拼单「取消部分」口径(已确认):
|
||||
* - 只保留已验收份额;打手进行中(joined / submitted)与待接单的剩余份数一起取消;
|
||||
* - 取消的份额退还押金,已验收份额的报酬 / 押金 / 验收资料一律不动;
|
||||
* - 取消后剩下的份额全部是已验收,所以母单直接收尾为已验收;一份都没验收则整单取消。
|
||||
*/
|
||||
|
||||
/** 允许执行取消部分的母单状态:问题单也允许,问题标记随份额一起取消。 */
|
||||
export const SHARING_PART_CANCEL_STATUSES = [
|
||||
'open',
|
||||
'in_progress',
|
||||
'pending_acceptance',
|
||||
'problem',
|
||||
] as const
|
||||
|
||||
export function resolveSharingPartCancelPlan(input: {
|
||||
workOrderStatus: string
|
||||
/** 母单当前总份数 */
|
||||
totalQuantity: number
|
||||
/** 已验收份额的份数合计 */
|
||||
acceptedQuantity: number
|
||||
}): {
|
||||
/** 取消后保留的总份数(= 已验收份数) */
|
||||
nextTotalQuantity: number
|
||||
/** 本次要取消的份数(进行中份额 + 待接单剩余) */
|
||||
cancelledQuantity: number
|
||||
failureReason: 'status_invalid' | 'nothing_to_cancel' | null
|
||||
} {
|
||||
const status = String(input.workOrderStatus || '').trim()
|
||||
if (!SHARING_PART_CANCEL_STATUSES.includes(status as never)) {
|
||||
return { nextTotalQuantity: 0, cancelledQuantity: 0, failureReason: 'status_invalid' }
|
||||
}
|
||||
const total = Math.max(0, Math.floor(Number(input.totalQuantity) || 0))
|
||||
const accepted = Math.min(total, Math.max(0, Math.floor(Number(input.acceptedQuantity) || 0)))
|
||||
const cancelled = total - accepted
|
||||
if (cancelled <= 0) {
|
||||
return { nextTotalQuantity: accepted, cancelledQuantity: 0, failureReason: 'nothing_to_cancel' }
|
||||
}
|
||||
return { nextTotalQuantity: accepted, cancelledQuantity: cancelled, failureReason: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* 建议退款金额:按订单实付金额 × 取消份数 / 原总量,四舍五入到分。
|
||||
* 只是默认值,后台可以在弹窗里手工改。
|
||||
*/
|
||||
export function resolveSharingPartCancelRefund(input: {
|
||||
paidAmountFen: number
|
||||
totalQuantity: number
|
||||
cancelledQuantity: number
|
||||
}): number {
|
||||
const paid = Math.max(0, Math.round(Number(input.paidAmountFen) || 0))
|
||||
const total = Math.max(0, Math.floor(Number(input.totalQuantity) || 0))
|
||||
const cancelled = Math.max(0, Math.floor(Number(input.cancelledQuantity) || 0))
|
||||
if (paid <= 0 || total <= 0 || cancelled <= 0) return 0
|
||||
return Math.round((paid * Math.min(cancelled, total)) / total)
|
||||
}
|
||||
Reference in New Issue
Block a user