Merge remote-tracking branch 'gitea-aliyun/main'
This commit is contained in:
@@ -158,8 +158,16 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
title: note ? `客服已处理问题反馈:${note}` : '客服已处理问题反馈',
|
||||
color: 'green',
|
||||
}
|
||||
case 'timeout_reopen':
|
||||
return { title: '订单超时未完成,已退回抢单大厅', color: 'orange' }
|
||||
case 'timeout_reopen': {
|
||||
const isSharing = Boolean(event.payload?.isSharing)
|
||||
const quantity = readPayloadAmount(event, 'quantity')
|
||||
return {
|
||||
title: isSharing
|
||||
? `拼单份额超时未完成${quantity > 0 ? `(${quantity} 份)` : ''},已退回抢单大厅`
|
||||
: '订单超时未完成,已退回抢单大厅',
|
||||
color: 'orange',
|
||||
}
|
||||
}
|
||||
|
||||
// —— 资料节点 ——
|
||||
case 'material_submitted':
|
||||
@@ -304,6 +312,23 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
}
|
||||
|
||||
// —— 其他 ——
|
||||
case 'payment_amount_updated': {
|
||||
const previousAmountFen = readPayloadAmount(event, 'previousAmountFen')
|
||||
const amountFen = readPayloadAmount(event, 'amountFen')
|
||||
const reason = readPayload(event, 'reason')
|
||||
return {
|
||||
title: '修改了用户付款金额',
|
||||
description: [
|
||||
previousAmountFen > 0
|
||||
? `${formatFenAmount(previousAmountFen)} → ${formatFenAmount(amountFen)}`
|
||||
: `改为 ${formatFenAmount(amountFen)}`,
|
||||
reason ? `原因:${reason}` : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(';'),
|
||||
color: 'gold',
|
||||
}
|
||||
}
|
||||
case 'edited': {
|
||||
const productName = readPayload(event, 'productName')
|
||||
const rewardAmount = readPayloadAmount(event, 'rewardAmount')
|
||||
@@ -418,9 +443,14 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
case 'sharing_share_cancelled_by_admin': {
|
||||
const workerLabel = resolveEventWorkerLabel(event)
|
||||
const quantity = readPayload(event, 'quantity')
|
||||
const details = [
|
||||
quantity ? `撤销 ${quantity} 份` : '',
|
||||
'空出的份数已回到接单大厅',
|
||||
note ? `原因:${note}` : '',
|
||||
].filter(Boolean)
|
||||
return {
|
||||
title: `撤销拼单参与者${workerLabel ? `(${workerLabel})` : ''}`,
|
||||
description: `${quantity ? `撤销 ${quantity} 份;` : ''}${note ? `原因:${note}` : '份数已退回接单大厅'}`,
|
||||
description: details.join(';'),
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { App, Input, InputNumber, Space, Tag, Typography } from 'antd'
|
||||
import { EditOutlined } from '@ant-design/icons'
|
||||
import { App, Input, InputNumber, Modal, Space, Tag, Typography } from 'antd'
|
||||
import { useRef, useState } from 'react'
|
||||
|
||||
import { updateAdminWorkOrder } from '@/services/admin'
|
||||
import { updateAdminWorkOrder, updateAdminWorkOrderPaymentAmount } from '@/services/admin'
|
||||
import type { WorkOrder } from '@/types/worker-platform'
|
||||
|
||||
import { formatMoney } from './shared'
|
||||
import { getRewardAbovePayment } from './work-order-panel-utils'
|
||||
import {
|
||||
canEditUserPaymentAmount,
|
||||
getRewardAbovePayment,
|
||||
getUserPaymentFen,
|
||||
} from './work-order-panel-utils'
|
||||
|
||||
/** 商品名:单击看详情,双击原地修改(仅未接走工单) */
|
||||
export function ProductNameEditableCell({
|
||||
@@ -91,6 +96,131 @@ export function ProductNameEditableCell({
|
||||
)
|
||||
}
|
||||
|
||||
/** 用户付款金额:单击弹窗修改,保留修改原因并写入工单流转记录 */
|
||||
export function UserPaymentAmountEditableCell({
|
||||
order,
|
||||
onSaved,
|
||||
}: {
|
||||
order: WorkOrder
|
||||
onSaved: () => Promise<void> | void
|
||||
}) {
|
||||
const { message } = App.useApp()
|
||||
const editable = canEditUserPaymentAmount(order)
|
||||
const currentFen = getUserPaymentFen(order)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [draft, setDraft] = useState<number | null>(null)
|
||||
const [reason, setReason] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
const savingRef = useRef(false)
|
||||
|
||||
function openEditor() {
|
||||
if (!editable || savingRef.current) return
|
||||
setDraft(currentFen === null ? null : Number((currentFen / 100).toFixed(2)))
|
||||
setReason('')
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (savingRef.current) return
|
||||
if (draft === null || !Number.isFinite(Number(draft))) {
|
||||
message.error('请输入用户付款金额')
|
||||
return
|
||||
}
|
||||
const nextYuan = Number(draft)
|
||||
if (nextYuan < 0.01 || nextYuan > 1000000) {
|
||||
message.error('用户付款金额需在 0.01 元到 1000000 元之间')
|
||||
return
|
||||
}
|
||||
if (currentFen !== null && Math.abs(nextYuan * 100 - currentFen) < 0.5) {
|
||||
message.info('用户付款金额未变化')
|
||||
setOpen(false)
|
||||
return
|
||||
}
|
||||
|
||||
savingRef.current = true
|
||||
setSaving(true)
|
||||
try {
|
||||
await updateAdminWorkOrderPaymentAmount(order.workOrderId, {
|
||||
paymentAmount: nextYuan,
|
||||
reason: reason.trim(),
|
||||
})
|
||||
message.success('用户付款金额已更新')
|
||||
setOpen(false)
|
||||
await onSaved()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '保存失败')
|
||||
} finally {
|
||||
savingRef.current = false
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
title={editable ? '点击修改用户付款金额' : undefined}
|
||||
style={{ cursor: editable ? 'pointer' : 'default', whiteSpace: 'nowrap' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
openEditor()
|
||||
}}
|
||||
>
|
||||
<Space size={4} align="center">
|
||||
<Typography.Text style={editable ? { color: 'rgba(0,0,0,0.65)' } : undefined}>
|
||||
{currentFen === null ? '-' : formatMoney(currentFen)}
|
||||
</Typography.Text>
|
||||
{editable ? <EditOutlined style={{ color: 'rgba(0,0,0,0.35)', fontSize: 12 }} /> : null}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title="修改用户付款金额"
|
||||
open={open}
|
||||
width={420}
|
||||
okText="确认修改"
|
||||
cancelText="取消"
|
||||
confirmLoading={saving}
|
||||
destroyOnHidden
|
||||
onCancel={() => {
|
||||
if (savingRef.current) return
|
||||
setOpen(false)
|
||||
}}
|
||||
onOk={() => void save()}
|
||||
>
|
||||
<div style={{ display: 'grid', gap: 12 }}>
|
||||
<Typography.Text type="secondary">
|
||||
当前金额:{currentFen === null ? '-' : formatMoney(currentFen)}
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
autoFocus
|
||||
min={0.01}
|
||||
max={1000000}
|
||||
precision={2}
|
||||
step={1}
|
||||
addonBefore="¥"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请输入用户实际付款金额"
|
||||
value={draft}
|
||||
onChange={setDraft}
|
||||
onPressEnter={() => void save()}
|
||||
/>
|
||||
<Input.TextArea
|
||||
value={reason}
|
||||
maxLength={200}
|
||||
showCount
|
||||
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||
placeholder="修改原因(选填,最多 200 字)"
|
||||
onChange={(event) => setReason(event.target.value)}
|
||||
/>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
修改会记录到工单流转记录与后台审计日志;财务统计按下一次定时同步更新。
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function AmountEditableCell({
|
||||
order,
|
||||
field,
|
||||
|
||||
@@ -130,11 +130,14 @@ import {
|
||||
resolveSharingShareStatusColor,
|
||||
resolveStatusColor,
|
||||
} from './shared'
|
||||
import { AmountEditableCell, ProductNameEditableCell } from './WorkOrderTableCells'
|
||||
import {
|
||||
AmountEditableCell,
|
||||
ProductNameEditableCell,
|
||||
UserPaymentAmountEditableCell,
|
||||
} from './WorkOrderTableCells'
|
||||
import { MockWorkOrderForm, WorkOrderStatisticsBar } from './WorkOrderPanelParts'
|
||||
import {
|
||||
formatRequirementFieldsText,
|
||||
formatUserPaymentAmount,
|
||||
getDisplayOrderNo,
|
||||
getRewardAbovePayment,
|
||||
parseWorkerMaterialText,
|
||||
@@ -780,7 +783,7 @@ export default function WorkOrdersPanel() {
|
||||
cancelAdminWorkOrderShare(workOrderId, cancelShare.shareId, {
|
||||
reason: String(values.reason || '').trim(),
|
||||
}),
|
||||
'该参与者已撤单,押金已退还,母单状态未变更',
|
||||
'该参与者已撤单,押金已退还,空出的份数已回到接单大厅',
|
||||
)
|
||||
if (!succeeded) return
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -1724,7 +1727,7 @@ export default function WorkOrdersPanel() {
|
||||
{
|
||||
title: '用户付款金额',
|
||||
width: 130,
|
||||
render: (_, row) => formatUserPaymentAmount(row),
|
||||
render: (_, row) => <UserPaymentAmountEditableCell order={row} onSaved={refreshAll} />,
|
||||
},
|
||||
{
|
||||
title: '发单金额',
|
||||
@@ -3714,7 +3717,7 @@ export default function WorkOrdersPanel() {
|
||||
danger
|
||||
size="small"
|
||||
icon={<UndoOutlined />}
|
||||
title="只撤销当前参与者的拼单份额,其他参与者不受影响"
|
||||
title="只撤销当前参与者的拼单份额,空出的份数回到接单大厅,其他参与者不受影响"
|
||||
onClick={() => {
|
||||
setCancelShare(share)
|
||||
cancelShareForm.setFieldsValue({ reason: '' })
|
||||
@@ -3776,7 +3779,7 @@ export default function WorkOrdersPanel() {
|
||||
showIcon
|
||||
type="warning"
|
||||
message={`将撤销该打手承接的 ${cancelShare.quantity} 份拼单`}
|
||||
description="撤单只释放这一份份额并退还该份额冻结的押金,母单状态保持不变(待验收/进行中的单不会被踢回大厅);已提交的验收资料会保留在后台记录中。"
|
||||
description="撤单只释放这一份份额并退还该份额冻结的押金;空出的份数会回到接单大厅,母单显示待抢单;重新拼满后再按各份额状态推进。该份额已提交的验收资料会被清空,仅保留撤单记录。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item label="撤单原因" name="reason">
|
||||
@@ -3810,7 +3813,7 @@ export default function WorkOrdersPanel() {
|
||||
showIcon
|
||||
type="warning"
|
||||
message={`将从该打手的 ${reduceShare.quantity} 份中撤掉指定份数`}
|
||||
description="撤掉的份数会立刻回到抢单大厅;押金保持原冻结额不退还;验收资料保留,之后仍可单独验收剩余份数。"
|
||||
description="撤掉的份数会立刻回到抢单大厅,母单显示待抢单;押金保持原冻结额不退还;验收资料保留,之后仍可单独验收剩余份数。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form.Item
|
||||
|
||||
@@ -3,10 +3,11 @@ import { Space, Tag, Typography } from 'antd'
|
||||
import type { CollectField, WorkOrder } from '@/types/worker-platform'
|
||||
import { resolveConsignmentWorkerDisplay } from '@/utils/consignment-display'
|
||||
|
||||
import { formatMoney } from './shared'
|
||||
import { getUserPaymentFen } from './work-order-payment'
|
||||
|
||||
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'
|
||||
|
||||
@@ -26,11 +27,6 @@ export function setSkipPublishConfirm() {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatUserPaymentAmount(order: WorkOrder): string {
|
||||
const paymentFen = getUserPaymentFen(order)
|
||||
return paymentFen === null ? '-' : formatMoney(paymentFen)
|
||||
}
|
||||
|
||||
/** 渲染工单关联打手:普通打手 / 拼单参与打手 / 寄卖供货打手与现货资料 */
|
||||
export function renderWorkOrderWorkers(order: WorkOrder) {
|
||||
if (order.worker) return order.worker.displayName || order.worker.username || '-'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
type PaymentSourceOrder = {
|
||||
material?: unknown
|
||||
rewardAmount?: number
|
||||
/** 后台列表接口返回的 083 派生列(分);来源 JSON 缺失时兜底 */
|
||||
sourceOrderAmount?: number
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
@@ -9,13 +11,31 @@ function asRecord(value: unknown): Record<string, unknown> {
|
||||
: {}
|
||||
}
|
||||
|
||||
function parsePaymentFen(value: unknown): number | null {
|
||||
if (value === undefined || value === null) return null
|
||||
const text = String(value).trim()
|
||||
if (!text) return null
|
||||
const parsed = Number(text)
|
||||
return Number.isFinite(parsed) ? parsed : null
|
||||
}
|
||||
|
||||
/** 用户付款金额(分);未知返回 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
|
||||
const fromMaterial =
|
||||
parsePaymentFen(sendCode.paymentFen) ?? parsePaymentFen(source.orderAmountFen)
|
||||
if (fromMaterial !== null) return fromMaterial
|
||||
|
||||
// 历史单可能只有派生列;0 同时代表「NULL 回退值」,按未知处理。
|
||||
const fromColumn = parsePaymentFen(order.sourceOrderAmount)
|
||||
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(付款未知时不提示) */
|
||||
|
||||
@@ -806,6 +806,24 @@ export function updateAdminWorkOrder(
|
||||
)
|
||||
}
|
||||
|
||||
export function updateAdminWorkOrderPaymentAmount(
|
||||
workOrderId: number,
|
||||
payload: {
|
||||
/** 用户付款金额(元) */
|
||||
paymentAmount: number
|
||||
/** 修改原因(选填,≤200 字) */
|
||||
reason?: string
|
||||
},
|
||||
) {
|
||||
return apiPut<{
|
||||
order: WorkOrder
|
||||
previousAmountFen: number | null
|
||||
amountFen: number
|
||||
reason: string
|
||||
unchanged: boolean
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/payment-amount`, payload)
|
||||
}
|
||||
|
||||
export function updateAdminWorkOrderNote(workOrderId: number, note: string) {
|
||||
return apiPut<{ order: WorkOrder }>(`/api/v1/admin/worker-platform/orders/${workOrderId}/note`, {
|
||||
note,
|
||||
|
||||
@@ -37,6 +37,7 @@ export function formatAuditAction(action: string) {
|
||||
work_order_published: '工单发布',
|
||||
work_order_unpublished: '工单下架',
|
||||
work_order_updated: '工单信息更新',
|
||||
work_order_payment_amount_updated: '用户付款金额修改',
|
||||
work_order_deleted: '工单删除',
|
||||
work_order_accepted: '工单验收',
|
||||
work_orders_batch_accepted: '批量验收',
|
||||
|
||||
@@ -88,6 +88,7 @@ export const adminAuditActionOptions = [
|
||||
{ label: '工单发布', value: 'work_order_published' },
|
||||
{ label: '工单下架', value: 'work_order_unpublished' },
|
||||
{ label: '工单信息更新', value: 'work_order_updated' },
|
||||
{ label: '用户付款金额修改', value: 'work_order_payment_amount_updated' },
|
||||
{ label: '工单删除', value: 'work_order_deleted' },
|
||||
{ label: '工单验收', value: 'work_order_accepted' },
|
||||
{ label: '批量验收', value: 'work_orders_batch_accepted' },
|
||||
|
||||
Reference in New Issue
Block a user