Merge branch 'dev1023' into dev

This commit is contained in:
2026-10-26 15:26:43 +08:00
16 changed files with 860 additions and 43 deletions
@@ -440,6 +440,24 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
color: 'red',
}
}
case 'sharing_part_cancelled': {
const cancelledQuantity = readPayloadAmount(event, 'cancelledQuantity')
const totalQuantityAfter = readPayloadAmount(event, 'totalQuantityAfter')
const releasedDepositAmount = readPayloadAmount(event, 'releasedDepositAmount')
const refundAmount = readPayloadAmount(event, 'refundAmount')
const details = [
cancelledQuantity > 0 ? `取消 ${cancelledQuantity} 份未验收份数` : '',
totalQuantityAfter > 0 ? `保留 ${totalQuantityAfter} 份已验收` : '整单已取消',
releasedDepositAmount > 0 ? `退还押金 ${formatFenAmount(releasedDepositAmount)}` : '',
refundAmount > 0 ? `登记退款 ${formatFenAmount(refundAmount)}` : '',
note ? `原因:${note}` : '',
].filter(Boolean)
return {
title: '取消拼单未验收部分',
description: details.length > 0 ? details.join(';') : undefined,
color: 'red',
}
}
case 'pending_deposit_deducted':
return { title: '扣减了待解冻押金', color: 'default' }
case 'voucher_backfilled':
@@ -56,6 +56,7 @@ import {
cancelAdminWorkOrder,
cancelAdminWorkOrderShare,
cancelAdminWorkOrderSharing,
cancelAdminWorkOrderSharingPart,
completeAdminWorkOrderGiftCooldown,
confirmAdminWorkOrderFriend,
deductAdminWorkOrderPendingDeposit,
@@ -185,6 +186,11 @@ type CancelSharingFormValues = {
reason?: string
}
type CancelSharingPartFormValues = {
refundAmount?: number
reason?: string
}
/** 工单 `material.consignment`(寄售履约信息);没有寄售履约信息时返回 null。 */
function getConsignmentFulfillment(row: WorkOrder): ConsignmentFulfillmentInfo | null {
const consignment = asRecord((row.material as Record<string, unknown> | undefined)?.consignment)
@@ -311,6 +317,7 @@ export default function WorkOrdersPanel() {
const [problemShare, setProblemShare] = useState<WorkOrderShare | null>(null)
const [disposeShare, setDisposeShare] = useState<WorkOrderShare | null>(null)
const [cancelSharingOrder, setCancelSharingOrder] = useState<WorkOrder | null>(null)
const [cancelSharingPartOrder, setCancelSharingPartOrder] = useState<WorkOrder | null>(null)
const [assignOrder, setAssignOrder] = useState<WorkOrder | null>(null)
const [assignWorkerId, setAssignWorkerId] = useState<number | undefined>(undefined)
const [editOrder, setEditOrder] = useState<WorkOrder | null>(null)
@@ -329,6 +336,7 @@ export default function WorkOrdersPanel() {
note?: string
}>()
const [cancelSharingForm] = Form.useForm<CancelSharingFormValues>()
const [cancelSharingPartForm] = Form.useForm<CancelSharingPartFormValues>()
const [deductForm] = Form.useForm<{ amount?: number; note?: string }>()
const [editForm] = Form.useForm<{
productName?: string
@@ -551,6 +559,18 @@ export default function WorkOrdersPanel() {
['joined', 'submitted'].includes(share.status),
)
const hasAcceptedSharingShares = sharingShares.some((share) => share.status === 'accepted')
const cancelSharingPartTotalQuantity = Math.max(
1,
Number(cancelSharingPartOrder?.sharing?.totalQuantity || 1),
)
const cancelSharingPartAcceptedQuantity = Math.max(
0,
Number(cancelSharingPartOrder?.sharingProgress?.acceptedQuantity || 0),
)
const cancelSharingPartCancelledQuantity = Math.max(
0,
cancelSharingPartTotalQuantity - cancelSharingPartAcceptedQuantity,
)
const assignWorkersQuery = useQuery({
queryKey: ['admin-worker-platform-workers', 'assign', assignWorkerKeyword],
queryFn: () =>
@@ -821,6 +841,48 @@ export default function WorkOrdersPanel() {
cancelSharingForm.resetFields()
}
/** 打开「取消部分」:默认按实付金额比例预填退款金额。 */
function openCancelSharingPartModal(order: WorkOrder) {
const totalQuantity = Math.max(1, Number(order.sharing?.totalQuantity || 1))
const acceptedQuantity = Math.max(0, Number(order.sharingProgress?.acceptedQuantity || 0))
const cancelledQuantity = Math.max(0, totalQuantity - acceptedQuantity)
const paidAmountFen = Math.max(0, Number(order.sourceOrderAmount || 0))
setCancelSharingPartOrder(order)
cancelSharingPartForm.setFieldsValue({
refundAmount:
totalQuantity > 0
? Math.round((paidAmountFen * cancelledQuantity) / totalQuantity) / 100
: 0,
reason: '',
})
}
async function submitCancelSharingPart(values: CancelSharingPartFormValues) {
if (!cancelSharingPartOrder) return
const workOrderId = cancelSharingPartOrder.workOrderId
const refundAmount = values.refundAmount
const succeeded = await runAction(
() =>
cancelAdminWorkOrderSharingPart(workOrderId, {
...(refundAmount === undefined || refundAmount === null
? {}
: { refundAmount: Number(refundAmount) }),
reason: String(values.reason || '').trim(),
}),
(result) =>
`已取消 ${result.data.cancelledQuantity} 份未验收份数,退还押金 ${formatMoney(
result.data.releasedDepositAmount,
)},登记退款 ${formatMoney(result.data.refundAmount)}`,
)
if (!succeeded) return
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-order-sharing', workOrderId],
})
setCancelSharingPartOrder(null)
setSharingOrder(null)
cancelSharingPartForm.resetFields()
}
async function refreshAll() {
await Promise.all([
queryClient.invalidateQueries({
@@ -3114,6 +3176,7 @@ export default function WorkOrdersPanel() {
setCancelShare(null)
setReduceShare(null)
setCancelSharingOrder(null)
setCancelSharingPartOrder(null)
sharingForm.resetFields()
cancelShareForm.resetFields()
reduceShareForm.resetFields()
@@ -3201,6 +3264,26 @@ export default function WorkOrdersPanel() {
</Form>
<Card size="small" title="拼单明细">
{sharingOrder.capabilities?.canCancelSharingPart ? (
<Space
direction="vertical"
size={8}
className="full-width"
style={{ marginBottom: 12 }}
>
<Alert
showIcon
type="warning"
message="取消部分会取消所有未验收份数(进行中 + 待接单)"
description="已验收份额保留;取消的份额退还押金。退款金额只登记,实际退款请在来源平台操作。"
/>
<div>
<Button danger onClick={() => openCancelSharingPartModal(sharingOrder)}>
取消部分
</Button>
</div>
</Space>
) : null}
{sharingQuery.isLoading ? (
<Typography.Text type="secondary">加载中...</Typography.Text>
) : (sharingQuery.data?.data.shares || []).length === 0 ? (
@@ -3782,6 +3865,60 @@ export default function WorkOrdersPanel() {
) : null}
</Modal>
<Modal
title={`取消部分 · ${getDisplayOrderNo(cancelSharingPartOrder)}`}
open={Boolean(cancelSharingPartOrder)}
onCancel={() => {
setCancelSharingPartOrder(null)
cancelSharingPartForm.resetFields()
}}
onOk={() => cancelSharingPartForm.submit()}
okText="确认取消"
okButtonProps={{ danger: true }}
destroyOnHidden
>
{cancelSharingPartOrder ? (
<Form form={cancelSharingPartForm} layout="vertical" onFinish={submitCancelSharingPart}>
<Alert
showIcon
type="warning"
message="只保留已验收份额,其余份数全部取消"
description={
cancelSharingPartAcceptedQuantity > 0
? '取消后母单总量收敛为已验收份数,并直接转为已验收;取消的份额会退还押金。'
: '本单没有已验收份额,提交后整张订单会被取消;取消的份额会退还押金。'
}
style={{ marginBottom: 16 }}
/>
<Descriptions size="small" column={1} bordered style={{ marginBottom: 16 }}>
<Descriptions.Item label="当前总量">
{cancelSharingPartTotalQuantity} 份
</Descriptions.Item>
<Descriptions.Item label="已验收(保留)">
{cancelSharingPartAcceptedQuantity} 份
</Descriptions.Item>
<Descriptions.Item label="将取消">
{cancelSharingPartCancelledQuantity} 份(进行中 + 待接单)
</Descriptions.Item>
</Descriptions>
<Form.Item
label="退款金额"
name="refundAmount"
extra="默认按订单实付金额 × 取消份数 / 原总量计算,可手工修改;本系统只登记金额。"
>
<InputNumber min={0} precision={2} addonAfter="元" style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="取消原因"
name="reason"
rules={[{ required: true, whitespace: true, message: '请填写取消原因' }]}
>
<Input.TextArea rows={3} placeholder="如:剩余份数不再需要,按实付比例退款" />
</Form.Item>
</Form>
) : null}
</Modal>
{/* 内部寄售库存匹配拦截弹窗 */}
<Modal
title={
@@ -942,6 +942,24 @@ export function cancelAdminWorkOrderSharing(
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/sharing/cancel`, payload)
}
/** 取消部分:保留已验收份额,取消进行中份额与待接单剩余份数,退还取消份额的押金。 */
export function cancelAdminWorkOrderSharingPart(
workOrderId: number,
payload: {
/** 退款金额(元);不传则后端按实付比例算 */
refundAmount?: number
reason: string
},
) {
return apiPost<{
order: WorkOrder
cancelledShareCount: number
cancelledQuantity: number
releasedDepositAmount: number
refundAmount: number
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/sharing/cancel-part`, payload)
}
export function acceptAdminWorkOrders(workOrderIds: number[]) {
return apiPost<{ orders: WorkOrder[] }>('/api/v1/admin/worker-platform/orders/accept-batch', {
workOrderIds,
@@ -648,6 +648,8 @@ export type WorkOrder = {
canPublish: boolean
canCancel: boolean
canCancelSharing?: boolean
/** 取消部分:保留已验收份额,取消进行中份额与待接单剩余份数。 */
canCancelSharingPart?: boolean
canResolveProblem: boolean
canDeduct: boolean
canConfirmFriend?: boolean
@@ -655,6 +657,8 @@ export type WorkOrder = {
canResetGiftFlow?: boolean
}
rewardAmount: number
/** 来源订单实付金额(分):取消部分时用于预填退款金额。 */
sourceOrderAmount?: number
/** 当前打手生效的免押额度,仅打手端订单数据返回。 */
depositFreeAmount?: number
requiredDepositAmount?: number
@@ -668,6 +672,10 @@ export type WorkOrder = {
joinedQuantity: number
pendingSubmissionCount: number
remainingQuantity?: number
/** 已验收份额的份数合计 */
acceptedQuantity?: number
/** 可取消的未验收份数 = 进行中 + 待接单 */
cancellableQuantity?: number
filled?: boolean
}
pendingUnfreezeAmount?: number