From 5879568ad09ccb0f98ed67316680745951a9f9b8 Mon Sep 17 00:00:00 2001 From: zhaoxing <2558691827@qq.com> Date: Sun, 1 Nov 2026 16:22:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(worker):=20=E7=94=A8=E6=88=B7=E4=BB=98?= =?UTF-8?q?=E6=AC=BE=E9=87=91=E9=A2=9D=E4=BB=85=E5=BE=85=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=8F=AF=E4=BF=AE=E6=94=B9=E5=B9=B6=E8=A1=A5=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../work-order-management-repo.ts | 13 ++------- .../admin-work-order-management-service.ts | 23 +++++++-------- .../work-order-payment-amount.test.ts | 23 +++++++++++++++ .../work-order-payment-amount.ts | 11 +++++++ .../admin/panels/WorkOrderTableCells.tsx | 8 +++-- .../admin/panels/work-order-panel-utils.tsx | 6 +++- .../pages/admin/panels/work-order-payment.ts | 5 ++++ .../tests/work-order-payment-loss.test.ts | 29 +++++++++++++++++++ 8 files changed, 91 insertions(+), 27 deletions(-) diff --git a/apps/backend/src/repositories/worker-platform/work-order-management-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-management-repo.ts index 3a6a0083..43ff9261 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-management-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-management-repo.ts @@ -308,8 +308,8 @@ export async function updateWorkOrderBasic( /** * 后台修正「用户付款金额」:同时更新 083 派生列与 material_json 来源快照。 * - * WHERE 里再守一次状态与接单人,避免读完到写入之间被打手接走; - * 待抢单(open)还要求没有有效拼单份额,与 capabilities.canEdit 口径一致。 + * 只允许待完善(pending_material)阶段;WHERE 里再守一次状态与接单人, + * 避免读完到写入之间状态已推进或被打手接走。 */ export async function updateWorkOrderPaymentAmount(input: { workOrderId: number | string @@ -327,15 +327,6 @@ export async function updateWorkOrderPaymentAmount(input: { WHERE id = $4 AND assigned_worker_id IS NULL AND status = ANY($5::text[]) - AND ( - status <> 'open' - OR NOT EXISTS ( - SELECT 1 - FROM work_order_shares wos - WHERE wos.work_order_id = work_orders.id - AND wos.status <> 'cancelled' - ) - ) RETURNING id `, [ diff --git a/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts index aeb64468..12fb049d 100644 --- a/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts +++ b/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts @@ -37,6 +37,7 @@ import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-s import { getWorkerHallConfig } from './worker-hall-config-service.js' import { USER_PAYMENT_AMOUNT_REASON_MAX_LENGTH, + canModifyUserPaymentAmountStatus, mergeUserPaymentAmountIntoMaterial, normalizeUserPaymentAmountFen, resolveDisplayedUserPaymentAmountFen, @@ -130,9 +131,9 @@ export async function updateAdminWorkOrder( /** * 后台修正「用户付款金额」。 * - * 只允许和普通编辑相同的状态(待完善/未分配/待抢单且未指派), - * 金额按元提交、按分落库;修改会写入 work_order_events + 后台审计。 - * 同步 material_json.source 快照,避免列表展示与 083 派生列分叉。 + * 只允许「待完善(pending_material)」阶段修改:金额进入未分配/待抢单后已参与 + * 发单与大厅展示,再改会破坏对账口径。金额按元提交、按分落库;修改会写入 + * work_order_events + 后台审计,并同步 material_json.source 快照。 */ export async function updateAdminWorkOrderPaymentAmount( workOrderId: number | string, @@ -140,16 +141,16 @@ export async function updateAdminWorkOrderPaymentAmount( options: { actorName?: string } = {}, ) { const workOrder = await getRequiredWorkOrder(workOrderId) - if (!mapWorkOrderAdmin(workOrder).capabilities.canEdit) { - throw createHttpError('只有待完善、未分配或待抢单的工单可以编辑', { + if (!canModifyUserPaymentAmountStatus(workOrder.status)) { + throw createHttpError('只有待完善的工单可以修改用户付款金额', { statusCode: 409, - errorCode: 'work_order_edit_status_invalid', + errorCode: 'work_order_payment_amount_status_invalid', }) } if (workOrder.assigned_worker_id) { - throw createHttpError('工单已被打手接走,不能编辑', { + throw createHttpError('工单已被打手接走,不能修改用户付款金额', { statusCode: 409, - errorCode: 'work_order_edit_assigned_invalid', + errorCode: 'work_order_payment_amount_assigned_invalid', }) } @@ -184,11 +185,7 @@ export async function updateAdminWorkOrderPaymentAmount( workOrderId: workOrder.id, amountFen, materialJson: mergeUserPaymentAmountIntoMaterial(workOrder.material_json, amountFen), - allowedStatuses: [ - WORK_ORDER_STATUS.PENDING_MATERIAL, - WORK_ORDER_STATUS.UNASSIGNED, - WORK_ORDER_STATUS.OPEN, - ], + allowedStatuses: [WORK_ORDER_STATUS.PENDING_MATERIAL], now, }) if (!updated) { diff --git a/apps/backend/src/services/worker-platform/work-order-payment-amount.test.ts b/apps/backend/src/services/worker-platform/work-order-payment-amount.test.ts index db5e5c4d..59ff8a2a 100644 --- a/apps/backend/src/services/worker-platform/work-order-payment-amount.test.ts +++ b/apps/backend/src/services/worker-platform/work-order-payment-amount.test.ts @@ -3,6 +3,7 @@ import test from 'node:test' import { buildWorkOrderPaymentAmountAuditData, + canModifyUserPaymentAmountStatus, mergeUserPaymentAmountIntoMaterial, normalizeUserPaymentAmountFen, resolveDisplayedUserPaymentAmountFen, @@ -21,6 +22,28 @@ test('normalizeUserPaymentAmountFen:0、负数、超上限与非数字都判 } }) +test('canModifyUserPaymentAmountStatus:只有待完善(pending_material)可改', () => { + assert.equal(canModifyUserPaymentAmountStatus('pending_material'), true) + for (const status of [ + 'unassigned', + 'open', + 'in_progress', + 'pending_acceptance', + 'problem', + 'accepted', + 'cancelled', + '', + null, + undefined, + ]) { + assert.equal( + canModifyUserPaymentAmountStatus(status), + false, + `${String(status)} 不应允许修改用户付款金额`, + ) + } +}) + test('resolveDisplayedUserPaymentAmountFen:优先送码实付,其次来源金额,最后派生列', () => { assert.equal( resolveDisplayedUserPaymentAmountFen({ diff --git a/apps/backend/src/services/worker-platform/work-order-payment-amount.ts b/apps/backend/src/services/worker-platform/work-order-payment-amount.ts index 2be94b20..53cf6986 100644 --- a/apps/backend/src/services/worker-platform/work-order-payment-amount.ts +++ b/apps/backend/src/services/worker-platform/work-order-payment-amount.ts @@ -1,3 +1,4 @@ +import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js' import type { JsonObject } from '../../types/json.js' import { safeParseJson } from '../admin/admin-query-utils.js' @@ -29,6 +30,16 @@ export function normalizeUserPaymentAmountFen(value: unknown): number | null { return fen } +/** + * 用户付款金额只允许在「待完善」阶段修改。 + * + * 进入未分配/待抢单后金额已经参与发单与大厅展示,后台再改会破坏对账口径; + * 已接单/已验收阶段更不允许改动。 + */ +export function canModifyUserPaymentAmountStatus(status: unknown): boolean { + return String(status || '').trim() === WORK_ORDER_STATUS.PENDING_MATERIAL +} + /** * 读取工单当前展示的「用户付款金额」(分)。 * diff --git a/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx b/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx index 4faa85a1..9fca83c1 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx @@ -6,7 +6,11 @@ import { updateAdminWorkOrder, updateAdminWorkOrderPaymentAmount } from '@/servi import type { WorkOrder } from '@/types/worker-platform' import { formatMoney } from './shared' -import { getRewardAbovePayment, getUserPaymentFen } from './work-order-panel-utils' +import { + canEditUserPaymentAmount, + getRewardAbovePayment, + getUserPaymentFen, +} from './work-order-panel-utils' /** 商品名:单击看详情,双击原地修改(仅未接走工单) */ export function ProductNameEditableCell({ @@ -101,7 +105,7 @@ export function UserPaymentAmountEditableCell({ onSaved: () => Promise | void }) { const { message } = App.useApp() - const editable = order.capabilities?.canEdit === true + const editable = canEditUserPaymentAmount(order) const currentFen = getUserPaymentFen(order) const [open, setOpen] = useState(false) const [draft, setDraft] = useState(null) diff --git a/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx b/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx index a582b2ad..826e08fe 100644 --- a/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx +++ b/apps/frontend/src/pages/admin/panels/work-order-panel-utils.tsx @@ -3,7 +3,11 @@ import { Space, Tag, Typography } from 'antd' import type { CollectField, WorkOrder } from '@/types/worker-platform' import { resolveConsignmentWorkerDisplay } from '@/utils/consignment-display' -export { getRewardAbovePayment, getUserPaymentFen } from './work-order-payment' +export { + canEditUserPaymentAmount, + getRewardAbovePayment, + getUserPaymentFen, +} from './work-order-payment' const SKIP_PUBLISH_CONFIRM_KEY = 'admin-work-order-skip-publish-confirm' diff --git a/apps/frontend/src/pages/admin/panels/work-order-payment.ts b/apps/frontend/src/pages/admin/panels/work-order-payment.ts index 91c5e280..ff0b912a 100644 --- a/apps/frontend/src/pages/admin/panels/work-order-payment.ts +++ b/apps/frontend/src/pages/admin/panels/work-order-payment.ts @@ -33,6 +33,11 @@ export function getUserPaymentFen(order: PaymentSourceOrder): number | null { return fromColumn !== null && fromColumn > 0 ? fromColumn : null } +/** 用户付款金额仅在「待完善」阶段可编辑,与后端校验同口径。 */ +export function canEditUserPaymentAmount(order: { status?: string; rawStatus?: string }): boolean { + return String(order.rawStatus || order.status || '').trim() === 'pending_material' +} + /** 发单金额高于用户付款时返回两边金额,否则 null(付款未知时不提示) */ export function getRewardAbovePayment(order: PaymentSourceOrder, rewardFen?: number) { const paymentFen = getUserPaymentFen(order) diff --git a/apps/frontend/tests/work-order-payment-loss.test.ts b/apps/frontend/tests/work-order-payment-loss.test.ts index 4445c182..47ce33fe 100644 --- a/apps/frontend/tests/work-order-payment-loss.test.ts +++ b/apps/frontend/tests/work-order-payment-loss.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict' import test from 'node:test' import { + canEditUserPaymentAmount, getRewardAbovePayment, getUserPaymentFen, } from '../src/pages/admin/panels/work-order-payment.ts' @@ -62,3 +63,31 @@ test('getUserPaymentFen:空值或非法值不误判成 0 元', () => { assert.equal(getUserPaymentFen({ material: { source: { orderAmountFen: 'abc' } } }), null) assert.equal(getUserPaymentFen({ material: {}, sourceOrderAmount: 0 }), null) }) + +test('用户付款金额仅在待完善(pending_material)可编辑', () => { + assert.equal(canEditUserPaymentAmount({ status: 'pending_material' }), true) + assert.equal( + canEditUserPaymentAmount({ status: 'pending_material', rawStatus: 'pending_material' }), + true, + ) + + for (const status of [ + 'unassigned', + 'open', + 'in_progress', + 'pending_acceptance', + 'problem', + 'accepted', + 'cancelled', + ]) { + assert.equal( + canEditUserPaymentAmount({ status, rawStatus: status }), + false, + `${status} 不应允许编辑用户付款金额`, + ) + } + + // rawStatus 是后端原始状态,优先级高于运营态 status + assert.equal(canEditUserPaymentAmount({ status: 'open', rawStatus: 'pending_material' }), true) + assert.equal(canEditUserPaymentAmount({ status: 'pending_material', rawStatus: 'open' }), false) +})