fix(worker-platform): 修复售后欠款抵扣导致验收失败,并支持余额偿还
有待还欠款时结算写入 settled_at 发生 timestamptz/text 类型错误会整单 500;顺带支持充值/解冻自动偿还与后台手动偿还。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
createAdminAfterSalesCase,
|
||||
fetchAdminAfterSalesCases,
|
||||
fetchAdminWorkOrderSharing,
|
||||
repayAdminAfterSalesCaseDebt,
|
||||
resolveAdminAfterSalesCase,
|
||||
} from '@/services/admin'
|
||||
import type { UploadedFile, WorkerAfterSalesCase, WorkOrder } from '@/types/worker-platform'
|
||||
@@ -43,6 +44,11 @@ type AfterSalesResolutionFormValues = {
|
||||
recoveryAmount?: number
|
||||
}
|
||||
|
||||
type AfterSalesRepayFormValues = {
|
||||
amount?: number
|
||||
note?: string
|
||||
}
|
||||
|
||||
export type AfterSalesPanelHandle = {
|
||||
openCreateModal: (row: WorkOrder) => void
|
||||
}
|
||||
@@ -52,11 +58,13 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
|
||||
const queryClient = useQueryClient()
|
||||
const [afterSalesOrder, setAfterSalesOrder] = useState<WorkOrder | null>(null)
|
||||
const [afterSalesCase, setAfterSalesCase] = useState<WorkerAfterSalesCase | null>(null)
|
||||
const [repayCase, setRepayCase] = useState<WorkerAfterSalesCase | null>(null)
|
||||
const [afterSalesFiles, setAfterSalesFiles] = useState<UploadedFile[]>([])
|
||||
const [afterSalesPage, setAfterSalesPage] = useState(1)
|
||||
const [afterSalesPageSize, setAfterSalesPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
|
||||
const [afterSalesCreateForm] = Form.useForm<AfterSalesCreateFormValues>()
|
||||
const [afterSalesResolutionForm] = Form.useForm<AfterSalesResolutionFormValues>()
|
||||
const [afterSalesRepayForm] = Form.useForm<AfterSalesRepayFormValues>()
|
||||
|
||||
const afterSalesSharingQuery = useQuery({
|
||||
queryKey: ['admin-worker-platform-after-sales-sharing', afterSalesOrder?.workOrderId],
|
||||
@@ -147,6 +155,31 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
|
||||
afterSalesResolutionForm.resetFields()
|
||||
}
|
||||
|
||||
function openAfterSalesRepayModal(item: WorkerAfterSalesCase) {
|
||||
setRepayCase(item)
|
||||
afterSalesRepayForm.setFieldsValue({
|
||||
amount: Number(item.debtAmount || 0) / 100,
|
||||
note: '后台手动偿还售后欠款',
|
||||
})
|
||||
}
|
||||
|
||||
async function submitAfterSalesRepay(values: AfterSalesRepayFormValues) {
|
||||
if (!repayCase) return
|
||||
const succeeded = await runAction(
|
||||
() =>
|
||||
repayAdminAfterSalesCaseDebt(repayCase.caseId, {
|
||||
amount: Number(values.amount || 0),
|
||||
note: String(values.note || '').trim(),
|
||||
}),
|
||||
(result) =>
|
||||
`已偿还 ${formatMoney(result.data?.repaidAmount || 0)},余额剩余 ${formatMoney(result.data?.availableAfter || 0)}`,
|
||||
)
|
||||
if (!succeeded) return
|
||||
setRepayCase(null)
|
||||
afterSalesRepayForm.resetFields()
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-workers'] })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
@@ -277,7 +310,7 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 120,
|
||||
width: 160,
|
||||
render: (_, item) =>
|
||||
['awaiting_worker_response', 'under_review'].includes(item.status) ? (
|
||||
<Button
|
||||
@@ -287,6 +320,10 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
|
||||
>
|
||||
处置
|
||||
</Button>
|
||||
) : item.status === 'recovery_pending' && Number(item.debtAmount || 0) > 0 ? (
|
||||
<Button type="primary" size="small" danger onClick={() => openAfterSalesRepayModal(item)}>
|
||||
手动偿还
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">已完成</Typography.Text>
|
||||
),
|
||||
@@ -403,7 +440,7 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
|
||||
label="追缴金额"
|
||||
name="recoveryAmount"
|
||||
rules={[{ required: true, message: '请输入追缴金额' }]}
|
||||
extra="系统依次扣减该责任打手的本单待解冻金额、可用余额;不足部分转为欠款,并从后续验收报酬自动抵扣。"
|
||||
extra="系统依次扣减该责任打手的本单待解冻金额、可用余额;不足部分转为欠款,可从后续验收报酬、充值入账自动抵扣,也可在此手动偿还。"
|
||||
>
|
||||
<InputNumber min={0} precision={2} addonAfter="元" className="full-width" />
|
||||
</Form.Item>
|
||||
@@ -424,6 +461,50 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`手动偿还欠款 · ${repayCase?.caseNo || ''}`}
|
||||
open={Boolean(repayCase)}
|
||||
onCancel={() => {
|
||||
setRepayCase(null)
|
||||
afterSalesRepayForm.resetFields()
|
||||
}}
|
||||
onOk={() => afterSalesRepayForm.submit()}
|
||||
okText="确认偿还"
|
||||
destroyOnHidden
|
||||
>
|
||||
{repayCase ? (
|
||||
<Form form={afterSalesRepayForm} layout="vertical" onFinish={submitAfterSalesRepay}>
|
||||
<Alert
|
||||
showIcon
|
||||
type="info"
|
||||
style={{ marginBottom: 16 }}
|
||||
message={`将从打手「${repayCase.workerName || '-'}」可用余额扣减,当前待还 ${formatMoney(repayCase.debtAmount)}。`}
|
||||
/>
|
||||
<Form.Item
|
||||
label="偿还金额"
|
||||
name="amount"
|
||||
rules={[{ required: true, message: '请输入偿还金额' }]}
|
||||
extra="默认偿还全部待还;也可填写部分金额。"
|
||||
>
|
||||
<InputNumber
|
||||
min={0.01}
|
||||
max={Number(repayCase.debtAmount || 0) / 100}
|
||||
precision={2}
|
||||
addonAfter="元"
|
||||
className="full-width"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="偿还备注"
|
||||
name="note"
|
||||
rules={[{ required: true, message: '请填写偿还备注' }]}
|
||||
>
|
||||
<Input.TextArea rows={3} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -582,6 +582,20 @@ export function resolveAdminAfterSalesCase(
|
||||
}>(`/api/v1/admin/worker-platform/after-sales-cases/${caseId}/resolve`, payload)
|
||||
}
|
||||
|
||||
export function repayAdminAfterSalesCaseDebt(
|
||||
caseId: number,
|
||||
payload: {
|
||||
amount?: number | null
|
||||
note?: string
|
||||
} = {},
|
||||
) {
|
||||
return apiPost<{
|
||||
case: WorkerAfterSalesCase
|
||||
repaidAmount: number
|
||||
availableAfter: number
|
||||
}>(`/api/v1/admin/worker-platform/after-sales-cases/${caseId}/repay`, payload)
|
||||
}
|
||||
|
||||
export function fetchAdminWorkOrderEvents(workOrderId: number) {
|
||||
return apiGet<{ items: WorkOrderEvent[] }>(
|
||||
`/api/v1/admin/worker-platform/orders/${workOrderId}/events`,
|
||||
|
||||
@@ -53,6 +53,7 @@ export function formatAuditAction(action: string) {
|
||||
work_order_pending_deposit_deducted: '押金扣除',
|
||||
after_sales_case_created: '售后问题单创建',
|
||||
after_sales_case_resolved: '售后问题单处置',
|
||||
after_sales_case_debt_repaid: '售后欠款手动偿还',
|
||||
worker_level_saved: '接单等级保存',
|
||||
worker_level_deleted: '接单等级删除',
|
||||
work_category_saved: '接单分类保存',
|
||||
|
||||
@@ -80,6 +80,7 @@ export const adminAuditActionOptions = [
|
||||
{ label: '押金扣除', value: 'work_order_pending_deposit_deducted' },
|
||||
{ label: '售后问题单创建', value: 'after_sales_case_created' },
|
||||
{ label: '售后问题单处置', value: 'after_sales_case_resolved' },
|
||||
{ label: '售后欠款手动偿还', value: 'after_sales_case_debt_repaid' },
|
||||
{ label: '接单等级保存', value: 'worker_level_saved' },
|
||||
{ label: '接单等级删除', value: 'worker_level_deleted' },
|
||||
{ label: '接单分类保存', value: 'work_category_saved' },
|
||||
|
||||
Reference in New Issue
Block a user