fix(worker): 用户付款金额仅待完善可修改并补后端校验

This commit is contained in:
2026-11-01 16:22:45 +08:00
parent d135a34e35
commit 5879568ad0
8 changed files with 91 additions and 27 deletions
@@ -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
`,
[
@@ -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) {
@@ -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({
@@ -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
}
/**
* 读取工单当前展示的「用户付款金额」(分)。
*
@@ -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> | 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<number | null>(null)
@@ -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'
@@ -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)
@@ -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)
})