From ef491660e788af227bcd3cf0612016a881bf3871 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Mon, 21 Sep 2026 15:48:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin-worker):=20=E5=8F=91=E5=8D=95?= =?UTF-8?q?=E9=87=91=E9=A2=9D=E9=AB=98=E4=BA=8E=E7=94=A8=E6=88=B7=E4=BB=98?= =?UTF-8?q?=E6=AC=BE=E6=97=B6=E5=BC=BA=E5=88=B6=E7=A1=AE=E8=AE=A4=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E5=8F=AA=E9=9D=A0=E7=BA=A2=E8=89=B2=E5=AD=97?= =?UTF-8?q?=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发布、指派和改价都会弹出亏损确认,且不能用「不再提示」跳过;列表发单金额旁增加「高于付款」标签。 Co-authored-by: Cursor --- .../admin/panels/WorkOrderTableCells.tsx | 38 +++-- .../pages/admin/panels/WorkOrdersPanel.tsx | 159 ++++++++++++------ .../admin/panels/work-order-panel-utils.tsx | 14 +- .../pages/admin/panels/work-order-payment.ts | 29 ++++ .../tests/work-order-payment-loss.test.ts | 37 ++++ 5 files changed, 200 insertions(+), 77 deletions(-) create mode 100644 apps/frontend/src/pages/admin/panels/work-order-payment.ts create mode 100644 apps/frontend/tests/work-order-payment-loss.test.ts diff --git a/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx b/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx index 1bc923b6..3aa5bd6c 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrderTableCells.tsx @@ -1,11 +1,11 @@ -import { App, Input, InputNumber, Typography } from 'antd' +import { App, Input, InputNumber, Space, Tag, Typography } from 'antd' import { useRef, useState } from 'react' import { updateAdminWorkOrder } from '@/services/admin' import type { WorkOrder } from '@/types/worker-platform' import { formatMoney } from './shared' -import { getUserPaymentFen } from './work-order-panel-utils' +import { getRewardAbovePayment } from './work-order-panel-utils' /** 商品名:单击看详情,双击原地修改(仅未接走工单) */ export function ProductNameEditableCell({ @@ -107,8 +107,8 @@ export function AmountEditableCell({ const editable = order.capabilities?.canEdit === true const currentFen = Number(order[field] || 0) const currentYuan = currentFen / 100 - const paymentFen = field === 'rewardAmount' ? getUserPaymentFen(order) : null - const isLossOrder = paymentFen !== null && currentFen > paymentFen + const loss = field === 'rewardAmount' ? getRewardAbovePayment(order, currentFen) : null + const isLossOrder = Boolean(loss) async function doSave(nextYuan: number) { if (savingRef.current) return @@ -141,19 +141,21 @@ export function AmountEditableCell({ return } const nextFen = Math.round(nextYuan * 100) - if (paymentFen !== null && nextFen > paymentFen) { + const nextLoss = field === 'rewardAmount' ? getRewardAbovePayment(order, nextFen) : null + if (nextLoss) { modal.confirm({ title: '发单金额高于用户付款金额', content: ( - 该订单用户付款 {formatMoney(paymentFen)},发单金额{' '} + 该订单用户付款 {formatMoney(nextLoss.paymentFen)},发单金额{' '} - {formatMoney(nextFen)} + {formatMoney(nextLoss.rewardFen)} ,本单平台将亏损。确认保存? ), okText: '确认保存', + okButtonProps: { danger: true }, cancelText: '取消', onOk: () => doSave(nextYuan), }) @@ -164,22 +166,26 @@ export function AmountEditableCell({ if (!editing) { return ( - { if (!editable) return setDraft(currentYuan) setEditing(true) }} > - {formatMoney(currentFen)} - + + + {formatMoney(currentFen)} + + {isLossOrder ? 高于付款 : null} + + ) } diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 9a96f2e3..188c3727 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -119,7 +119,7 @@ import { formatRequirementFieldsText, formatUserPaymentAmount, getDisplayOrderNo, - getUserPaymentFen, + getRewardAbovePayment, parseWorkerMaterialText, renderWorkOrderWorkers, setSkipPublishConfirm, @@ -546,9 +546,21 @@ export default function WorkOrdersPanel() { }, [acceptanceMode]) function openAssignModal(row: WorkOrder) { - setAssignOrder(row) - setAssignWorkerId(undefined) - setAssignWorkerKeyword('') + const open = () => { + setAssignOrder(row) + setAssignWorkerId(undefined) + setAssignWorkerKeyword('') + } + if ( + confirmRewardAbovePayment(row, { + okText: '继续指派', + question: '确认继续指派?', + onOk: open, + }) + ) { + return + } + open() } function openCancelModal(row: WorkOrder) { @@ -730,6 +742,36 @@ export default function WorkOrdersPanel() { } } + function confirmRewardAbovePayment( + order: WorkOrder, + options: { + rewardFen?: number + okText: string + question: string + onOk: () => void | Promise + }, + ) { + const loss = getRewardAbovePayment(order, options.rewardFen) + if (!loss) return false + modal.confirm({ + title: '发单金额高于用户付款金额', + content: ( + + 该订单用户付款 {formatMoney(loss.paymentFen)},发单金额{' '} + + {formatMoney(loss.rewardFen)} + + ,本单平台将亏损。{options.question} + + ), + okText: options.okText, + okButtonProps: { danger: true }, + cancelText: '取消', + onOk: options.onOk, + }) + return true + } + function publishWorkOrder(row: WorkOrder) { return runAction( () => publishAdminWorkOrder(row.workOrderId), @@ -745,6 +787,16 @@ export default function WorkOrdersPanel() { } function confirmPublishWorkOrder(row: WorkOrder) { + if ( + confirmRewardAbovePayment(row, { + okText: '仍要发布', + question: '确认发布?', + onOk: () => publishWorkOrder(row), + }) + ) { + return + } + if (shouldSkipPublishConfirm()) { void publishWorkOrder(row) return @@ -983,28 +1035,44 @@ export default function WorkOrdersPanel() { materialOrder.status === 'in_progress' && !materialOrder.sharing?.enabled ? Number(values.rewardAmount || 0) : undefined - const succeeded = await runAction( - async () => { - const response = await submitAdminWorkOrderMaterial( - materialOrder.workOrderId, - values.fields || {}, - materialScreenshots, - requestedRewardAmount, - ) - if ( - requestedRewardAmount !== undefined && - Number(response.data.order.rewardAmount || 0) !== Math.round(requestedRewardAmount * 100) - ) { - throw new Error('接单价格保存未生效,请刷新后重试') - } - return response - }, - materialOrder.status === 'in_progress' ? '订单资料已更新' : '订单资料已保存', - ) - if (!succeeded) return - setMaterialOrder(null) - materialForm.resetFields() - setMaterialScreenshots([]) + const doSubmit = async () => { + const succeeded = await runAction( + async () => { + const response = await submitAdminWorkOrderMaterial( + materialOrder.workOrderId, + values.fields || {}, + materialScreenshots, + requestedRewardAmount, + ) + if ( + requestedRewardAmount !== undefined && + Number(response.data.order.rewardAmount || 0) !== + Math.round(requestedRewardAmount * 100) + ) { + throw new Error('接单价格保存未生效,请刷新后重试') + } + return response + }, + materialOrder.status === 'in_progress' ? '订单资料已更新' : '订单资料已保存', + ) + if (!succeeded) return + setMaterialOrder(null) + materialForm.resetFields() + setMaterialScreenshots([]) + } + if (requestedRewardAmount !== undefined) { + if ( + confirmRewardAbovePayment(materialOrder, { + rewardFen: Math.round(requestedRewardAmount * 100), + okText: '确认保存', + question: '确认保存资料?', + onOk: doSubmit, + }) + ) { + return + } + } + await doSubmit() } function isExplicitOrderNoSearch(text: string) { @@ -1144,35 +1212,24 @@ export default function WorkOrdersPanel() { }), '工单信息已保存', ) - const paymentFen = getUserPaymentFen(editOrder) const rewardFen = Math.round(Number(values.rewardAmount || 0) * 100) - if (paymentFen !== null && rewardFen > paymentFen) { - modal.confirm({ - title: '发单金额高于用户付款金额', - content: ( - - 该订单用户付款 {formatMoney(paymentFen)},发单金额{' '} - - {formatMoney(rewardFen)} - - ,本单平台将亏损。确认保存? - - ), + const doFinish = async () => { + const succeeded = await doSave() + if (!succeeded) return + setEditOrder(null) + editForm.resetFields() + } + if ( + confirmRewardAbovePayment(editOrder, { + rewardFen, okText: '确认保存', - cancelText: '取消', - onOk: async () => { - const succeeded = await doSave() - if (!succeeded) return - setEditOrder(null) - editForm.resetFields() - }, + question: '确认保存?', + onOk: doFinish, }) + ) { return } - const succeeded = await doSave() - if (!succeeded) return - setEditOrder(null) - editForm.resetFields() + await doFinish() } function requestDeleteWorkOrder(row: WorkOrder) { @@ -1393,7 +1450,7 @@ export default function WorkOrdersPanel() { }, { title: '发单金额', - width: 130, + width: 160, render: (_, row) => ( ), 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 b31d7668..60cdc40f 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,10 @@ import { Space, Tag, Typography } from 'antd' import type { CollectField, WorkOrder } from '@/types/worker-platform' import { resolveConsignmentWorkerDisplay } from '@/utils/consignment-display' -import { asRecord, formatMoney } from './shared' +import { formatMoney } from './shared' +import { getUserPaymentFen } from './work-order-payment' + +export { getRewardAbovePayment, getUserPaymentFen } from './work-order-payment' const SKIP_PUBLISH_CONFIRM_KEY = 'admin-work-order-skip-publish-confirm' @@ -88,15 +91,6 @@ export function getDisplayOrderNo( return String(order?.workOrderNo || order?.platformOrderId || '').trim() || '-' } -/** 用户付款金额(分);未知返回 null(不参与超价提示) */ -export function getUserPaymentFen(order: WorkOrder): number | null { - const material = asRecord(order.material) - const source = asRecord(material.source) - const sendCode = asRecord(source.kuaishouSendCode) - const paymentFen = Number(sendCode.paymentFen ?? source.orderAmountFen) - return Number.isFinite(paymentFen) ? paymentFen : null -} - /** 解析客服粘贴文本(如「安卓还是苹果(必填):安卓」),返回字段 key → 值 */ export function parseWorkerMaterialText(text: string): Record { const result: Record = {} diff --git a/apps/frontend/src/pages/admin/panels/work-order-payment.ts b/apps/frontend/src/pages/admin/panels/work-order-payment.ts new file mode 100644 index 00000000..04bcb46b --- /dev/null +++ b/apps/frontend/src/pages/admin/panels/work-order-payment.ts @@ -0,0 +1,29 @@ +type PaymentSourceOrder = { + material?: unknown + rewardAmount?: number +} + +function asRecord(value: unknown): Record { + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : {} +} + +/** 用户付款金额(分);未知返回 null(不参与超价提示) */ +export function getUserPaymentFen(order: PaymentSourceOrder): number | null { + const material = asRecord(order.material) + const source = asRecord(material.source) + const sendCode = asRecord(source.kuaishouSendCode) + const paymentFen = Number(sendCode.paymentFen ?? source.orderAmountFen) + return Number.isFinite(paymentFen) ? paymentFen : null +} + +/** 发单金额高于用户付款时返回两边金额,否则 null(付款未知时不提示) */ +export function getRewardAbovePayment(order: PaymentSourceOrder, rewardFen?: number) { + const paymentFen = getUserPaymentFen(order) + const nextRewardFen = Number(rewardFen === undefined ? order.rewardAmount || 0 : rewardFen) + if (paymentFen === null || !Number.isFinite(nextRewardFen) || nextRewardFen <= paymentFen) { + return null + } + return { paymentFen, rewardFen: nextRewardFen } +} diff --git a/apps/frontend/tests/work-order-payment-loss.test.ts b/apps/frontend/tests/work-order-payment-loss.test.ts new file mode 100644 index 00000000..326281e9 --- /dev/null +++ b/apps/frontend/tests/work-order-payment-loss.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { getRewardAbovePayment } from '../src/pages/admin/panels/work-order-payment.ts' + +function orderWithPayment(paymentFen: number, rewardFen: number) { + return { + rewardAmount: rewardFen, + material: { + source: { + kuaishouSendCode: { paymentFen }, + }, + }, + } +} + +test('发单金额高于用户付款时返回两边金额', () => { + const loss = getRewardAbovePayment(orderWithPayment(900, 6800)) + assert.ok(loss) + assert.equal(loss.paymentFen, 900) + assert.equal(loss.rewardFen, 6800) +}) + +test('发单金额不超过用户付款时不提示', () => { + assert.equal(getRewardAbovePayment(orderWithPayment(6800, 6800)), null) + assert.equal(getRewardAbovePayment(orderWithPayment(6800, 6700)), null) +}) + +test('用户付款未知时不参与超价提示', () => { + assert.equal(getRewardAbovePayment({ rewardAmount: 6800, material: {} }), null) +}) + +test('可传入即将保存的发单金额做预检', () => { + const order = orderWithPayment(900, 800) + assert.equal(getRewardAbovePayment(order), null) + assert.ok(getRewardAbovePayment(order, 1000)) +})