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))
+})