feat(consignment): 内部现货履约改为停在「待加好友」,由客服确认后进赠送冷却
需求:发布匹配成功后订单要停在待加好友,客服线下通知加好友、手动确认后才进赠送冷却。 - 新增 parkWorkOrderWaitingFriendAddWithClient:占库落地时把工单停在 acceptance_mode=friend_gift + gift_phase=material_required 的「待加好友」阶段, friend_added_at / gift_available_at / deadline_at 都留空(没有打手在跑,不该背时限), 并把商品规则里的冷却时长落到工单上(两者都为 0 时补默认 72 小时, 否则客服确认后会得到 0 秒冷却而立刻可赠送)。 - 履约服务不再传 forceFriendGift(该开关会直接把 friend_added_at 写成当前时间、 跳过客服确认);同步删除这个已无调用方的开关,冷却起始事件里的 skippedWorkerMaterial 改为按是否内部现货履约判断。 - 前端:内部现货单在该阶段显示「待加好友」(原为「待打手提交资料」), 确认按钮统一为「确认加好友」,确认弹窗补上"先用库存账号加好友、通知进展记备注"的引导。 - 后台备注增加快捷项「已通知加好友」,一键追加「[已通知加好友] 操作人 时间」。 - 阶段文案抽到 utils/gift-phase.ts 以便单测覆盖这条口径。 验证:真实库跑通全链路(停阶段 → 确认加好友进冷却 → 重复确认被拒 → 事件流正确,临时单已清理); 后端 415 例(412 通过,新增 3 例)、前端 25 例(新增 4 例)全绿。
This commit is contained in:
@@ -85,7 +85,7 @@ import type { UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-pla
|
||||
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
|
||||
import dayjs, { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import { hasAdminPermission, hasAdminRole } from '@/utils/admin-auth'
|
||||
import { getAdminUsername, hasAdminPermission, hasAdminRole } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
import {
|
||||
DeadlineCountdown,
|
||||
@@ -122,6 +122,12 @@ import {
|
||||
shouldSkipPublishConfirm,
|
||||
} from './work-order-panel-utils'
|
||||
|
||||
/**
|
||||
* 客服备注快捷项。点击后往备注里追加一行「[标签] 操作人 时间」,
|
||||
* 保留手动补充的空间,同时让"什么时候通知的、谁通知的"自动留痕。
|
||||
*/
|
||||
const ADMIN_NOTE_QUICK_TAGS = ['已通知加好友'] as const
|
||||
|
||||
const FILTERABLE_WORK_ORDER_STATUSES = [
|
||||
'pending_material',
|
||||
'unassigned',
|
||||
@@ -994,6 +1000,12 @@ export default function WorkOrdersPanel() {
|
||||
noteForm.setFieldsValue({ note: row.adminNote || '' })
|
||||
}
|
||||
|
||||
function appendNoteQuickTag(tag: string) {
|
||||
const current = String(noteForm.getFieldValue('note') || '')
|
||||
const stamp = `[${tag}] ${getAdminUsername() || '客服'} ${formatAdminDateTime(new Date().toISOString())}`
|
||||
noteForm.setFieldValue('note', current.trim() ? `${current.trimEnd()}\n${stamp}` : stamp)
|
||||
}
|
||||
|
||||
async function submitNote(values: { note?: string }) {
|
||||
if (!noteOrder) return
|
||||
const succeeded = await runAction(
|
||||
@@ -1106,14 +1118,17 @@ export default function WorkOrdersPanel() {
|
||||
const cooldownLabel = cooldownMinutes > 0 ? `${cooldownMinutes} 分钟` : `${cooldownHours} 小时`
|
||||
const internal = isInternalConsignmentOrder(row)
|
||||
modal.confirm({
|
||||
title: internal ? '跳过打手资料,开始赠送冷却?' : '确认已添加打手为游戏好友?',
|
||||
title: internal ? '确认已加好友,开始赠送冷却?' : '确认已添加打手为游戏好友?',
|
||||
content: (
|
||||
<div>
|
||||
{internal ? (
|
||||
<>
|
||||
<Typography.Paragraph>
|
||||
内部寄售履约没有打手交资料。确认后直接开始 {cooldownLabel}
|
||||
赠送冷却,冷却结束后再用库存账号给买家赠送并验收。
|
||||
内部寄售履约单没有打手资料。订单会一直停在「待加好友」,请先用库存账号添加买家为游戏好友;
|
||||
通知进展可以记在订单备注里(备注支持一键填写「已通知加好友」)。
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph>
|
||||
确认后开始 {cooldownLabel} 赠送冷却,冷却结束后再用库存账号赠送并验收。
|
||||
</Typography.Paragraph>
|
||||
</>
|
||||
) : (
|
||||
@@ -1129,13 +1144,13 @@ export default function WorkOrdersPanel() {
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
okText: internal ? '开始冷却' : '已加好友,开始冷却',
|
||||
okText: internal ? '确认加好友,开始冷却' : '已加好友,开始冷却',
|
||||
cancelText: '取消',
|
||||
onOk: () =>
|
||||
runAction(
|
||||
() => confirmAdminWorkOrderFriend(row.workOrderId),
|
||||
internal
|
||||
? `已跳过打手资料,${cooldownLabel} 冷却开始`
|
||||
? `已确认加好友,${cooldownLabel} 冷却开始`
|
||||
: `已确认加好友,${cooldownLabel} 冷却开始`,
|
||||
),
|
||||
})
|
||||
@@ -1326,7 +1341,7 @@ export default function WorkOrdersPanel() {
|
||||
(!['pending_material', 'unassigned', 'open'].includes(row.rawStatus || row.status) ||
|
||||
isInternalConsignmentOrder(row)) ? (
|
||||
<Tag color={resolveGiftPhaseColor(row.gift.phase)}>
|
||||
{formatGiftPhase(row.gift.phase)}
|
||||
{formatGiftPhase(row.gift.phase, isInternalConsignmentOrder(row))}
|
||||
</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
@@ -1546,9 +1561,7 @@ export default function WorkOrdersPanel() {
|
||||
icon={<UserAddOutlined />}
|
||||
onClick={() => confirmFriendAdded(row)}
|
||||
>
|
||||
{isInternalConsignmentOrder(row) && row.gift?.phase === 'material_required'
|
||||
? '开始赠送冷却'
|
||||
: '确认加好友'}
|
||||
确认加好友
|
||||
</Button>
|
||||
) : null}
|
||||
{row.capabilities?.canCompleteGiftCooldown && canManageProblem ? (
|
||||
@@ -1954,7 +1967,10 @@ export default function WorkOrdersPanel() {
|
||||
<Space>
|
||||
<Tag color="purple">{formatAcceptanceMode(detailOrder.acceptanceMode)}</Tag>
|
||||
<Tag color={resolveGiftPhaseColor(detailOrder.gift.phase)}>
|
||||
{formatGiftPhase(detailOrder.gift.phase)}
|
||||
{formatGiftPhase(
|
||||
detailOrder.gift.phase,
|
||||
isInternalConsignmentOrder(detailOrder),
|
||||
)}
|
||||
</Tag>
|
||||
{detailOrder.capabilities?.canConfirmFriend && canManageProblem ? (
|
||||
<Button
|
||||
@@ -1962,10 +1978,7 @@ export default function WorkOrdersPanel() {
|
||||
size="small"
|
||||
onClick={() => confirmFriendAdded(detailOrder)}
|
||||
>
|
||||
{isInternalConsignmentOrder(detailOrder) &&
|
||||
detailOrder.gift?.phase === 'material_required'
|
||||
? '开始赠送冷却'
|
||||
: '确认加好友'}
|
||||
确认加好友
|
||||
</Button>
|
||||
) : null}
|
||||
{detailOrder.capabilities?.canCompleteGiftCooldown && canManageProblem ? (
|
||||
@@ -2137,6 +2150,14 @@ export default function WorkOrdersPanel() {
|
||||
placeholder="请输入本单客服备注"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Space size={8} wrap style={{ marginTop: -8 }}>
|
||||
<Typography.Text type="secondary">快捷填写:</Typography.Text>
|
||||
{ADMIN_NOTE_QUICK_TAGS.map((tag) => (
|
||||
<Button key={tag} size="small" onClick={() => appendNoteQuickTag(tag)}>
|
||||
{tag}
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
</Form>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
@@ -249,25 +249,8 @@ export function formatAcceptanceMode(mode: string) {
|
||||
return '标准验收'
|
||||
}
|
||||
|
||||
export function formatGiftPhase(phase: string) {
|
||||
const labels: Record<string, string> = {
|
||||
material_required: '待打手提交资料',
|
||||
material_submitted: '待确认加好友',
|
||||
friend_countdown: '赠送冷却中',
|
||||
gift_ready: '可赠送',
|
||||
}
|
||||
return labels[phase] || phase
|
||||
}
|
||||
|
||||
export function resolveGiftPhaseColor(phase: string) {
|
||||
const colors: Record<string, string> = {
|
||||
material_required: 'orange',
|
||||
material_submitted: 'geekblue',
|
||||
friend_countdown: 'gold',
|
||||
gift_ready: 'green',
|
||||
}
|
||||
return colors[phase] || 'default'
|
||||
}
|
||||
// 阶段文案与配色在 utils 里实现,便于单测覆盖「内部现货单显示待加好友」这条口径。
|
||||
export { formatGiftPhase, resolveGiftPhaseColor } from '@/utils/gift-phase'
|
||||
|
||||
export function resolveSharingShareStatusColor(status: string) {
|
||||
if (status === 'accepted') return 'green'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 好友赠送阶段文案与配色。
|
||||
*
|
||||
* 注意 material_required 在不同履约方式下含义不同:
|
||||
* - 打手履约单:等打手提交好友资料,显示「待打手提交资料」;
|
||||
* - 内部现货履约单:没有打手资料这一步,占库成功后停在这里等客服线下通知加好友并手动确认,
|
||||
* 所以要显示「待加好友」。
|
||||
*/
|
||||
export function formatGiftPhase(phase: string, internalConsignment = false) {
|
||||
if (internalConsignment && phase === 'material_required') return '待加好友'
|
||||
const labels: Record<string, string> = {
|
||||
material_required: '待打手提交资料',
|
||||
material_submitted: '待确认加好友',
|
||||
friend_countdown: '赠送冷却中',
|
||||
gift_ready: '可赠送',
|
||||
}
|
||||
return labels[phase] || phase
|
||||
}
|
||||
|
||||
export function resolveGiftPhaseColor(phase: string) {
|
||||
const colors: Record<string, string> = {
|
||||
material_required: 'orange',
|
||||
material_submitted: 'geekblue',
|
||||
friend_countdown: 'gold',
|
||||
gift_ready: 'green',
|
||||
}
|
||||
return colors[phase] || 'default'
|
||||
}
|
||||
Reference in New Issue
Block a user