diff --git a/src/modules/overseas-warehouse/components/OrderReferenceText.test.ts b/src/modules/overseas-warehouse/components/OrderReferenceText.test.ts
index 81855ab4..81512bdc 100644
--- a/src/modules/overseas-warehouse/components/OrderReferenceText.test.ts
+++ b/src/modules/overseas-warehouse/components/OrderReferenceText.test.ts
@@ -1,5 +1,5 @@
-import { mount } from '@vue/test-utils'
-import { describe, expect, it } from 'vitest'
+import { flushPromises, mount } from '@vue/test-utils'
+import { describe, expect, it, vi } from 'vitest'
import OrderReferenceText from './OrderReferenceText.vue'
describe('OrderReferenceText', () => {
@@ -23,4 +23,15 @@ describe('OrderReferenceText', () => {
expect(wrapper.text()).toBe('LMO-260827-0001')
expect(wrapper.attributes('title')).toBe('LMO-260827-0001')
})
+
+ it('copies the exact server short number without frontend formatting', async () => {
+ const writeText = vi.fn().mockResolvedValue(undefined)
+ vi.stubGlobal('navigator', { clipboard: { writeText } })
+ const wrapper = mount(OrderReferenceText, { props: { value: 'LMO-9A7', copyable: true } })
+ await wrapper.get('el-button').trigger('click')
+ await flushPromises()
+ expect(writeText).toHaveBeenCalledWith('LMO-9A7')
+ expect(wrapper.attributes('title')).toBe('LMO-9A7')
+ vi.unstubAllGlobals()
+ })
})
diff --git a/src/modules/overseas-warehouse/components/OrderReferenceText.vue b/src/modules/overseas-warehouse/components/OrderReferenceText.vue
index 3f4576dc..fbf99f24 100644
--- a/src/modules/overseas-warehouse/components/OrderReferenceText.vue
+++ b/src/modules/overseas-warehouse/components/OrderReferenceText.vue
@@ -1,10 +1,13 @@
- {{ displayValue }}
+ {{ displayValue }}
diff --git a/src/modules/overseas-warehouse/lastMilePresentation.test.ts b/src/modules/overseas-warehouse/lastMilePresentation.test.ts
index 2e8bffbf..a2e437a4 100644
--- a/src/modules/overseas-warehouse/lastMilePresentation.test.ts
+++ b/src/modules/overseas-warehouse/lastMilePresentation.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
-import { formatOverseasDateTime, toLastMileOrderListItem } from './lastMilePresentation'
+import { formatOverseasDateTime, lastMileOrderReference, toLastMileOrderListItem } from './lastMilePresentation'
describe('last-mile order presentation adapter', () => {
it('maps backend codes, associations and long ids without inventing data', () => {
@@ -23,6 +23,14 @@ describe('last-mile order presentation adapter', () => {
}).owner).toBe('—')
})
+ it('preserves the exact server LMO short number without padding, prefixing or deriving it from id', () => {
+ const serverOrderNo = 'LMO-9A7'
+ const row = toLastMileOrderListItem({ id: '2092873463568596994', customerId: '100', orderNo: serverOrderNo })
+ expect(row.orderNo).toBe(serverOrderNo)
+ expect(lastMileOrderReference(serverOrderNo)).toBe(serverOrderNo)
+ expect(row.orderNo).not.toContain(row.id)
+ })
+
it('preserves real metrics and distinguishes zero from unknown fields and times', () => {
const estimatedArrivalAt = '2026-08-27T12:30:00Z'
const row = toLastMileOrderListItem({
diff --git a/src/modules/overseas-warehouse/lastMilePresentation.ts b/src/modules/overseas-warehouse/lastMilePresentation.ts
index dafe7d12..b8dd0768 100644
--- a/src/modules/overseas-warehouse/lastMilePresentation.ts
+++ b/src/modules/overseas-warehouse/lastMilePresentation.ts
@@ -45,6 +45,11 @@ export function lastMileOrderTypeLabel(value: unknown) {
return orderTypeLabels[code] || text(code)
}
+export function lastMileOrderReference(value: unknown, emptyLabel = '—') {
+ const serverValue = value == null ? '' : String(value)
+ return serverValue || emptyLabel
+}
+
export function formatOverseasDateTime(value: unknown) {
const raw = String(value ?? '').trim()
if (!raw) return '—'
@@ -57,7 +62,7 @@ export function toLastMileOrderListItem(dto: LastMileOrderSummaryDto): LastMileO
id: String(dto.id),
customerId: String(dto.customerId),
sourceContainerBookingId: String(dto.sourceContainerBookingId ?? '').trim(),
- orderNo: text(dto.orderNo),
+ orderNo: lastMileOrderReference(dto.orderNo),
warning: dto.abnormal ? '异常' : '—',
orderType: text(dto.orderTypeName || lastMileOrderTypeLabel(dto.orderType)),
rejectedAt: '—',
diff --git a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue
index 4dbdca51..6057758d 100644
--- a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue
+++ b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue
@@ -11,7 +11,7 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import { createLastMileOrderFee, getLastMileOrder, type LastMileOrderDetailDto } from '../api/lastMileOrders'
-import { formatOverseasDateTime, lastMileOrderTypeLabel, lastMileStatusLabel } from '../lastMilePresentation'
+import { formatOverseasDateTime, lastMileOrderReference, lastMileOrderTypeLabel, lastMileStatusLabel } from '../lastMilePresentation'
import { overseasWarehousePermissions as permission } from '../permissions'
import OverseasDetailShell from '../components/OverseasDetailShell.vue'
import OrderReferenceText from '../components/OrderReferenceText.vue'
@@ -39,7 +39,7 @@ function text(value: unknown) { return String(value ?? '').trim() || '—' }
function yesNo(value: boolean | null | undefined) { return value == null ? '—' : value ? '是' : '否' }
function feeStatusLabel(value: string) { return ({ PENDING_REVIEW: '待审核', APPROVED: '审核通过', REJECTED: '已驳回' } as Record)[value] || value }
-const referenceValue = computed(() => text(detail.value?.order.orderNo || '订单编码待补充'))
+const referenceValue = computed(() => lastMileOrderReference(detail.value?.order.orderNo, '订单编码待补充'))
const orderFields = computed(() => {
const order = detail.value?.order
return [
@@ -151,7 +151,7 @@ onMounted(load)
-
+
diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue
index ee04dec6..47ce3fb7 100644
--- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue
+++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue
@@ -406,7 +406,7 @@ onMounted(() => { void load() })
转仓生成入库计划按柜批量生成
查看
-
+
共 {{ total }} 条尾程订单 · 已选 {{ selectedRows.length }} 条
diff --git a/src/modules/overseas-warehouse/pages/prototypeContract.test.ts b/src/modules/overseas-warehouse/pages/prototypeContract.test.ts
index 904b6d87..f5fa6fca 100644
--- a/src/modules/overseas-warehouse/pages/prototypeContract.test.ts
+++ b/src/modules/overseas-warehouse/pages/prototypeContract.test.ts
@@ -128,6 +128,16 @@ describe('overseas warehouse first-batch prototype contract', () => {
expect(value).not.toContain('海外派送')
})
+ it('renders and copies the server-provided tail-leg short number in list and detail', () => {
+ const list = source('LastMileOrdersView.vue')
+ const detail = source('LastMileOrderDetailView.vue')
+ expect(list).toContain('')
+ expect(detail).toContain('lastMileOrderReference(detail.value?.order.orderNo')
+ expect(detail).toContain('')
+ expect(list).not.toMatch(/padStart|LMO-\$\{|slice\([^)]*orderNo/)
+ expect(detail).not.toMatch(/padStart|LMO-\$\{|slice\([^)]*orderNo/)
+ })
+
it('connects the tail-leg detail to the real overseas API and keeps missing contracts pending', () => {
const tailLeg = source('LastMileOrderDetailView.vue')
expect(tailLeg).toContain('getLastMileOrder')
@@ -137,6 +147,8 @@ describe('overseas warehouse first-batch prototype contract', () => {
expect(tailLeg).toContain('label="费用明细"')
expect(tailLeg).toContain('label="操作记录"')
expect(tailLeg).toContain('title="新增费用"')
+ expect(tailLeg).toContain('lastMileOrderReference(detail.value?.order.orderNo')
+ expect(tailLeg).toContain('')
const inbound = source('InboundOrderDetailView.vue')
expect(inbound).toContain('getInboundPlan')
expect(inbound).toContain('sourceOrderId')
diff --git a/src/modules/trucking/api/platformTrucking.test.ts b/src/modules/trucking/api/platformTrucking.test.ts
index 108d5043..2f46fab4 100644
--- a/src/modules/trucking/api/platformTrucking.test.ts
+++ b/src/modules/trucking/api/platformTrucking.test.ts
@@ -69,4 +69,16 @@ describe('truck appointment API', () => {
expect(form.get('file')).toBe(file)
expect(form.get('attachmentType')).toBe('DISPATCH_ORDER')
})
+
+ it('never forwards a legacy booking document type into truck attachments', async () => {
+ vi.mocked(http.post).mockResolvedValue({ data: { id: '1', fileId: '2', fileName: 'legacy.pdf' } })
+ await referenceTruckAppointmentAttachment('77', { fileId: '2', fileName: 'legacy.pdf', attachmentType: 'BILL_OF_LADING' as never })
+ expect(http.post).toHaveBeenLastCalledWith('/platform/v1/truck-appointments/77/attachments', { fileId: '2', fileName: 'legacy.pdf' })
+
+ const file = new File(['legacy'], 'legacy.pdf', { type: 'application/pdf' })
+ await uploadTruckAppointmentAttachment('77', file, 'BILL_OF_LADING' as never)
+ const form = vi.mocked(http.post).mock.calls.at(-1)?.[1] as FormData
+ expect(form.get('file')).toBe(file)
+ expect(form.has('attachmentType')).toBe(false)
+ })
})
diff --git a/src/modules/trucking/api/platformTrucking.ts b/src/modules/trucking/api/platformTrucking.ts
index 36d4c3a1..e95378a8 100644
--- a/src/modules/trucking/api/platformTrucking.ts
+++ b/src/modules/trucking/api/platformTrucking.ts
@@ -1,5 +1,5 @@
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
-import { TRUCK_APPOINTMENT_SAVE_FIELDS, normalizeTruckAppointmentCurrency } from '../types'
+import { TRUCK_APPOINTMENT_SAVE_FIELDS, isTruckAttachmentType, normalizeTruckAppointmentCurrency } from '../types'
import type {
SaveTruckAppointmentRequest,
TruckAppointmentPage,
@@ -77,14 +77,16 @@ export async function listTruckAppointmentAttachments(id: string | number) {
}
export async function referenceTruckAppointmentAttachment(id: string | number, payload: { fileId: string; attachmentType?: TruckAttachmentType | null; fileName?: string | null; fileHash?: string | null }) {
- const response = await http.post, ApiEnvelope>(`${basePath}/${id}/attachments`, payload)
+ const { attachmentType, ...rest } = payload
+ const body = { ...rest, ...(isTruckAttachmentType(attachmentType) ? { attachmentType } : {}) }
+ const response = await http.post, ApiEnvelope>(`${basePath}/${id}/attachments`, body)
return unwrapApiResponse(response)
}
export async function uploadTruckAppointmentAttachment(id: string | number, file: File, attachmentType?: TruckAttachmentType | null) {
const form = new FormData()
form.append('file', file)
- if (attachmentType) form.append('attachmentType', attachmentType)
+ if (isTruckAttachmentType(attachmentType)) form.append('attachmentType', attachmentType)
const response = await http.post, ApiEnvelope>(`${basePath}/${id}/attachments/upload`, form, { headers: { 'Content-Type': 'multipart/form-data' } })
return unwrapApiResponse(response)
}
diff --git a/src/modules/trucking/types.ts b/src/modules/trucking/types.ts
index c5fed852..e44f63e0 100644
--- a/src/modules/trucking/types.ts
+++ b/src/modules/trucking/types.ts
@@ -52,7 +52,11 @@ export function truckAppointmentActionLabel(action: string | null | undefined) {
return truckAppointmentActionLabels[action] || '状态更新'
}
-export type TruckAttachmentType = 'DISPATCH_ORDER' | 'DRIVER_DOCUMENT' | 'VEHICLE_DOCUMENT' | 'OTHER'
+export const TRUCK_ATTACHMENT_TYPES = ['DISPATCH_ORDER', 'DRIVER_DOCUMENT', 'VEHICLE_DOCUMENT', 'OTHER'] as const
+export type TruckAttachmentType = typeof TRUCK_ATTACHMENT_TYPES[number]
+export function isTruckAttachmentType(value: unknown): value is TruckAttachmentType {
+ return TRUCK_ATTACHMENT_TYPES.includes(value as TruckAttachmentType)
+}
export const TRUCK_APPOINTMENT_CURRENCIES = ['RMB', 'USD'] as const
export type TruckAppointmentCurrency = typeof TRUCK_APPOINTMENT_CURRENCIES[number]
export function normalizeTruckAppointmentCurrency(value: unknown): TruckAppointmentCurrency {