diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts index 4c0fea2e..25a6d45d 100644 --- a/src/modules/container-loading/api/platformContainerLoading.test.ts +++ b/src/modules/container-loading/api/platformContainerLoading.test.ts @@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { http } from '@/shared/api/http' import { - adjustSmartLoadingItem, assignPendingReviewSmartLoadingOrder, assignSmartLoadingGroup, assignSmartLoadingItem, getContainer, + adjustSmartLoadingItem, assignPendingReviewSmartLoadingOrder, assignSmartLoadingGroup, assignSmartLoadingItem, assignSmartLoadingPallet, getContainer, listContainers, listSmartLoadingCandidates, listSmartLoadingWorkbenchContainers, removeAllUnpickedSmartLoadingItems, removeSmartLoadingGroup, removeSmartLoadingItem, getContainerClearance, downloadContainerClearance, uploadContainerClearance, completeContainerClearance, getContainerOverview, markContainerException, cancelContainerException, uploadContainerDocument, downloadContainerDocument, downloadContainerPdaPhoto, @@ -91,6 +91,17 @@ describe('platform container loading API', () => { expect(vi.mocked(http.delete).mock.calls[0]?.[1]).toMatchObject({ data: { expectedPlanVersion: 9, expectedItemVersion: 10, operationId: expect.stringContaining('remove-') } }) }) + it('assigns a sealed pallet through the whole-pallet command', async () => { + vi.mocked(http.post).mockResolvedValue({ code: '200', data: plan }) + + await assignSmartLoadingPallet('10', { palletId: '9007199254740998', expectedVersion: 5 }) + + expect(http.post).toHaveBeenCalledWith( + '/platform/v1/first-leg/smart-loading/plans/10/pallet-assignments', + { palletId: '9007199254740998', expectedVersion: 5, operationId: expect.stringContaining('assign-pallet-') }, + ) + }) + it('uses the dedicated provisional assignment command for a pending-review order', async () => { vi.mocked(http.post).mockResolvedValue({ code: '200', data: plan }) diff --git a/src/modules/container-loading/api/platformContainerLoading.ts b/src/modules/container-loading/api/platformContainerLoading.ts index a9454c01..249e211a 100644 --- a/src/modules/container-loading/api/platformContainerLoading.ts +++ b/src/modules/container-loading/api/platformContainerLoading.ts @@ -101,6 +101,8 @@ function item(raw: SmartLoadingItem): SmartLoadingItem { id: id(raw.id), loadingPlanId: id(raw.loadingPlanId), accountId: id(raw.accountId), inboundId: id(raw.inboundId), inboundDetailId: id(raw.inboundDetailId), orderId: id(raw.orderId), destinationWarehouseId: optionalId(raw.destinationWarehouseId), version: numberValue(raw.version), + palletId: optionalId(raw.palletId), palletNo: raw.palletNo || null, + unitType: raw.unitType, indivisible: Boolean(raw.indivisible), packageCount: numberValue(raw.packageCount), pickedPackageCount: numberValue(raw.pickedPackageCount), minPackageCount: numberValue(raw.minPackageCount, 1), @@ -267,7 +269,8 @@ export async function listSmartLoadingCandidates(query: SmartLoadingCandidateQue return { ...raw, accountId, customerId: accountId, inboundId: optionalId(raw.inboundId), orderId: id(raw.orderId), warehouseId: id(raw.warehouseId), destinationWarehouseId: optionalId(raw.destinationWarehouseId), - coLoadGroupId: optionalId(raw.coLoadGroupId), + coLoadGroupId: optionalId(raw.coLoadGroupId), palletId: optionalId(raw.palletId), palletNo: raw.palletNo || null, + unitType: raw.unitType, indivisible: Boolean(raw.indivisible), memberCount: raw.memberCount == null ? null : numberValue(raw.memberCount), groupTotalBoxes: raw.groupTotalBoxes == null ? null : numberValue(raw.groupTotalBoxes), locked: raw.locked == null ? null : Boolean(raw.locked), @@ -317,6 +320,14 @@ export async function assignSmartLoadingItem(planId: string, payload: { inboundI return plan(unwrapApiResponse(response)) } +export async function assignSmartLoadingPallet(planId: string, payload: { palletId: string; expectedVersion: number }) { + const response = await http.post, ApiEnvelope>( + `${smartLoadingPath}/plans/${encodeURIComponent(planId)}/pallet-assignments`, + { ...payload, operationId: operationId('assign-pallet') }, + ) + return plan(unwrapApiResponse(response)) +} + export async function assignPendingReviewSmartLoadingOrder(planId: string, payload: { customerId: string; orderId: string; inboundCode: string; packageCount: number; expectedVersion: number }) { const response = await http.post, ApiEnvelope>( `${smartLoadingPath}/plans/${encodeURIComponent(planId)}/pending-review-assignments`, diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index 0dec60bd..0cc2bdd9 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -13,6 +13,7 @@ import { assignPendingReviewSmartLoadingOrder, assignSmartLoadingGroup, assignSmartLoadingItem, + assignSmartLoadingPallet, getSmartLoadingPlanByBooking, listContainers, listSmartLoadingCandidates, @@ -43,6 +44,7 @@ vi.mock('../api/platformContainerLoading', () => ({ listSmartLoadingWorkbenchContainers: vi.fn(), createSmartLoadingPlan: vi.fn(), assignSmartLoadingItem: vi.fn(), + assignSmartLoadingPallet: vi.fn(), assignPendingReviewSmartLoadingOrder: vi.fn(), assignSmartLoadingGroup: vi.fn(), recommendSmartLoading: vi.fn(), @@ -74,7 +76,7 @@ const plan = { canAssign: true, canEdit: true, blockedReason: null, finalCompletionStatus: 'NOT_STARTED' as const, pdaTaskId: null, pdaTaskStatus: null, pdaTaskVersion: null, coLoadGroups: [], - items: [{ id: '101', loadingPlanId: '99', accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', customerName: '示例企业', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', skuCode: 'SKU-1', productNameZh: '服装', packageCount: 6, productQuantity: 6, grossWeightKg: 60, volumeCbm: 1.2, loadSequence: 1, version: 1, pickedPackageCount: 0, minPackageCount: 1, canAdjust: true, canRemove: true }], + items: [{ id: '101', loadingPlanId: '99', accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', customerName: '示例企业', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', skuCode: 'SKU-1', productNameZh: '服装', unitType: 'PACKAGE' as const, palletId: null, palletNo: null, indivisible: false, packageCount: 6, productQuantity: 6, grossWeightKg: 60, volumeCbm: 1.2, loadSequence: 1, version: 1, pickedPackageCount: 0, minPackageCount: 1, canAdjust: true, canRemove: true }], } const candidate = { accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', @@ -89,7 +91,7 @@ function candidatePage(records: unknown[], page = 1, size = 20, total = records. return { records: records.map((record) => { const candidate = record as { accountId?: string; customerId?: string; groupMembers?: unknown[] } - return { customerId: candidate.customerId || candidate.accountId || '', groupMembers: [], ...candidate } + return { customerId: candidate.customerId || candidate.accountId || '', groupMembers: [], unitType: 'PACKAGE', palletId: null, palletNo: null, indivisible: false, ...candidate } }), page, size, total, pages: total ? Math.ceil(total / size) : 0, } as Awaited> @@ -116,7 +118,8 @@ describe('GlobalSmartLoadingView', () => { it('uses one-line shared text clamps for co-load and assigned-count summaries', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') expect(source).toContain('') - expect(source).toContain('') + expect(source).toContain('') + expect(source).toContain("row.unitType === 'SEALED_PALLET'") expect(source).toContain('.candidate-group { display: grid; width: 100%; min-width: 0;') expect(source).toContain('.candidate-group :deep(.line-clamp-text) { width: 100%; }') expect(source).not.toContain(' { const editable = computed(() => plan.value?.canEdit === true) const assignable = computed(() => plan.value?.canAssign === true) const assignedInboundIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundId))) +const assignedPalletIds = computed(() => new Set((plan.value?.items || []).map((item) => item.palletId).filter(Boolean))) type SmartLoadingCandidateRow = SmartLoadingCandidate & { memberInboundIds: string[]; memberInboundCodes: string[]; memberOrderSummaries: string[]; memberOrderCount: number; inboundFragmentCount: number; totalGrossWeightKg: number | null; totalVolumeCbm: number | null } function memberOrderSummaries(members: T[]) { const orders = new Map() @@ -114,6 +116,7 @@ function groupMemberLabel(group: SmartLoadingPlanCoLoadGroup) { : `${group.memberOrderCount} 票 · ${group.inboundFragmentCount} 个入仓片段` } function candidateTotalGrossWeight(candidate: SmartLoadingCandidate) { + if (candidate.unitType === 'SEALED_PALLET') return candidate.unitGrossWeightKg return candidate.unitGrossWeightKg == null ? null : Number(candidate.unitGrossWeightKg) * Number(candidate.availablePackageCount || 0) } function candidateTotalVolume(candidate: SmartLoadingCandidate) { @@ -221,20 +224,23 @@ function preferredContainerId() { const candidateColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true }, + { key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 145, showOverflowTooltip: true }, + { key: 'unitType', label: '分配单位', width: 105, slotName: 'unitType' }, { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70, slotName: 'customerName' }, { key: 'orderStatusSnapshot', prop: 'orderStatusSnapshot', label: '订单状态', width: 100, slotName: 'orderStatusSnapshot' }, - { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 }, + { key: 'availablePackageCount', prop: 'availablePackageCount', label: '箱数', width: 80 }, // { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, - { key: 'unitGrossWeightKg', label: '单箱毛重', width: 80, slotName: 'unitGrossWeight' }, + { key: 'unitGrossWeightKg', label: '单位毛重', width: 90, slotName: 'unitGrossWeight' }, { key: 'totalGrossWeightKg', label: '重量', width: 90, slotName: 'candidateTotalWeight' }, - { key: 'averageUnitVolumeCbm', label: '单箱体积', width: 80, slotName: 'averageUnitVolume' }, + { key: 'averageUnitVolumeCbm', label: '单位体积', width: 90, slotName: 'averageUnitVolume' }, { key: 'totalVolumeCbm', label: '体积', width: 90, slotName: 'candidateTotalVolume' }, { key: 'estimatedVolumeCbm', label: '预估体积', width: 100, slotName: 'estimatedVolume' }, ] const itemColumns: DataTableColumn[] = [ { key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true }, + { key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 145, showOverflowTooltip: true }, { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70, slotName: 'customerName' }, { key: 'packageCount', label: '分配箱数', width: 140, slotName: 'packageCount' }, @@ -490,7 +496,8 @@ function openAssign(source: object) { assignOpen.value = true } async function assign() { - if (!plan.value || !selectedCandidate.value || assignForm.packageCount < 1) return + if (!plan.value || !selectedCandidate.value) return + if (selectedCandidate.value.unitType !== 'SEALED_PALLET' && assignForm.packageCount < 1) return if (selectedCandidate.value.coLoadGroupId && (selectedCandidate.value.blockReason || selectedCandidate.value.locked)) return if (!selectedCandidate.value.coLoadGroupId && assignForm.packageCount > selectedCandidate.value.availablePackageCount) { ElMessage.warning(`本次最多可分配 ${selectedCandidate.value.availablePackageCount} 箱`) @@ -498,7 +505,9 @@ async function assign() { } await mutate(async () => { const selected = selectedCandidate.value! - acceptPlan(selected.orderStatusSnapshot === 'PENDING_REVIEW' + acceptPlan(selected.unitType === 'SEALED_PALLET' && selected.palletId + ? await assignSmartLoadingPallet(plan.value!.id, { palletId: selected.palletId, expectedVersion: plan.value!.version }) + : selected.orderStatusSnapshot === 'PENDING_REVIEW' ? await assignPendingReviewSmartLoadingOrder(plan.value!.id, { customerId: selected.accountId, orderId: selected.orderId, @@ -519,6 +528,10 @@ async function assign() { }, '分配明细失败', { preserveAssign: true }) } function candidateBlocked(candidate: SmartLoadingCandidateRow) { + if (candidate.unitType === 'SEALED_PALLET') { + return Boolean(candidate.blockReason) || candidate.locked === true || !candidate.palletId + || !candidate.indivisible || assignedPalletIds.value.has(candidate.palletId) + } if (candidate.orderStatusSnapshot === 'PENDING_REVIEW') { return Boolean(candidate.blockReason) || candidate.locked === true || !candidate.accountId || !candidate.inboundCode || candidate.availablePackageCount < 1 @@ -547,6 +560,7 @@ async function recommend() { } async function adjust(source: object) { const item = itemRow(source) + if (item.unitType === 'SEALED_PALLET') return if (!plan.value || mutating.value) return const count = Number(quantityDrafts[item.id]) if (count === item.packageCount) return @@ -712,10 +726,10 @@ onMounted(() => { void load(); void loadCustomers() }) plain :disabled="mutating || candidateBlocked(row as SmartLoadingCandidateRow)" @click="openAssign(row)" - >{{ row.coLoadGroupId ? '整组分配' : '加入' }} + >{{ row.unitType === 'SEALED_PALLET' ? '整托分配' : row.coLoadGroupId ? '整组分配' : '加入' }} - + @@ -789,8 +803,9 @@ onMounted(() => { void load(); void loadCustomers() }) - - + + + @@ -798,7 +813,7 @@ onMounted(() => { void load(); void loadCustomers() }) - +

{{ selectedCandidate.groupNo || selectedCandidate.customerName || '客户待补充' }}

目标柜次:{{ displayCabinetNo(selectedContainer) }} · 集装箱号:{{ displayActualContainerNo(selectedContainer) }}

@@ -810,6 +825,10 @@ onMounted(() => { void load(); void loadCustomers() })

{{ summary }}

+ -

{{ selectedCandidate.groupNo || selectedCandidate.inboundCode || '入仓码暂缺' }}

+

{{ selectedCandidate.palletNo || selectedCandidate.groupNo || selectedCandidate.inboundCode || '入仓码暂缺' }}

diff --git a/src/modules/container-loading/smartLoadingItems.test.ts b/src/modules/container-loading/smartLoadingItems.test.ts index 570ff2fd..f6e9917f 100644 --- a/src/modules/container-loading/smartLoadingItems.test.ts +++ b/src/modules/container-loading/smartLoadingItems.test.ts @@ -7,7 +7,7 @@ function item(overrides: Partial = {}): SmartLoadingItem { id: '1', loadingPlanId: '10', accountId: '20', inboundId: '30', inboundDetailId: '40', orderId: '50', orderNo: 'FL-1', inboundCode: 'NYEXX2608180003', customerName: '柏悦物流', destinationWarehouseId: '60', destinationWarehouseCode: 'LAX', destinationWarehouseName: '洛杉矶仓', - skuCode: 'SKU-1', productNameZh: '货物一', packageCount: 2, productQuantity: 20, + skuCode: 'SKU-1', productNameZh: '货物一', unitType: 'PACKAGE', palletId: null, palletNo: null, indivisible: false, packageCount: 2, productQuantity: 20, grossWeightKg: 662.8, volumeCbm: 0.334, loadSequence: 2, version: 1, pickedPackageCount: 0, minPackageCount: 1, canAdjust: true, canRemove: true, ...overrides, @@ -15,6 +15,17 @@ function item(overrides: Partial = {}): SmartLoadingItem { } describe('groupSmartLoadingItems', () => { + it('keeps every sealed pallet as an independent non-adjustable allocation unit', () => { + const grouped = groupSmartLoadingItems([ + item({ id: 'p1', unitType: 'SEALED_PALLET', palletId: '101', palletNo: 'PLT-101', indivisible: true, packageCount: 4 }), + item({ id: 'p2', unitType: 'SEALED_PALLET', palletId: '102', palletNo: 'PLT-102', indivisible: true, packageCount: 6 }), + ]) + + expect(grouped).toHaveLength(2) + expect(grouped.map((row) => row.palletNo)).toEqual(['PLT-101', 'PLT-102']) + expect(grouped.every((row) => row.canAdjust === false)).toBe(true) + }) + it('shows multiple specification rows as one inbound-code row with summed quantities', () => { const grouped = groupSmartLoadingItems([ item(), diff --git a/src/modules/container-loading/smartLoadingItems.ts b/src/modules/container-loading/smartLoadingItems.ts index 574fbd7b..8d66768e 100644 --- a/src/modules/container-loading/smartLoadingItems.ts +++ b/src/modules/container-loading/smartLoadingItems.ts @@ -21,7 +21,9 @@ function joinDistinct(items: SmartLoadingItem[], value: (item: SmartLoadingItem) export function groupSmartLoadingItems(items: SmartLoadingItem[]): SmartLoadingInboundItem[] { const groups = new Map() for (const item of items) { - const key = `${item.accountId}:${item.inboundId}` + const key = item.unitType === 'SEALED_PALLET' + ? `${item.accountId}:${item.inboundId}:pallet:${item.palletId}` + : `${item.accountId}:${item.inboundId}:packages` groups.set(key, [...(groups.get(key) || []), item]) } @@ -44,7 +46,7 @@ export function groupSmartLoadingItems(items: SmartLoadingItem[]): SmartLoadingI minPackageCount: sourceItems.reduce((sum, item) => sum + Number(item.minPackageCount || 0), 0), totalPackageCount: sourceItems.reduce((sum, item) => sum + Number(item.totalPackageCount || item.packageCount || 0), 0), remainingPackageCount: sourceItems.reduce((sum, item) => sum + Number(item.remainingPackageCount || 0), 0), - canAdjust: sourceItems.length === 1 && first.canAdjust, + canAdjust: first.unitType !== 'SEALED_PALLET' && sourceItems.length === 1 && first.canAdjust, canRemove: sourceItems.every((item) => item.canRemove), sourceItems, detailCount: sourceItems.length, diff --git a/src/modules/container-loading/smartLoadingPlanGroups.test.ts b/src/modules/container-loading/smartLoadingPlanGroups.test.ts index cf4db946..3a87e604 100644 --- a/src/modules/container-loading/smartLoadingPlanGroups.test.ts +++ b/src/modules/container-loading/smartLoadingPlanGroups.test.ts @@ -5,7 +5,7 @@ import type { SmartLoadingPlan } from './types' const item = { id: '701', loadingPlanId: '99', accountId: '1', inboundId: '201', inboundDetailId: '801', orderId: '301', orderNo: 'ORD-1', inboundCode: 'IN-201', customerName: '客户一', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', - skuCode: 'SKU-1', productNameZh: '货物一', packageCount: 5, productQuantity: 50, grossWeightKg: 50, volumeCbm: 1, + skuCode: 'SKU-1', productNameZh: '货物一', unitType: 'PACKAGE' as const, palletId: null, palletNo: null, indivisible: false, packageCount: 5, productQuantity: 50, grossWeightKg: 50, volumeCbm: 1, loadSequence: 1, version: 1, pickedPackageCount: 0, minPackageCount: 1, canAdjust: true, canRemove: true, } diff --git a/src/modules/container-loading/types.ts b/src/modules/container-loading/types.ts index c7a282dc..699a3aaf 100644 --- a/src/modules/container-loading/types.ts +++ b/src/modules/container-loading/types.ts @@ -246,6 +246,10 @@ export interface SmartLoadingCandidate { destinationWarehouseName: string | null customerName: string | null goodsSummary: string | null + unitType: 'PACKAGE' | 'SEALED_PALLET' + palletId: string | null + palletNo: string | null + indivisible: boolean availablePackageCount: number unitGrossWeightKg: number | null unitNetWeightKg: number | null @@ -336,6 +340,10 @@ export interface SmartLoadingItem { destinationWarehouseName: string | null skuCode: string | null productNameZh: string | null + unitType: 'PACKAGE' | 'SEALED_PALLET' + palletId: string | null + palletNo: string | null + indivisible: boolean packageCount: number productQuantity: number | null grossWeightKg: number | null diff --git a/src/modules/inbounds/components/InboundWorkflowWorkspace.test.ts b/src/modules/inbounds/components/InboundWorkflowWorkspace.test.ts index 94f9382a..9b5f1dc9 100644 --- a/src/modules/inbounds/components/InboundWorkflowWorkspace.test.ts +++ b/src/modules/inbounds/components/InboundWorkflowWorkspace.test.ts @@ -19,7 +19,8 @@ describe('InboundWorkflowWorkspace', () => { expect(source).toContain('label="测量审核"') expect(source).toContain('label="费用信息"') expect(source).toContain('label="状态历史"') - expect(source).toContain('label="其他记录"') + expect(source).toContain("detail.summary.fulfillmentType === 'ONE_CLICK_DROPSHIP' ? '清点组托与其他记录' : '其他记录'") + expect(source).toContain('listPlatformInboundDropshipPallets(props.customerId, props.inboundId)') }) it('opens measurement editing and review in prototype-sized centered dialogs', () => { diff --git a/src/modules/inbounds/components/InboundWorkflowWorkspace.vue b/src/modules/inbounds/components/InboundWorkflowWorkspace.vue index 4d98e83d..4786aa0d 100644 --- a/src/modules/inbounds/components/InboundWorkflowWorkspace.vue +++ b/src/modules/inbounds/components/InboundWorkflowWorkspace.vue @@ -6,7 +6,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http' import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue' import LoadErrorState from '@/shared/components/LoadErrorState.vue' import StatusBadge from '@/shared/components/StatusBadge.vue' -import { getPlatformInbound } from '../core/api' +import { getPlatformInbound, listPlatformInboundDropshipPallets } from '../core/api' import InboundHistoryPanel from '../core/InboundHistoryPanel.vue' import InboundSummaryPanel from '../core/InboundSummaryPanel.vue' import type { InboundOrderDetail } from '../core/types' @@ -37,6 +37,7 @@ let disposed = false const title = computed(() => detail.value?.summary.inboundCode || '入仓码待补充') const canViewMeasurement = computed(() => access.can('platform:firstleg:inbound:measure:view')) +const canViewDropshipPallets = computed(() => access.can('platform:firstleg:pda:view')) const statusLabel = computed(() => inboundStatusLabel(detail.value?.summary.processStatus, inboundProcessStatusLabels, '未知状态')) async function loadDetail() { @@ -51,6 +52,9 @@ async function loadDetail() { loadError.value = null try { const response = await getPlatformInbound(props.customerId, props.inboundId) + if (response.summary.fulfillmentType === 'ONE_CLICK_DROPSHIP' && canViewDropshipPallets.value) { + response.dropshipPallets = await listPlatformInboundDropshipPallets(props.customerId, props.inboundId) + } if (disposed || serial !== requestSerial) return detail.value = response } catch (error) { @@ -174,12 +178,13 @@ onBeforeUnmount(() => { :sections="['history']" /> - + { + it('shows the frozen pallet, carton and discrepancy facts', () => { + for (const marker of [ + '一件代发清点与组托结果', 'pallet.palletNo', 'pallet.status', 'pallet.cartonCount', + 'pallet.skuCount', 'pallet.weightKg', 'pallet.volumeCbm', 'pallet.discrepancyCartons', 'pallet.sealedAt', + 'item.inboundCode', 'item.skuCode', 'item.cargoName', 'item.skuQuantity', 'item.cartonCount', 'item.cartonMarks', + ]) expect(source).toContain(marker) + expect(source).toContain("SEALED: '已封托'") + }) + + it('keeps the existing scoped pallet-label action available', () => { + expect(source).toContain('downloadDropshipPalletLabel(props.customerId, props.inboundId, pallet.palletId)') + expect(source).toContain('打印托盘唛') + }) +}) diff --git a/src/modules/inbounds/core/InboundHistoryPanel.vue b/src/modules/inbounds/core/InboundHistoryPanel.vue index cd6710c0..ff8c85a9 100644 --- a/src/modules/inbounds/core/InboundHistoryPanel.vue +++ b/src/modules/inbounds/core/InboundHistoryPanel.vue @@ -9,6 +9,7 @@ const props = withDefaults(defineProps<{ dropshipPallets?: InboundDropshipPallet[] customerId?: string inboundId?: string + canViewDropship?: boolean files: InboundFileSummary[] internalNotes: InboundInternalNote[] statusHistory: InboundStatusHistory[] @@ -16,6 +17,7 @@ const props = withDefaults(defineProps<{ sections?: Array<'dropship' | 'allocations' | 'files' | 'notes' | 'history' | 'remark'> }>(), { dropshipPallets: () => [], + canViewDropship: true, sections: () => ['dropship', 'allocations', 'files', 'notes', 'history', 'remark'], }) @@ -30,6 +32,9 @@ function date(value: string | null) { const parsed = new Date(value) return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString('zh-CN', { hour12: false }) } +function dropshipPalletStatus(value: string) { + return ({ OPEN: '清点组托中', SEALED: '已封托' } as Record)[value] || value || '状态未知' +} async function printPallet(pallet: InboundDropshipPallet) { if (!props.customerId || !props.inboundId || !pallet.palletNo) return try { @@ -44,9 +49,35 @@ async function printPallet(pallet: InboundDropshipPallet) {