fix(consignment): 内部现货履约文案对齐待加好友,并移除已废弃的指派打手路径
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -76,7 +76,6 @@ import {
|
||||
updateAdminWorkOrderSharing,
|
||||
} from '@/services/admin'
|
||||
import {
|
||||
assignWorkOrderWithConsignmentWorker,
|
||||
checkWorkOrderConsignmentMatches,
|
||||
fulfillWorkOrderWithConsignment,
|
||||
revokeWorkOrderConsignmentFulfillment,
|
||||
@@ -85,6 +84,7 @@ import { notifyVoucherConsume } from '@/utils/voucher-consume'
|
||||
import type { ConsignmentFulfillmentInfo, WorkOrderInventoryMatchCheck } from '@/types/consignment'
|
||||
import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform'
|
||||
import { isManuallyMatchableOrderStatus } from '@/utils/consignment-manual-match'
|
||||
import { resolveConsignmentScreenshotUrls } from '@/utils/consignment-display'
|
||||
import ConsignmentManualMatchModal from '@/components/admin/ConsignmentManualMatchModal'
|
||||
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
|
||||
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
|
||||
@@ -798,26 +798,21 @@ export default function WorkOrdersPanel() {
|
||||
|
||||
/** 非精确匹配(订单规格信息不全 / 高配充低配)必须人工确认,后端也会二次拦截 */
|
||||
/**
|
||||
* 匹配弹窗里的两个动作**都会立刻占用现货**,点错就得走「撤销寄售履约」才能退回来,
|
||||
* 而且两个按钮挨在一起很容易点错,所以一律二次确认,并把后果写清楚:
|
||||
* 用哪个号占用、给谁、要不要冻结押金、能不能回退。
|
||||
* 内部现货履约会立刻占用库存,点错就得走「撤销寄售履约」才能退回来,
|
||||
* 所以二次确认写清楚:用哪个号占用、结算价、工单会停在待加好友。
|
||||
*/
|
||||
function confirmConsignmentMatchAction(input: {
|
||||
kind: 'assign_worker' | 'internal_stock'
|
||||
matchType?: string
|
||||
consignmentNo: string
|
||||
workerName?: string
|
||||
depositFen?: number | null
|
||||
costPriceFen?: number | null
|
||||
onOk: () => void
|
||||
}) {
|
||||
const isPartial = input.matchType !== 'exact'
|
||||
const isAssignWorker = input.kind === 'assign_worker'
|
||||
const depositText =
|
||||
input.depositFen != null ? `履约押金 ¥${(input.depositFen / 100).toFixed(2)}` : '履约押金'
|
||||
const costText =
|
||||
input.costPriceFen != null ? `结算价 ¥${(input.costPriceFen / 100).toFixed(2)}` : ''
|
||||
|
||||
modal.confirm({
|
||||
title: isAssignWorker ? '确认走接单平台指派打手?' : '确认用内部现货履约?',
|
||||
title: '确认用内部现货履约?',
|
||||
width: 460,
|
||||
content: (
|
||||
<div style={{ fontSize: 13, lineHeight: 1.8 }}>
|
||||
@@ -828,14 +823,11 @@ export default function WorkOrdersPanel() {
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
{isAssignWorker
|
||||
? `动作:把订单指派给供货打手${input.workerName ? `「${input.workerName}」` : ''}接单,需冻结${depositText}`
|
||||
: '动作:不向外部打手派单,直接用这件现货履约'}
|
||||
动作:不向外部打手派单,直接用这件现货履约
|
||||
{costText ? `(${costText})` : ''}
|
||||
</div>
|
||||
<div>
|
||||
{isAssignWorker
|
||||
? '结果:立刻占用该现货,订单转入打手履约,其它订单不能再匹配它。'
|
||||
: '结果:立刻占用该现货,跳过「待打手提交资料」直接进入赠送冷却期,其它订单不能再匹配它。'}
|
||||
结果:立刻占用该现货,跳过「待打手提交资料」,工单进入「待加好友」;加好后再点确认加好友才会进入赠送冷却期。其它订单不能再匹配它。
|
||||
</div>
|
||||
<div style={{ color: '#8c8c8c' }}>
|
||||
退回需要走「撤销寄售履约」,请先核对商品与买家订单一致。
|
||||
@@ -848,7 +840,7 @@ export default function WorkOrdersPanel() {
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
okText: isAssignWorker ? '确认指派打手' : '确认占用并履约',
|
||||
okText: '确认占用并履约',
|
||||
okButtonProps: isPartial ? { danger: true } : undefined,
|
||||
cancelText: '再想想',
|
||||
onOk: input.onOk,
|
||||
@@ -864,7 +856,7 @@ export default function WorkOrdersPanel() {
|
||||
inventoryId,
|
||||
allowPartial,
|
||||
})
|
||||
message.success('已占用内部库存,跳过打手资料,进入赠送冷静期')
|
||||
message.success('已占用内部库存,跳过打手资料,已进入待加好友阶段')
|
||||
notifyVoucherConsume(res?.data?.voucherConsume)
|
||||
setConsignmentInterceptModal({ open: false, order: null, matchData: null, submitting: false })
|
||||
await refreshAll()
|
||||
@@ -874,30 +866,6 @@ export default function WorkOrdersPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAssignWithConsignmentWorker(
|
||||
inventoryId: number,
|
||||
workerId?: number | null,
|
||||
allowPartial = false,
|
||||
) {
|
||||
if (!consignmentInterceptModal.order) return
|
||||
setConsignmentInterceptModal((prev) => ({ ...prev, submitting: true }))
|
||||
try {
|
||||
const res = await assignWorkOrderWithConsignmentWorker({
|
||||
workOrderId: consignmentInterceptModal.order.workOrderId,
|
||||
inventoryId,
|
||||
workerId,
|
||||
allowPartial,
|
||||
})
|
||||
message.success('已通过接单平台指派打手!系统已自动冻结履约押金,等待打手交货核销')
|
||||
notifyVoucherConsume(res?.data?.voucherConsume)
|
||||
setConsignmentInterceptModal({ open: false, order: null, matchData: null, submitting: false })
|
||||
await refreshAll()
|
||||
} catch (err: any) {
|
||||
message.error(err.message || '走接单平台指派打手失败')
|
||||
setConsignmentInterceptModal((prev) => ({ ...prev, submitting: false }))
|
||||
}
|
||||
}
|
||||
|
||||
function openRevokeConsignmentModal(row: WorkOrder) {
|
||||
setRevokeConsignmentModal({
|
||||
open: true,
|
||||
@@ -2207,6 +2175,9 @@ export default function WorkOrdersPanel() {
|
||||
const effectiveNote = String(
|
||||
detailOrder.gift?.boosterMaterial?.note || consignmentInfo?.remark || '',
|
||||
).trim()
|
||||
const consignmentScreenshotUrls = resolveConsignmentScreenshotUrls(
|
||||
consignmentInfo?.screenshots,
|
||||
)
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -2414,14 +2385,8 @@ export default function WorkOrdersPanel() {
|
||||
files={detailOrder.gift.boosterMaterial.screenshots}
|
||||
size={72}
|
||||
/>
|
||||
) : Array.isArray(consignmentInfo?.screenshots) &&
|
||||
consignmentInfo.screenshots.length > 0 ? (
|
||||
<ImagePreviewList
|
||||
imageUrls={(consignmentInfo.screenshots as string[]).filter(
|
||||
(item) => typeof item === 'string' && item.trim(),
|
||||
)}
|
||||
size={72}
|
||||
/>
|
||||
) : consignmentScreenshotUrls.length > 0 ? (
|
||||
<ImagePreviewList imageUrls={consignmentScreenshotUrls} size={72} />
|
||||
) : (
|
||||
<Typography.Text type="secondary">未提交</Typography.Text>
|
||||
)}
|
||||
@@ -3329,8 +3294,7 @@ export default function WorkOrdersPanel() {
|
||||
</div>
|
||||
<div style={{ color: '#389e0d', marginTop: 4 }}>
|
||||
若使用内部寄售库存履约,<strong>无需向外部打手支付佣金(打手支出 ¥0.00)</strong>
|
||||
。系统会立刻占用库存,并跳过「待打手提交资料」,直接进入该商品规则设定的赠送冷静期(未单独配置时为
|
||||
72 小时);冷静期结束后再用库存账号赠送并验收。
|
||||
。系统会立刻占用库存,并跳过「待打手提交资料」,工单进入「待加好友」。加好买家好友后再点确认加好友,才会进入赠送冷却期。
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -3446,7 +3410,6 @@ export default function WorkOrdersPanel() {
|
||||
loading={consignmentInterceptModal.submitting}
|
||||
onClick={() =>
|
||||
confirmConsignmentMatchAction({
|
||||
kind: 'internal_stock',
|
||||
matchType: m.matchType,
|
||||
consignmentNo: inv.sourceSubmissionNo || '客服手工入库',
|
||||
costPriceFen: inv.costPriceFen,
|
||||
@@ -3466,7 +3429,7 @@ export default function WorkOrdersPanel() {
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
免外派佣金 · 不扣打手押金 · 自动进冷静期
|
||||
免外派佣金 · 不扣打手押金 · 进入待加好友
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import WorkOrderEventTimeline from '@/components/WorkOrderEventTimeline'
|
||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||
import type { WorkOrder } from '@/types/worker-platform'
|
||||
import { formatDateTime } from '@/utils/date-time'
|
||||
import { resolveOrderConsignmentScreenshotUrls } from '@/utils/consignment-display'
|
||||
|
||||
import {
|
||||
canEditAcceptanceImages,
|
||||
@@ -183,6 +184,9 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
|
||||
const boosterItems = Object.entries(boosterFields).filter(([, value]) =>
|
||||
String(value || '').trim(),
|
||||
)
|
||||
const consignmentScreenshotUrls = resolveOrderConsignmentScreenshotUrls(order)
|
||||
const hasBoosterScreenshots = (gift.boosterMaterial?.screenshots || []).length > 0
|
||||
const hasConsignmentScreenshots = consignmentScreenshotUrls.length > 0
|
||||
|
||||
return (
|
||||
<Card title="好友赠送进度" size="small">
|
||||
@@ -228,7 +232,7 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{boosterItems.length > 0 || (gift.boosterMaterial?.screenshots || []).length > 0 ? (
|
||||
{boosterItems.length > 0 || hasBoosterScreenshots || hasConsignmentScreenshots ? (
|
||||
<div>
|
||||
<Typography.Text type="secondary">已提交的本人资料</Typography.Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
@@ -241,20 +245,13 @@ function GiftProgressCard({ order }: { order: WorkOrder }) {
|
||||
))}
|
||||
</Descriptions>
|
||||
) : null}
|
||||
{(gift.boosterMaterial?.screenshots || []).length > 0 ? (
|
||||
{hasBoosterScreenshots ? (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<ImagePreviewList files={gift.boosterMaterial.screenshots} size={64} />
|
||||
<ImagePreviewList files={gift.boosterMaterial?.screenshots} size={64} />
|
||||
</div>
|
||||
) : Array.isArray(
|
||||
(order.material as Record<string, any> | undefined)?.consignment?.screenshots,
|
||||
) &&
|
||||
((order.material as Record<string, any>).consignment.screenshots as unknown[])
|
||||
.length > 0 ? (
|
||||
) : hasConsignmentScreenshots ? (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<ImagePreviewList
|
||||
imageUrls={(order.material as Record<string, any>).consignment.screenshots}
|
||||
size={64}
|
||||
/>
|
||||
<ImagePreviewList imageUrls={consignmentScreenshotUrls} size={64} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,27 @@ function asRecord(value: unknown): Record<string, unknown> {
|
||||
: {}
|
||||
}
|
||||
|
||||
/** 寄售截图可能是字符串 URL,也可能是 UploadedFile 对象。 */
|
||||
export function resolveConsignmentScreenshotUrls(screenshots: unknown): string[] {
|
||||
if (!Array.isArray(screenshots)) return []
|
||||
return screenshots
|
||||
.map((item) => {
|
||||
if (typeof item === 'string') return item.trim()
|
||||
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||
const rec = item as Record<string, unknown>
|
||||
return String(rec.url || rec.mediumUrl || rec.thumbnailUrl || '').trim()
|
||||
}
|
||||
return ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function resolveOrderConsignmentScreenshotUrls(order: { material?: unknown }): string[] {
|
||||
return resolveConsignmentScreenshotUrls(
|
||||
asRecord(asRecord(order.material).consignment).screenshots,
|
||||
)
|
||||
}
|
||||
|
||||
export type ConsignmentWorkerDisplayInfo = {
|
||||
tagText: '供货打手' | '内部现货'
|
||||
primaryName: string
|
||||
|
||||
@@ -100,3 +100,18 @@ test('手动匹配弹窗:数据过期的 409 要清选择并刷新,走内部
|
||||
assert.ok(source.includes("from '@/utils/voucher-consume'"), '核销反馈必须用共享出口')
|
||||
assert.ok(source.includes('keyword: keyword.trim() || undefined'), '库存侧关键字要透传后端')
|
||||
})
|
||||
|
||||
test('发布拦截弹窗:只走内部现货履约,文案停在待加好友而不是冷静期', () => {
|
||||
const source = readFileSync(
|
||||
new URL('../src/pages/admin/panels/WorkOrdersPanel.tsx', import.meta.url).pathname,
|
||||
'utf8',
|
||||
)
|
||||
assert.ok(source.includes('fulfillWorkOrderWithConsignment'), '拦截弹窗必须调用内部现货履约')
|
||||
assert.ok(
|
||||
!source.includes('assignWorkOrderWithConsignmentWorker'),
|
||||
'拦截弹窗不得再调用旧的指派打手接口',
|
||||
)
|
||||
assert.ok(source.includes('进入待加好友'), '拦截弹窗必须写明工单进入待加好友')
|
||||
assert.ok(!source.includes('自动进冷静期'), '拦截弹窗不得再写自动进冷静期')
|
||||
assert.ok(!source.includes('进入赠送冷静期'), '成功提示不得再写成进入赠送冷静期')
|
||||
})
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { resolveConsignmentWorkerDisplay } from '../src/utils/consignment-display.ts'
|
||||
import {
|
||||
resolveConsignmentWorkerDisplay,
|
||||
resolveConsignmentScreenshotUrls,
|
||||
} from '../src/utils/consignment-display.ts'
|
||||
|
||||
test('普通工单无寄售信息时 resolveConsignmentWorkerDisplay 返回 null', () => {
|
||||
const order = {
|
||||
@@ -53,3 +56,18 @@ test('寄售工单无供货打手名时回退展示角色昵称与现货标签',
|
||||
assert.equal(result.primaryName, '客服角色')
|
||||
assert.equal(result.secondaryInfo, 'ID:1234551')
|
||||
})
|
||||
|
||||
test('寄售截图兼容字符串 URL 与 UploadedFile 对象', () => {
|
||||
assert.deepEqual(
|
||||
resolveConsignmentScreenshotUrls([
|
||||
'/api/v1/files/object?key=consignment_screenshot%2Fa.webp',
|
||||
{ url: '/api/v1/files/object?key=consignment_screenshot%2Fb.webp' },
|
||||
{ objectKey: 'x', url: '' },
|
||||
12,
|
||||
]),
|
||||
[
|
||||
'/api/v1/files/object?key=consignment_screenshot%2Fa.webp',
|
||||
'/api/v1/files/object?key=consignment_screenshot%2Fb.webp',
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
- 彻底解除 `selected.workerId` 强依赖与无法指派错误提示,详情卡片调整为展示供货来源(“供货打手”或“内部现货”);
|
||||
- 文案统一调整为“确认内部现货履约”,明确告知“免外派佣金、不冻结重复押金、自动跳过打手资料”。
|
||||
2. **工单发布拦截弹窗候选操作收敛为【内部现货直接履约】**:
|
||||
- 在 `WorkOrdersPanel.tsx` 中将卡片操作按钮替换为【内部现货直接履约】高亮主按钮(绿色),副标题标注“免外派佣金 · 不扣打手押金 · 自动进冷静期”;
|
||||
- 在 `WorkOrdersPanel.tsx` 中将卡片操作按钮替换为【内部现货直接履约】高亮主按钮(绿色),副标题标注“免外派佣金 · 不扣打手押金 · 进入待加好友”;
|
||||
- 杜绝客服误点走旧指派流程。
|
||||
3. **测试套件与类型风格全面达标**:
|
||||
- `consignment-manual-match-mount.test.ts` 更新为断言手动匹配走内部现货直接履约;
|
||||
|
||||
Reference in New Issue
Block a user