Merge remote-tracking branch 'gitea-aliyun/main'
This commit is contained in:
@@ -539,7 +539,7 @@ export async function disposeWorkOrderShareProblem(input: {
|
||||
)
|
||||
}
|
||||
|
||||
// 母单状态保持不变:与「后台单人撤单」同口径,只处置这一份。
|
||||
// 份额级问题单只处置这一份,不重算母单状态:被标记的份额不走「退回大厅」的份额释放口径。
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
|
||||
@@ -305,6 +305,42 @@ export async function updateWorkOrderBasic(
|
||||
return getWorkOrderById(workOrderId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台修正「用户付款金额」:同时更新 083 派生列与 material_json 来源快照。
|
||||
*
|
||||
* 只允许待完善(pending_material)阶段;WHERE 里再守一次状态与接单人,
|
||||
* 避免读完到写入之间状态已推进或被打手接走。
|
||||
*/
|
||||
export async function updateWorkOrderPaymentAmount(input: {
|
||||
workOrderId: number | string
|
||||
amountFen: number
|
||||
materialJson: unknown
|
||||
allowedStatuses: string[]
|
||||
now: string
|
||||
}): Promise<WorkOrderRow | null> {
|
||||
const result = await query<{ id: number }>(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET source_order_amount_fen = $1,
|
||||
material_json = $2::jsonb,
|
||||
updated_at = $3
|
||||
WHERE id = $4
|
||||
AND assigned_worker_id IS NULL
|
||||
AND status = ANY($5::text[])
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
input.amountFen,
|
||||
toJsonString(input.materialJson),
|
||||
input.now,
|
||||
Number(input.workOrderId),
|
||||
input.allowedStatuses,
|
||||
],
|
||||
)
|
||||
if (!result.rows[0]) return null
|
||||
return getWorkOrderById(result.rows[0].id)
|
||||
}
|
||||
|
||||
export async function updateWorkOrderAdminNote(
|
||||
workOrderId: number | string,
|
||||
note: string,
|
||||
|
||||
@@ -99,16 +99,23 @@ const TIMEOUT_COUNT_CACHE_TTL_MS = 5_000
|
||||
const TIMEOUT_COUNT_CACHE_MAX_ENTRIES = 256
|
||||
const timeoutCountCache = new Map<string, TimeoutCountCacheEntry>()
|
||||
|
||||
/** 后台按母工单拼单参与情况计算业务状态,open 只代表大厅生命周期。 */
|
||||
/**
|
||||
* 后台按母工单拼单参与情况计算业务状态,open 只代表大厅生命周期。
|
||||
*
|
||||
* 有份额回到大厅(有效份数 < 总份数)时必须先显示「待抢单」:空位还在,
|
||||
* 母单就还没到按份额状态推进的时候。满额后才按份额状态收敛:
|
||||
* - 仍有 joined 份额 → in_progress(代练中)
|
||||
* - 无 joined 份额 → pending_acceptance(待验收)
|
||||
*/
|
||||
function adminOperationalStatusExpression(alias = 'wo', shareSummaryAlias = '') {
|
||||
if (shareSummaryAlias) {
|
||||
return `CASE
|
||||
WHEN ${alias}.status = 'open'
|
||||
AND ${alias}.sharing_enabled IS TRUE
|
||||
AND COALESCE(${shareSummaryAlias}.active_share_quantity, 0) > 0
|
||||
THEN CASE
|
||||
WHEN COALESCE(${shareSummaryAlias}.active_share_quantity, 0) >= ${alias}.sharing_total_quantity
|
||||
AND COALESCE(${shareSummaryAlias}.pending_share_count, 0) = 0
|
||||
WHEN COALESCE(${shareSummaryAlias}.active_share_quantity, 0) < ${alias}.sharing_total_quantity
|
||||
THEN 'open'
|
||||
WHEN COALESCE(${shareSummaryAlias}.pending_share_count, 0) = 0
|
||||
THEN 'pending_acceptance'
|
||||
ELSE 'in_progress'
|
||||
END
|
||||
@@ -116,13 +123,16 @@ function adminOperationalStatusExpression(alias = 'wo', shareSummaryAlias = '')
|
||||
END`
|
||||
}
|
||||
|
||||
const activeShareQuantitySql = `COALESCE((SELECT SUM(wos.quantity) FROM work_order_shares wos WHERE wos.work_order_id = ${alias}.id AND wos.status != 'cancelled'), 0)`
|
||||
const pendingShareCountSql = `COALESCE((SELECT COUNT(*) FROM work_order_shares wos WHERE wos.work_order_id = ${alias}.id AND wos.status = 'joined'), 0)`
|
||||
|
||||
return `CASE
|
||||
WHEN ${alias}.status = 'open'
|
||||
AND ${alias}.sharing_enabled IS TRUE
|
||||
AND COALESCE((SELECT SUM(wos.quantity) FROM work_order_shares wos WHERE wos.work_order_id = ${alias}.id AND wos.status != 'cancelled'), 0) > 0
|
||||
THEN CASE
|
||||
WHEN COALESCE((SELECT SUM(wos.quantity) FROM work_order_shares wos WHERE wos.work_order_id = ${alias}.id AND wos.status != 'cancelled'), 0) >= ${alias}.sharing_total_quantity
|
||||
AND COALESCE((SELECT COUNT(*) FROM work_order_shares wos WHERE wos.work_order_id = ${alias}.id AND wos.status = 'joined'), 0) = 0
|
||||
WHEN ${activeShareQuantitySql} < ${alias}.sharing_total_quantity
|
||||
THEN 'open'
|
||||
WHEN ${pendingShareCountSql} = 0
|
||||
THEN 'pending_acceptance'
|
||||
ELSE 'in_progress'
|
||||
END
|
||||
|
||||
@@ -821,9 +821,9 @@ export async function acceptWorkOrderShareAndSettle(input: {
|
||||
/**
|
||||
* 后台撤销单个拼单份额:退还该打手押金、释放这一份的占用。
|
||||
*
|
||||
* **不改母工单状态**:后台撤单常用于剔除错误参与者/纠错,撤掉一份不应把
|
||||
* 待验收(或进行中)的母单踢回大厅;需要改母单状态时走「拼单撤单」的
|
||||
* 退回大厅/取消整单显式操作。
|
||||
* 份额取消后空出的份数必须回到接单大厅,所以这里用统一口径重算母单状态:
|
||||
* 存在空位 → open(后台显示待抢单);一份不剩 → open(同样待抢单)。
|
||||
* 重算时保留原 published_at / hall_queued_at,空出的份额沿用原大厅队列位置。
|
||||
*/
|
||||
export async function cancelWorkOrderShare(input: {
|
||||
workOrderId: number
|
||||
@@ -936,22 +936,27 @@ export async function cancelWorkOrderShare(input: {
|
||||
WHERE id = $3`,
|
||||
[input.now, String(input.reason || '').trim(), input.shareId],
|
||||
)
|
||||
// 母单状态保持不变:这里只取消这一份份额,不再按剩余份额重推母单状态。
|
||||
// (旧实现会 recompute:待验收的母单撤掉一份后 allocated < total → 变回 open,
|
||||
// 等于整单被踢回抢单大厅,与「后台撤单只影响该份额」的预期不符。)
|
||||
// 空出的份数回大厅:母单状态必须跟着剩余份额重算,不能停留在待验收。
|
||||
// 保留原队列位置,避免撤掉一份就把整单排到大厅队尾。
|
||||
await recomputeWorkOrderSharingStatusWithClient(client, input.workOrderId, input.now, {
|
||||
preserveHallQueue: true,
|
||||
})
|
||||
const updatedOrder = await getWorkOrderByIdWithClient(client, input.workOrderId)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName || '',
|
||||
eventType: 'sharing_share_cancelled_by_admin',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
toStatus: updatedOrder?.status || workOrder.status,
|
||||
payloadJson: JSON.stringify({
|
||||
shareId: input.shareId,
|
||||
workerId,
|
||||
quantity: Number(share.quantity || 0),
|
||||
releasedDepositAmount,
|
||||
reason: String(input.reason || '').trim(),
|
||||
orderStatusAfter: updatedOrder?.status || workOrder.status,
|
||||
activeShareQuantity: Number(updatedOrder?.active_share_quantity || 0),
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
@@ -961,7 +966,7 @@ export async function cancelWorkOrderShare(input: {
|
||||
[input.shareId],
|
||||
)
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
order: updatedOrder || workOrder,
|
||||
share: updatedShareResult.rows[0] || null,
|
||||
releasedDepositAmount,
|
||||
failureReason: null,
|
||||
@@ -1127,8 +1132,11 @@ export async function settleOverdueWorkOrderShare(input: {
|
||||
[input.now, input.shareId],
|
||||
)
|
||||
|
||||
// 母单状态只由份额集合推导:这一份超时不应把其他已提交份额的母单踢回大厅
|
||||
await recomputeWorkOrderSharingStatusWithClient(client, input.workOrderId, input.now)
|
||||
// 母单状态只由份额集合推导:这一份超时不应把其他已提交份额的母单踢回大厅;
|
||||
// 空出的份数回大厅时保留原队列位置,避免超时一次就重排到队尾。
|
||||
await recomputeWorkOrderSharingStatusWithClient(client, input.workOrderId, input.now, {
|
||||
preserveHallQueue: true,
|
||||
})
|
||||
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
|
||||
@@ -497,13 +497,19 @@ export async function reduceWorkOrderShareQuantity(input: {
|
||||
],
|
||||
)
|
||||
|
||||
// 减少的份数会回到大厅,母单状态跟着剩余份额重算;保留原队列位置。
|
||||
await recomputeWorkOrderSharingStatusWithClient(client, input.workOrderId, input.now, {
|
||||
preserveHallQueue: true,
|
||||
})
|
||||
const updatedOrder = await getWorkOrderByIdWithClient(client, input.workOrderId)
|
||||
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName || '',
|
||||
eventType: 'sharing_share_quantity_reduced',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: workOrder.status,
|
||||
toStatus: updatedOrder?.status || workOrder.status,
|
||||
payloadJson: JSON.stringify({
|
||||
shareId: input.shareId,
|
||||
workerId: Number(share.worker_id || 0),
|
||||
@@ -515,6 +521,8 @@ export async function reduceWorkOrderShareQuantity(input: {
|
||||
// 押金口径:本次保持原冻结额,不退不补
|
||||
shareDeposit: Number(share.share_deposit || 0),
|
||||
note: String(input.note || '').trim(),
|
||||
orderStatusAfter: updatedOrder?.status || workOrder.status,
|
||||
activeShareQuantity: Number(updatedOrder?.active_share_quantity || 0),
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
@@ -524,7 +532,6 @@ export async function reduceWorkOrderShareQuantity(input: {
|
||||
[input.shareId],
|
||||
)
|
||||
// 必须回读聚合口径的母单(active_share_quantity 等),否则前端拿到的拼单进度会归零
|
||||
const updatedOrder = await getWorkOrderByIdWithClient(client, input.workOrderId)
|
||||
return {
|
||||
order: updatedOrder || workOrder,
|
||||
share: updatedShare.rows[0] || null,
|
||||
@@ -619,17 +626,22 @@ export async function submitWorkOrderShareAcceptance(input: {
|
||||
/**
|
||||
* 按份额集合推导母单拼单状态(唯一口径,禁止各调用点各自 UPDATE work_orders.status)。
|
||||
*
|
||||
* allocated >= total && joined == 0 && submitted > 0 → pending_acceptance
|
||||
* allocated < total 或 joined > 0 → open
|
||||
* allocated == 0(全部取消) → open(重新可参与)
|
||||
* allocated >= total && joined == 0 && submitted > 0 → pending_acceptance(待验收)
|
||||
* allocated < total → open(后台显示待抢单:空位还在)
|
||||
* allocated >= total && joined > 0 → open(后台显示代练中:还有份额在做)
|
||||
* allocated == 0(全部取消) → open(待抢单,重新可参与)
|
||||
* 母单 accepted / cancelled → 不动
|
||||
*
|
||||
* 返回更新后的母单行(供调用方回给前端)。
|
||||
*
|
||||
* preserveHallQueue:份额退回大厅时保留原 published_at / hall_queued_at,
|
||||
* 避免因为母单状态从待验收回退到 open 而被重新排到大厅队尾。
|
||||
*/
|
||||
export async function recomputeWorkOrderSharingStatusWithClient(
|
||||
client: PoolClient,
|
||||
workOrderId: number,
|
||||
now: string,
|
||||
options: { preserveHallQueue?: boolean } = {},
|
||||
): Promise<WorkOrderRow | null> {
|
||||
const orderResult = await client.query<WorkOrderRow>(
|
||||
`SELECT * FROM work_orders WHERE id = $1 FOR UPDATE`,
|
||||
@@ -676,13 +688,17 @@ export async function recomputeWorkOrderSharingStatusWithClient(
|
||||
return workOrder
|
||||
}
|
||||
|
||||
const hallQueueAssignments = options.preserveHallQueue
|
||||
? ''
|
||||
: `published_at = CASE WHEN $1 = 'open' THEN $2 ELSE published_at END,
|
||||
hall_queued_at = CASE WHEN $1 = 'open' THEN $2 ELSE hall_queued_at END,`
|
||||
|
||||
const updated = await client.query<WorkOrderRow>(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
status = $1,
|
||||
published_at = CASE WHEN $1 = 'open' THEN $2 ELSE published_at END,
|
||||
hall_queued_at = CASE WHEN $1 = 'open' THEN $2 ELSE hall_queued_at END,
|
||||
${hallQueueAssignments}
|
||||
submitted_at = CASE
|
||||
WHEN $1 = 'pending_acceptance' THEN COALESCE(submitted_at, $2)
|
||||
ELSE submitted_at
|
||||
|
||||
Reference in New Issue
Block a user