fix(admin): integrate workbench contracts and dropship review states

This commit is contained in:
2026-09-09 11:50:42 +08:00
79 changed files with 2288 additions and 448 deletions
@@ -258,7 +258,7 @@ async function batchApproveMeasurements() {
batchReviewLoading.value = true
try {
await ElMessageBox.confirm(
`确认批量审核通过已勾选的 ${selected.length} 条测量数据吗?通过后将进入待分柜。`,
`确认批量审核通过已勾选的 ${selected.length} 条测量数据吗?一件代发通过后进入待组托,其他订单进入待分柜。`,
'批量审核测量数据',
{ confirmButtonText: '确认批量审核', cancelButtonText: '取消', type: 'warning' },
)
@@ -426,6 +426,7 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
v-if="selectedInbound"
:tenant-id="selectedInbound.tenantId"
:inbound-id="selectedInbound.id"
:fulfillment-type="selectedInbound.fulfillmentType"
:customer-name="selectedInbound.customerName || selectedInbound.companyName"
:channel-name="selectedInbound.channelName"
:channel-code="selectedInbound.channelCode"
@@ -218,6 +218,7 @@ onBeforeUnmount(() => {
:key="`review-dialog-${revision}`"
:tenant-id="customerId"
:inbound-id="inboundId"
:fulfillment-type="detail?.summary.fulfillmentType"
:customer-name="detail?.summary.customerName || detail?.summary.companyName"
:channel-name="detail?.summary.channelName"
:channel-code="detail?.summary.channelCode"
+1
View File
@@ -8,6 +8,7 @@ export type InboundProcessStatus =
| 'WAIT_PRICING'
| 'FEE_CONFIRMING'
| 'INBOUNDING'
| 'WAIT_PALLETIZE'
| 'WAIT_SPLIT'
| 'VOIDED'
@@ -1,4 +1,4 @@
import ElementPlus, { ElMessageBox } from 'element-plus'
import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -92,6 +92,22 @@ describe('MeasurementReviewPanel', () => {
expect(source).not.toContain('listMeasurementReviewHistory')
})
it.each([true, false])('describes dropship palletizing and uses the server result when pricing succeeds=%s', async (initialPricingSucceeded) => {
vi.mocked(approveMeasurement).mockResolvedValue({ inboundId: '90001', measurementId: '70001', processStatus: 'WAIT_PALLETIZE', feeStatus: initialPricingSucceeded ? 'CALCULATED' : 'UNPRICED', initialPricingSucceeded, initialPricingMessage: null, initialFee: null })
const confirmation = vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
const success = vi.spyOn(ElMessage, 'success')
const warning = vi.spyOn(ElMessage, 'warning')
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001', fulfillmentType: 'ONE_CLICK_DROPSHIP' }, global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.text()).not.toContain('通过并进入待分柜')
await wrapper.findAll('button').find((button) => button.text().includes('通过并进入待一件代发组托'))!.trigger('click')
await flushPromises()
expect(confirmation).toHaveBeenCalledWith(expect.stringContaining('进入待一件代发组托'), '确认测量审核通过', expect.objectContaining({ confirmButtonText: '通过并进入待一件代发组托' }))
expect(initialPricingSucceeded ? success : warning).toHaveBeenCalledWith(expect.stringContaining('入库单已进入待一件代发组托'))
expect(approveMeasurement).toHaveBeenCalledTimes(1)
expect(wrapper.emitted('changed')).toHaveLength(1)
})
it('uses the list customer context when the review detail has no customer name', async () => {
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({ ...detail, companyName: null })
const wrapper = mount(MeasurementReviewPanel, {
@@ -8,7 +8,7 @@ import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import { downloadInboundMeasurementImage } from '../measurement/api'
import MeasurementEvidenceThumbnail from '../measurement/components/MeasurementEvidenceThumbnail.vue'
import { inboundReviewStatusLabels, inboundStatusLabel } from '../status'
import { inboundProcessStatusLabels, inboundReviewStatusLabels, inboundStatusLabel } from '../status'
import WeightOcrPanel from '../weight/components/WeightOcrPanel.vue'
import { approveMeasurement, getMeasurementReviewDetail } from './api'
import type { MeasurementReviewDetail } from './types'
@@ -18,6 +18,7 @@ const props = defineProps<{
/** @deprecated 仅用于兼容尚未完成 customerId 映射的上层集成。 */
tenantId?: string
inboundId: string
fulfillmentType?: string | null
customerName?: string | null
channelName?: string | null
channelCode?: string | null
@@ -44,6 +45,8 @@ let imagePreviewRequestSerial = 0
const canReview = computed(() => access.can('firstleg:inbound:measurement-review'))
const customerId = computed(() => props.customerId || props.tenantId || '')
const isPending = computed(() => detail.value?.measureReviewStatus === 'PENDING' && detail.value?.processStatus === 'MEASURE_PENDING')
const approvalTargetLabel = computed(() => props.fulfillmentType === 'ONE_CLICK_DROPSHIP' ? '待一件代发组托' : '待分柜')
const approvalButtonLabel = computed(() => `通过并进入${approvalTargetLabel.value}`)
const isRejected = computed(() => detail.value?.measureReviewStatus === 'REJECTED' && detail.value?.processStatus === 'MEASURE_REJECTED')
const reviewStatusLabel = computed(() => inboundStatusLabel(detail.value?.measureReviewStatus, inboundReviewStatusLabels, '未提交'))
const displayCustomerName = computed(() => detail.value?.companyName?.trim() || props.customerName?.trim() || '客户未命名')
@@ -134,9 +137,9 @@ async function confirmApprove() {
if (!detail.value || !isPending.value || !canReview.value || actionLoading.value || props.postApproveLoading || props.approvalCompleted) return
try {
await ElMessageBox.confirm(
'审核通过后,入库单将进入待分柜。系统会尝试生成初算费用,但初算失败不影响审核通过,后续可由平台人员补充调整。是否继续?',
`审核通过后,入库单将进入${approvalTargetLabel.value}。系统会尝试生成初算费用,但初算失败不影响审核通过,后续可由平台人员补充调整。是否继续?`,
'确认测量审核通过',
{ confirmButtonText: '通过并进入待分柜', cancelButtonText: '返回', type: 'warning' },
{ confirmButtonText: approvalButtonLabel.value, cancelButtonText: '返回', type: 'warning' },
)
} catch {
return
@@ -147,8 +150,9 @@ async function confirmApprove() {
measurementId: detail.value.measurementId,
expectedVersion: detail.value.inboundVersion,
})
if (result.initialPricingSucceeded) ElMessage.success('审核已通过,入库单已进入待分柜,费用已完成初算')
else ElMessage.warning(`审核已通过,入库单已进入待分柜;费用初算未完成${result.initialPricingMessage ? `:${result.initialPricingMessage}` : ''}`)
const resultStatusLabel = inboundStatusLabel(result.processStatus, inboundProcessStatusLabels)
if (result.initialPricingSucceeded) ElMessage.success(`审核已通过,入库单已进入${resultStatusLabel},费用已完成初算`)
else ElMessage.warning(`审核已通过,入库单已进入${resultStatusLabel};费用初算未完成${result.initialPricingMessage ? `:${result.initialPricingMessage}` : ''}`)
emit('changed')
} catch (error) {
ElMessage.error(isConflict(error) ? '数据已被更新,请刷新后重新审核' : getApiErrorDetails(error, '审核通过失败').message)
@@ -184,7 +188,7 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
</div>
<div class="review-toolbar__actions">
<el-button v-if="isPending && canReview" type="primary" plain @click="correctionMode = true"><Pencil :size="16" />修改重量</el-button>
<el-button v-if="isPending && canReview" type="primary" :loading="actionLoading === 'approve' || postApproveLoading" :disabled="approvalCompleted" @click="confirmApprove"><Check :size="16" />{{ approvalCompleted ? '审核已通过' : '通过并进入待分柜' }}</el-button>
<el-button v-if="isPending && canReview" type="primary" :loading="actionLoading === 'approve' || postApproveLoading" :disabled="approvalCompleted" @click="confirmApprove"><Check :size="16" />{{ approvalCompleted ? '审核已通过' : approvalButtonLabel }}</el-button>
<el-button :loading="loading" aria-label="刷新审核数据" @click="reload"><RotateCcw :size="16" />刷新</el-button>
</div>
</div>
+2 -2
View File
@@ -1,12 +1,12 @@
export const inboundProcessStatusLabels: Record<string, string> = {
WAIT_MEASURE: '待测量', MEASURING: '测量中', MEASURE_PENDING: '待测量审核', MEASURE_REJECTED: '测量已驳回',
WAIT_PRICING: '待计费', FEE_CONFIRMING: '费用处理中', INBOUNDING: '入库处理中', WAIT_PALLETIZE: '待一件代发合托', WAIT_SPLIT: '待分柜', VOIDED: '已作废',
WAIT_PRICING: '待计费', FEE_CONFIRMING: '费用处理中', INBOUNDING: '入库处理中', WAIT_PALLETIZE: '待一件代发组托', WAIT_SPLIT: '待分柜', VOIDED: '已作废',
}
export const inboundOrderStatusLabels: Record<string, string> = {
DRAFT: '草稿', SUBMITTED: '待审核', APPROVED: '已审核', ACCEPTED: '已受理', REJECTED: '已驳回',
PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消', PENDING_REVIEW: '待审核',
WAIT_MEASURE: '待测量', WAIT_PALLETIZE: '待一件代发合托', WAIT_SPLIT: '待分柜', LOADING: '装柜中',
WAIT_MEASURE: '待测量', WAIT_PALLETIZE: '待一件代发组托', WAIT_SPLIT: '待分柜', LOADING: '装柜中',
LOADED: '装柜完成', DELIVERED: '派送完成',
}