From bc5c56d0e4d99b15d4c646d3295c200a30a72ca1 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Wed, 23 Sep 2026 11:25:35 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=B4=E6=A0=87=E4=BC=98=E5=8C=960923?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/warehouseServices.ts | 7 +- .../views/WarehouseServiceView.vue | 170 +++++++++++++++--- .../views/relabelAttachments.test.mjs | 17 +- .../views/warehouseTransferForm.test.mjs | 105 ++++++++++- 4 files changed, 260 insertions(+), 39 deletions(-) diff --git a/src/modules/overseas-warehouse/api/warehouseServices.ts b/src/modules/overseas-warehouse/api/warehouseServices.ts index 590cd74..9708681 100644 --- a/src/modules/overseas-warehouse/api/warehouseServices.ts +++ b/src/modules/overseas-warehouse/api/warehouseServices.ts @@ -5,8 +5,8 @@ export type RelabelDeliveryMethod = 'TRUCK' | 'EXPRESS' | 'TRUCK_PICKUP' | 'EXPR export interface PageResult { records: T[]; page: number; size: number; total: number; pages: number } export interface LastlegOrderSummary { id: string; orderNo: string; inboundCode: string; orderType: string; packageCount: number; overseasWarehouseId?: string } export interface HistoricalOrderSummary { id: string | null; orderNo: string; sourceOrderNo?: string; outboundOrderNo?: string; relabelOrderNo?: string; orderType?: string; status?: string; packageCount?: number } -export interface TransferSummary { id: string; transferNo: string; inboundCode: string; lastlegOrderId: string; lastlegOrderNo: string; operatingWarehouseId: string; fbaWarehouseCode?: string; packageCount: number; deliveryMethod: string; needRelabel: boolean; relabelNo?: string; status: string; estimatedArrivalAt: string; estimatedShipAt?: string; actualShipAt?: string; createdAt: string } -export interface RelabelSummary { id: string; relabelNo: string; lastlegOrderId: string; lastlegOrderNo: string; customerOrderNo: string; overseasWarehouseId: string; status: string; itemCount: number; totalPackageCount: number; notifiedPackageCount: number; skuCodes: string; createdAt: string; totalFileId?: string; totalFileName?: string; totalFileUrl?: string; cartonTotalFileId?: string; cartonTotalFileName?: string; cartonTotalFileUrl?: string; skuTotalFileId?: string; skuTotalFileName?: string; skuTotalFileUrl?: string; deliveryMethod?: RelabelDeliveryMethod; printShippingLabel?: boolean; shippingLabelFileId?: string; shippingLabelFileName?: string; shippingLabelFileUrl?: string; fulfillmentStatus?: string; outboundOrderNo?: string; customerConfirmedAt?: string } +export interface TransferSummary { id: string; transferNo: string; inboundCode: string; lastlegOrderId: string; lastlegOrderNo: string; operatingWarehouseId: string; fbaWarehouseCode?: string; packageCount: number; deliveryMethod: string; needRelabel: boolean; relabelNo?: string; relabelStatus?: string; relabelFulfillmentStatus?: string; status: string; estimatedArrivalAt: string; estimatedShipAt?: string; actualShipAt?: string; createdAt: string } +export interface RelabelSummary { id: string; relabelNo: string; lastlegOrderId: string; lastlegOrderNo: string; customerOrderNo: string; overseasWarehouseId: string; status: string; itemCount: number; totalPackageCount: number; notifiedPackageCount: number; skuCodes: string; createdAt: string; totalFileId?: string; totalFileName?: string; totalFileUrl?: string; cartonTotalFileId?: string; cartonTotalFileName?: string; cartonTotalFileUrl?: string; skuTotalFileId?: string; skuTotalFileName?: string; skuTotalFileUrl?: string; deliveryMethod?: RelabelDeliveryMethod; printShippingLabel?: boolean; shippingLabelFileId?: string; shippingLabelFileName?: string; shippingLabelFileUrl?: string; fulfillmentStatus?: string; outboundOrderNo?: string; customerConfirmedAt?: string; reviewChannel?: 'TAIL_ORDER' | 'RELABEL_ORDER'; reviewRemark?: string } export interface RelabelItemDetail { id: string; skuCode?: string; productNameZh: string; totalPackageCount: number; notifiedPackageCount: number shipmentNo: string; trackingCode?: string; newSkuCode?: string; labelRequirement?: string; remark?: string @@ -97,6 +97,9 @@ export function listRelabelInventoryCandidates(operationWarehouseId: string, inb export function createRelabel(payload: RelabelCreate) { return overseasApiRequest({ method: 'POST', url: '/merchant/v1/last-leg/relabel-applications', data: payload }) } +export function resubmitRelabel(relabelNo: string, payload: { labelDeadline: string; contactName: string; contactPhone: string; remark?: string; idempotencyKey: string }) { + return overseasApiRequest({ method: 'PUT', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/resubmit`, data: payload }) +} export function uploadRelabelShippingLabel(relabelNo: string, file: File) { const data = new FormData(); data.append('file', file) return overseasApiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/shipping-label`, data, headers: { 'Content-Type': 'multipart/form-data' } }) diff --git a/src/modules/overseas-warehouse/views/WarehouseServiceView.vue b/src/modules/overseas-warehouse/views/WarehouseServiceView.vue index c6cf869..94e4f5f 100644 --- a/src/modules/overseas-warehouse/views/WarehouseServiceView.vue +++ b/src/modules/overseas-warehouse/views/WarehouseServiceView.vue @@ -8,7 +8,7 @@ import DataTable, { type DataTableColumn } from '@/components/common/DataTable.v import type { TableAction } from '@/components/common/tableActions' import { useAuthStore } from '@/modules/auth/public' import { - createDiscard, createRelabel, createTransfer, getDiscard, getRelabel, getTransfer, + createDiscard, createRelabel, createTransfer, getDiscard, getRelabel, getTransfer, resubmitRelabel, getTransferCartonLabel, submitTransfer, updateTransfer, confirmRelabelCompletion, confirmTransferMeasurement, downloadRelabelCompletionPhoto, downloadTransferMeasurementPhoto, uploadRelabelShippingLabel, uploadRelabelFile, @@ -55,7 +55,7 @@ const canConfirmTransfer = computed(() => auth.hasPermission('merchant:overseas- const canCancelDiscard = computed(() => auth.hasPermission('merchant:overseas-warehouse:discard:cancel')) const configs: Record = { labeling: { title: '贴标申请', createTitle: '新增贴标申请', numberLabel: '贴标申请单号', emptyText: '暂无贴标申请', columns: ['操作', '贴标申请单号', '客户单号', '海外操作仓', '贴标要求', 'SKU 数', '总箱数', '状态', '创建时间'] }, - transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['操作', '转运单号', '入库箱数', '海外仓', 'FBA 目的仓', '入仓方式', '是否换标', '状态', '创建时间'] }, + transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['操作', '转运单号', '箱数', '海外仓', 'FBA 目的仓', '入仓方式', '是否换标', '换标单号', '换标状态', '状态', '创建时间'] }, disposal: { title: '货物废弃', createTitle: '新增货物废弃申请', numberLabel: '废弃申请单号', emptyText: '暂无货物废弃申请', columns: ['操作', '废弃申请单号', '进仓编号', '操作仓', 'SKU 汇总', '废弃数量', '预估费用', '状态', '创建时间'] }, relabel: { title: '退货换标', createTitle: '新建退货换标单', numberLabel: '退货换标单号', emptyText: '暂无退货换标订单', columns: ['操作', '退货换标单号', '来源 FBA 仓', '目的 FBA 仓', 'SKU 汇总', '退件总数', '是否换标', '换标状态', '尾程状态', '创建时间'] }, } @@ -70,7 +70,7 @@ const listQueryModel = computed({ }), }) const statusOptions = computed(() => props.service === 'labeling' - ? [{ label: '待处理', value: 'PENDING_PROCESSING' }, { label: '处理中', value: 'PROCESSING' }, { label: '换标完成', value: 'COMPLETED' }] + ? [{ label: '待审核', value: 'PENDING_REVIEW' }, { label: '待处理', value: 'PENDING_PROCESSING' }, { label: '处理中', value: 'PROCESSING' }, { label: '换标完成', value: 'COMPLETED' }, { label: '已驳回', value: 'REJECTED' }] : props.service === 'disposal' ? [{ label: '待审核', value: 'PENDING_REVIEW' }, { label: '待 PDA 废弃', value: 'APPROVED' }, { label: '待仓库确认完成', value: 'PDA_COMPLETED' }, { label: '已完成', value: 'COMPLETED' }, { label: '已拒绝', value: 'REJECTED' }, { label: '已取消', value: 'CANCELLED' }] : props.service === 'transfer' @@ -111,13 +111,14 @@ interface TransferPallet { id?: string | number; sequenceNo?: number; palletNo?: interface TransferMeasurementPhoto { fileId: string | number; fileName?: string; contentType?: string; sizeBytes?: number; uploadedAt?: string } interface ServiceDetail extends Record { transferNo?: string; status?: string; operatingWarehouseId?: string; fbaWarehouseCode?: string - destinationType?: 'FBA' | 'PRIVATE'; destinationWarehouseCode?: string; destinationWarehouseName?: string + destinationType?: 'NONE' | 'FBA' | 'PRIVATE'; destinationWarehouseCode?: string; destinationWarehouseName?: string privateDestinationAddress?: string; privateDestinationPostalCode?: string; privateDestinationContactName?: string; privateDestinationContactPhone?: string deliveryAddress?: string; pickupContactName?: string; pickupContactPhone?: string packageCount?: number; volumeCbm?: number; deliveryMethod?: string; estimatedArrivalAt?: string estimatedShipAt?: string; actualShipAt?: string; fbaShipmentNo?: string; trackingNo?: string remark?: string; needRelabel?: boolean; labelDeadline?: string; relabelContactName?: string - relabelContactPhone?: string; relabelDeliveryMethod?: string + relabelContactPhone?: string; relabelDeliveryMethod?: string; relabelNo?: string + relabelStatus?: string; relabelFulfillmentStatus?: string; createdAt?: string items?: Array<{ skuCode?: string; productNameZh?: string; packageCount?: number; shippingMark?: string }> relabelBindings?: Array<{ operationType?: string; customerCartonMark?: string; originalSku?: string; newSku?: string; quantity?: number; fbaCartonMark?: string; destinationWarehouseCode?: string; remark?: string }> cartons?: TransferCarton[]; pallets?: TransferPallet[]; measurementPhotos?: TransferMeasurementPhoto[]; customerConfirmedAt?: string @@ -158,7 +159,8 @@ const listQueryFields = computed(() => { }) const listColumns = computed(() => config.value.columns.map((label, index) => index === 0 ? { key: 'actions', label, slotName: 'actions', width: props.service === 'transfer' ? 210 : 160, fixed: 'left' } - : { key: String(index - 1), prop: String(index - 1), label, minWidth: index === 1 ? 190 : 130 })) + : { key: String(index - 1), prop: String(index - 1), label, minWidth: index === 1 || label === '换标单号' ? 190 : 130, + slotName: props.service === 'transfer' && label === '换标状态' ? 'transferRelabelStatus' : props.service === 'transfer' && label === '状态' ? 'transferStatus' : undefined })) const discardCandidates = ref([]) const selectedDiscardOrderId = ref('') const discardCandidatesLoading = ref(false) @@ -225,6 +227,13 @@ watch(() => form.relabel, (value) => { if (!transferRelabelRows.value.length) addTransferRelabelRow() } }) +watch(() => cargoRows.value.map((row) => row.sku), (skus) => { + if (props.service !== 'transfer' || !form.relabel) return + transferRelabelRows.value.forEach((row, index) => { + const sourceSku = skus[Math.min(index, Math.max(0, skus.length - 1))]?.trim() || '' + if (sourceSku) row.sku = sourceSku + }) +}) watch(labelingMode, (value) => { if (value !== 'carton' && !skuRelabelRows.value.length) addSkuRelabelRow() }) watch(() => form.destination, (value) => { if (props.service !== 'labeling') return @@ -397,7 +406,10 @@ function printTransferDraftCartonMarks() { printWindow.document.write(`仓储转运客户箱唛${sections}`) printWindow.document.close(); printWindow.focus(); printWindow.print() } -function addTransferRelabelRow() { transferRelabelRows.value.push(createCargoRow()) } +function addTransferRelabelRow() { + const source = cargoRows.value[Math.min(transferRelabelRows.value.length, Math.max(0, cargoRows.value.length - 1))] + transferRelabelRows.value.push({ ...createCargoRow(), sku: source?.sku || '' }) +} function clearTransferRelabelRows() { transferRelabelRows.value = [] } @@ -728,9 +740,12 @@ function warehouseDisplay(value?: string) { } function statusLabel(value: string) { if (props.service === 'labeling') { - if (value === 'PROCESSING') return '处理中' - if (value === 'CUSTOMER_CONFIRM_PENDING') return '待客户确认' - return ['READY_FOR_OUTBOUND', 'COMPLETED'].includes(value) ? '换标完成' : '待处理' + const relabelLabels: Record = { + WAIT_APPROVAL: '待审核', PENDING_REVIEW: '待审核', PENDING_PROCESSING: '待仓库处理', PROCESSING: '处理中', + CUSTOMER_CONFIRM_PENDING: '待客户确认', READY_FOR_OUTBOUND: '换标完成', COMPLETED: '已完成', + REJECTED: '已驳回', CANCELLED: '已取消', + } + return relabelLabels[value] || value || '—' } if (props.service === 'transfer') { const transferLabels: Record = { @@ -755,6 +770,55 @@ function statusLabel(value: string) { } return labels[value] || value || '—' } +type StatusTagType = 'primary' | 'success' | 'warning' | 'danger' | 'info' +function relabelStatusTagType(status: string): StatusTagType { + if (['READY_FOR_OUTBOUND', 'COMPLETED'].includes(status)) return 'success' + if (['REJECTED', 'CANCELLED'].includes(status)) return 'danger' + if (['WAIT_APPROVAL', 'PENDING_REVIEW', 'PENDING_PROCESSING', 'CUSTOMER_CONFIRM_PENDING'].includes(status)) return 'warning' + return 'primary' +} +function relabelFulfillmentLabel(value?: string) { + const labels: Record = { + WAIT_APPROVAL: '待审核', WAIT_PROCESSING: '待仓库处理', + WAIT_RELABEL: '待换标', WAIT_PRE_DISPATCH: '待排车', READY_FOR_DISPATCH: '待排车', + OUTBOUND_CREATED: '换标出库单已创建', OUTBOUNDED: '已出库', COMPLETED: '已完成', + } + return labels[value || ''] || value || '—' +} +function relabelFulfillmentTagType(status?: string): StatusTagType { + if (['OUTBOUNDED', 'COMPLETED'].includes(status || '')) return 'success' + if (['WAIT_APPROVAL', 'WAIT_PROCESSING', 'WAIT_RELABEL', 'WAIT_PRE_DISPATCH', 'READY_FOR_DISPATCH'].includes(status || '')) return 'warning' + return 'primary' +} +function transferStatusTagType(status: string): StatusTagType { + if (['RECEIVED', 'COMPLETED'].includes(status)) return 'success' + if (['REJECTED', 'CANCELLED'].includes(status)) return 'danger' + if (['DRAFT'].includes(status)) return 'info' + if (['PENDING_REVIEW', 'WAIT_CUSTOMER_CONFIRMATION', 'WAIT_RELABEL', 'WAIT_FBA_DESTINATION', 'READY_FOR_DISPATCH', 'WAIT_OUTBOUND', 'PARTIALLY_RECEIVED'].includes(status)) return 'warning' + return 'primary' +} +function transferRelabelStatus(item: { needRelabel?: boolean; relabelNo?: string; relabelStatus?: string; relabelFulfillmentStatus?: string }) { + if (!item.needRelabel) return { code: 'NOT_REQUIRED', label: '无需换标' } + if (!item.relabelNo) return { code: 'WAIT_RELABEL', label: '待换标' } + const code = item.relabelStatus || 'PENDING_PROCESSING' + const labels: Record = { + PENDING_REVIEW: '待审核', PENDING_PROCESSING: '待处理', PROCESSING: '换标中', + CUSTOMER_CONFIRM_PENDING: '待客户确认', READY_FOR_OUTBOUND: '已换标', COMPLETED: '已换标', + REJECTED: '已驳回', CANCELLED: '已取消', + } + const fulfillmentLabels: Record = { + WAIT_RELABEL: '待换标', WAIT_CUSTOMER_CONFIRMATION: '待客户确认', READY_FOR_DISPATCH: '已换标', + OUTBOUND_CREATED: '已换标', OUTBOUNDED: '已换标', NOT_REQUIRED: '无需换标', + } + return { code, label: labels[code] || fulfillmentLabels[item.relabelFulfillmentStatus || ''] || statusLabel(code) } +} +function transferRelabelStatusTagType(status: string): StatusTagType { + if (['READY_FOR_OUTBOUND', 'COMPLETED'].includes(status)) return 'success' + if (['REJECTED', 'CANCELLED'].includes(status)) return 'danger' + if (status === 'NOT_REQUIRED') return 'info' + if (['WAIT_RELABEL', 'PENDING_REVIEW', 'CUSTOMER_CONFIRM_PENDING'].includes(status)) return 'warning' + return 'primary' +} function deliveryMethodLabel(value: string) { const labels: Record = { TRUCK: '卡车派送', EXPRESS: '快递派送', TRUCK_PICKUP: '卡车自提', EXPRESS_PICKUP: '快递自提', @@ -764,6 +828,11 @@ function deliveryMethodLabel(value: string) { } return labels[value] || value || '—' } +function destinationTypeLabel(value?: string, fbaWarehouseCode?: string) { + if (value === 'PRIVATE') return '私人仓' + if (value === 'FBA' || fbaWarehouseCode) return 'FBA 仓' + return '暂未确定' +} function formatDateTime(value?: string | null) { return value ? value.replace('T', ' ').slice(0, 19) : '—' } @@ -786,8 +855,9 @@ async function load() { total.value = Number.isFinite(Number(result.total)) ? Number(result.total) : result.records.length submittedRows.value = result.records.map((item) => { const operationWarehouse = warehouseDisplay(item.operatingWarehouseId) + const relabelStatus = transferRelabelStatus(item) return props.service === 'transfer' - ? { ...row([item.transferNo, item.packageCount, operationWarehouse, item.fbaWarehouseCode, deliveryMethodLabel(item.deliveryMethod), item.needRelabel ? '是 · 待绑定换标单' : '否', statusLabel(item.status), formatDateTime(item.createdAt)]), __status: item.status } + ? { ...row([item.transferNo, item.packageCount, operationWarehouse, item.fbaWarehouseCode, deliveryMethodLabel(item.deliveryMethod), item.needRelabel ? '是' : '否', item.relabelNo, relabelStatus.label, statusLabel(item.status), formatDateTime(item.createdAt)]), __status: item.status, __statusLabel: statusLabel(item.status), __relabelStatus: relabelStatus.code, __relabelStatusLabel: relabelStatus.label } : row([item.transferNo, '—', item.fbaWarehouseCode, '—', item.packageCount, '是', item.relabelNo ? '已生成' : '待生成', statusLabel(item.status), formatDateTime(item.createdAt)]) }) } @@ -964,8 +1034,13 @@ async function submit(action: 'draft' | 'submit' = 'submit') { const submitted = await submitTransfer(transferNo) if (form.relabel) { const relabelNo = String(submitted.relabelNo || created.relabelNo || '') + if (!relabelNo) { + ElMessage.success('转运单已提交审核;PDA 入库确认后将自动生成关联换标单') + await ElMessageBox.alert(`转运单 ${transferNo} 已提交。关联换标单会在 PDA 完成入库、客户确认实测库存后自动生成;届时请进入关联换标单详情上传贴标总文件。`, '转运单已提交', { confirmButtonText: '知道了' }) + await openDetail({ '0': transferNo }) + return + } try { - if (!relabelNo) throw new Error('转运单已提交,但未返回关联换标单号') if (cartonTotalFile.value) await uploadRelabelFile(relabelNo, 'CARTON_TOTAL', cartonTotalFile.value) if (skuTotalFile.value) await uploadRelabelFile(relabelNo, 'SKU_TOTAL', skuTotalFile.value) } catch (error) { @@ -1003,8 +1078,8 @@ async function openDetail(value: Record) { } catch (error) { selectedDetail.value = null; selectedRelabelDetail.value = null; detailError.value = error instanceof Error ? error.message : '详情加载失败'; mode.value = 'detail'; ElMessage.error(detailError.value) } } async function loadDetailPage() { - await loadWarehouses() await openDetail({ '0': detailNumber.value }) + await loadWarehouses() } async function confirmTransferMeasurements() { const transferNo = String(selectedDetail.value?.transferNo || selectedDetail.value?.['0'] || '') @@ -1145,6 +1220,34 @@ async function selectTransferWarehouse() { ElMessage.error(error instanceof Error ? error.message : '中转仓联系信息加载失败') } finally { operationWarehouseDetailLoading.value = false } } +async function confirmSelectedRelabel() { + const relabelNo = selectedRelabelDetail.value?.application.relabelNo + if (!relabelNo) return + try { + await ElMessageBox.confirm('确认仓库上传的换标结果无误并继续出库流程吗?', '换标完成确认', { confirmButtonText: '确认并继续', cancelButtonText: '取消' }) + await confirmRelabelCompletion(relabelNo) + ElMessage.success('已确认换标完成,订单已进入后续出库流程') + await openDetail({ '0': relabelNo }) + } catch (error) { if (error !== 'cancel') ElMessage.error(error instanceof Error ? error.message : '确认失败') } +} +async function resubmitRejectedRelabel() { + const application = selectedRelabelDetail.value?.application + if (!application || application.status !== 'REJECTED') return + try { + const { value } = await ElMessageBox.prompt('可先在下方替换贴标文件,再补充本次修改说明。', '修改后重新提交审核', { + inputType: 'textarea', inputValue: selectedRelabelDetail.value?.remark || '', + inputPlaceholder: '修改说明(选填)', confirmButtonText: '重新提交', cancelButtonText: '取消', + }) + selectedRelabelDetail.value = await resubmitRelabel(application.relabelNo, { + labelDeadline: application.labelDeadline || '', contactName: application.contactName || '', + contactPhone: application.contactPhone || '', remark: value?.trim() || undefined, + idempotencyKey: crypto.randomUUID(), + }) + ElMessage.success('贴标单已重新提交审核') + } catch (error) { + if (error !== 'cancel' && error !== 'close') ElMessage.error(error instanceof Error ? error.message : '重新提交失败') + } +} const transferInventoryOptions = computed(() => { const grouped = new Map() for (const item of transferInventoryCandidates.value) { @@ -1264,6 +1367,8 @@ function selectRelabelInventory(inboundOrderId: string) { + + @@ -1304,7 +1409,7 @@ function selectRelabelInventory(inboundOrderId: string) { - +
*目的去向暂未确定FBA 仓私人仓
@@ -1328,9 +1433,9 @@ function selectRelabelInventory(inboundOrderId: string) {
1贴标总文件
{{ requiresBothTotalFiles ? '必传 · 两份文件' : '必传 · 二选一' }}
-
箱唛总文件
{{ cartonTotalFile ? '重新上传' : '选择文件' }}
{{ cartonTotalFile?.name || '尚未选择文件' }}
+
箱唛总文件
{{ cartonTotalFile ? '重新上传' : '选择文件' }}
{{ cartonTotalFile?.name || '尚未选择文件' }}
{{ requiresBothTotalFiles ? '和' : '或' }}
-
SKU 总文件
{{ skuTotalFile ? '重新上传' : '选择文件' }}
{{ skuTotalFile?.name || '尚未选择文件' }}
+
SKU 总文件
{{ skuTotalFile ? '重新上传' : '选择文件' }}
{{ skuTotalFile?.name || '尚未选择文件' }}
@@ -1346,7 +1451,7 @@ function selectRelabelInventory(inboundOrderId: string) {
- +
选择SKU中文品名库存总数本次贴标数量
{{ row.skuCode }}{{ row.productNameZh || '—' }}{{ row.availableQuantity }}
该退货单暂无可申请贴标的 SKU 库存
{{ row.skuCode }}{{ row.productNameZh || '—' }}{{ row.availableQuantity }}
该退货单暂无可申请贴标的 SKU 库存
@@ -1382,9 +1487,9 @@ function selectRelabelInventory(inboundOrderId: string) {
贴标总文件
{{ requiresBothTotalFiles ? '必传 · 两份文件' : '必传 · 二选一' }}
-
箱唛总文件
{{ cartonTotalFile ? '重新上传' : '选择文件' }}
{{ cartonTotalFile?.name || '尚未选择文件' }}
+
箱唛总文件
{{ cartonTotalFile ? '重新上传' : '选择文件' }}
{{ cartonTotalFile?.name || '尚未选择文件' }}
{{ requiresBothTotalFiles ? '和' : '或' }}
-
SKU 总文件
{{ skuTotalFile ? '重新上传' : '选择文件' }}
{{ skuTotalFile?.name || '尚未选择文件' }}
+
SKU 总文件
{{ skuTotalFile ? '重新上传' : '选择文件' }}
{{ skuTotalFile?.name || '尚未选择文件' }}
@@ -1407,10 +1512,17 @@ function selectRelabelInventory(inboundOrderId: string) {
{{ props.service === 'labeling' ? selectedRelabelDetail?.application.relabelNo : selectedDetail?.transferNo || selectedDetail?.['0'] || `${config.title}详情` }}{{ config.title }}详情
-
取消申请编辑确认测量数据{{ statusLabel(props.service === 'labeling' ? selectedRelabelDetail?.application.status || '' : String(selectedDetail?.status || '')) }}
+
修改后重新提交确认换标完成取消申请编辑确认测量数据{{ statusLabel(props.service === 'labeling' ? selectedRelabelDetail?.application.status || '' : String(selectedDetail?.status || '')) }}
-

订单概览

操作仓{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}
客户参考号{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}
SKU 数{{ selectedRelabelDetail?.application.itemCount ?? '—' }}
总箱数{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}
贴标截止时间{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}
仓库联系人{{ selectedRelabelDetail?.application.contactName || '—' }}
联系电话{{ selectedRelabelDetail?.application.contactPhone || '—' }}
创建时间{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}
{{ column }}{{ selectedDetail?.[String(index)] || '—' }}
+ +

订单概览

操作仓{{ warehouseDisplay(selectedRelabelDetail?.application.overseasWarehouseId) }}
客户参考号{{ selectedRelabelDetail?.application.customerOrderNo || '—' }}
SKU 数{{ selectedRelabelDetail?.application.itemCount ?? '—' }}
总箱数{{ selectedRelabelDetail?.application.totalPackageCount ?? '—' }}
贴标截止时间{{ formatDateTime(selectedRelabelDetail?.application.labelDeadline) }}
仓库联系人{{ selectedRelabelDetail?.application.contactName || '—' }}
联系电话{{ selectedRelabelDetail?.application.contactPhone || '—' }}
创建时间{{ formatDateTime(selectedRelabelDetail?.application.createdAt) }}
状态{{ statusLabel(selectedDetail?.status || '') }}
目的去向{{ destinationTypeLabel(selectedDetail?.destinationType, selectedDetail?.fbaWarehouseCode) }}
FBA 目的仓{{ warehouseDisplay(selectedDetail?.destinationWarehouseCode || selectedDetail?.fbaWarehouseCode) }}
FBA 货件号{{ selectedDetail?.fbaShipmentNo || '—' }}
私人仓地址{{ selectedDetail?.privateDestinationAddress || '—' }}
邮编{{ selectedDetail?.privateDestinationPostalCode || '—' }}
私人仓联系人{{ selectedDetail?.privateDestinationContactName || '—' }}
联系电话{{ selectedDetail?.privateDestinationContactPhone || '—' }}
海外仓{{ warehouseDisplay(selectedDetail?.operatingWarehouseId) }}
箱数{{ selectedDetail?.packageCount ?? '—' }}
入仓方式{{ deliveryMethodLabel(selectedDetail?.deliveryMethod || '') }}
期望发货时间{{ formatDateTime(selectedDetail?.estimatedShipAt) }}
实际发货时间{{ formatDateTime(selectedDetail?.actualShipAt) }}
是否换标{{ selectedDetail?.needRelabel ? '是' : '否' }}
换标状态{{ transferRelabelStatus(selectedDetail || {}).label }}
换标单号{{ selectedDetail?.relabelNo || '—' }}
创建时间{{ formatDateTime(selectedDetail?.createdAt) }}
转运单号{{ selectedDetail?.transferNo || '—' }}
{{ column }}{{ selectedDetail?.[String(index)] || '—' }}
+
+

PDA 打托实测数据

PDA 作业完成即显示入库完成,客户可确认仓库实测数据
+
托盘号库位箱数长 × 宽 × 高(cm)毛重(kg)体积(m³)状态完成时间
{{ pallet.palletNo || `第${pallet.sequenceNo || '—'}托` }}{{ pallet.warehouseLocationCode || '—' }}{{ pallet.cartonCount ?? '—' }}{{ [pallet.lengthCm, pallet.widthCm, pallet.heightCm].every((value) => value != null) ? `${pallet.lengthCm} × ${pallet.widthCm} × ${pallet.heightCm}` : '—' }}{{ pallet.grossWeightKg ?? '—' }}{{ pallet.volumeCbm ?? '—' }}{{ statusLabel(pallet.status || '') }}{{ formatDateTime(pallet.completedAt) }}
+
暂无 PDA 打托测量数据
+
测量打托现场照片
照片 {{ index + 1 }} 加载中…
仓库尚未上传测量打托照片
+

货物明细

打印箱唛
商品 SKU中文品名预计箱数客户箱唛号
{{ item.skuCode || '—' }}{{ item.productNameZh || '—' }}{{ item.packageCount ?? '—' }}{{ item.shippingMark || '—' }}
暂无货物明细
@@ -1419,12 +1531,11 @@ function selectRelabelInventory(inboundOrderId: string) {

换标箱级去向

换标类型客户箱唛号旧 SKU新 SKU新 FBA 箱唛实际 FBA 目的仓数量备注
{{ binding.operationType === 'CARTON_AND_SKU' ? 'SKU 和箱唛都换' : binding.operationType === 'SKU_LABEL' ? '只换 SKU' : '只换箱唛' }}{{ binding.customerCartonMark || '—' }}{{ binding.originalSku || '—' }}{{ binding.newSku || '—' }}{{ binding.fbaCartonMark || '—' }}{{ binding.operationType === 'SKU_LABEL' ? '沿用原箱去向' : warehouseDisplay(binding.destinationWarehouseCode) }}{{ binding.quantity ?? '—' }}{{ binding.remark || '—' }}
暂无换标绑定明细
-

发货时间

预计发货时间{{ formatDateTime(selectedDetail?.estimatedShipAt) }}
实际发货时间{{ formatDateTime(selectedDetail?.actualShipAt) }}

实际发货时间由仓库出库或 PDA 完成后回填,客户不能手动修改。

换标完成与出库

派送方式{{ deliveryMethodLabel(selectedRelabelDetail?.application.deliveryMethod || '') || '无需出库' }}
-
履约状态{{ statusLabel(selectedRelabelDetail?.application.fulfillmentStatus || '') }}
+
履约状态{{ relabelFulfillmentLabel(selectedRelabelDetail?.application.fulfillmentStatus) }}
客户确认时间{{ formatDateTime(selectedRelabelDetail?.application.customerConfirmedAt) }}
仓库完工照片
照片 {{ index + 1 }} 加载中…
仓库尚未上传完工照片
@@ -1438,12 +1549,6 @@ function selectRelabelInventory(inboundOrderId: string) {
暂无贴标明细
-
-

PDA 打托实测数据

PDA 作业完成即显示入库完成,客户可确认仓库实测数据
-
托盘号库位箱数长 × 宽 × 高(cm)毛重(kg)体积(m³)状态完成时间
{{ pallet.palletNo || `第${pallet.sequenceNo || '—'}托` }}{{ pallet.warehouseLocationCode || '—' }}{{ pallet.cartonCount ?? '—' }}{{ [pallet.lengthCm, pallet.widthCm, pallet.heightCm].every((value) => value != null) ? `${pallet.lengthCm} × ${pallet.widthCm} × ${pallet.heightCm}` : '—' }}{{ pallet.grossWeightKg ?? '—' }}{{ pallet.volumeCbm ?? '—' }}{{ statusLabel(pallet.status || '') }}{{ formatDateTime(pallet.completedAt) }}
-
暂无 PDA 打托测量数据
-
测量打托现场照片
照片 {{ index + 1 }} 加载中…
仓库尚未上传测量打托照片
-

换标任务明细

@@ -1474,6 +1579,8 @@ function selectRelabelInventory(inboundOrderId: string) { .warehouse-service-page--return-relabel .warehouse-table-scroll:has(> .warehouse-return-relabel-table)~.warehouse-table-scroll{display:none} .warehouse-native-file{min-height:32px;padding:5px 8px;border:1px solid #dcdfe6;border-radius:4px;background:#fff;color:#606266}.warehouse-fulfillment-grid{margin-bottom:18px}.warehouse-evidence{display:flex;align-items:flex-start;gap:16px;padding:14px;border:1px solid #e7ebf1;border-radius:4px;background:#f8fafc}.warehouse-evidence>span{padding-top:6px;color:#71809a;font-size:12px;white-space:nowrap}.warehouse-evidence>strong{padding-top:6px;color:#8a95a5;font-size:12px;font-weight:400}.warehouse-photo-grid{display:grid;grid-template-columns:repeat(4,140px);gap:12px}.warehouse-photo-item{width:140px;height:105px;overflow:hidden;border:1px solid #dce3ed;border-radius:5px;background:#fff}.warehouse-photo-item :deep(.el-image){display:block;width:100%;height:100%;cursor:zoom-in}.warehouse-photo-placeholder{display:flex;width:100%;height:100%;align-items:center;justify-content:center;padding:10px;border:0;background:#f4f7fb;color:#8792a2;font-size:12px;text-align:center}.warehouse-photo-retry{cursor:pointer}.warehouse-completion-remark{margin:10px 0 0;color:#596579;font-size:12px} .warehouse-measurement-evidence{margin-top:16px} +.warehouse-measurement-photo-grid{flex:1;grid-template-columns:repeat(auto-fill,minmax(240px,1fr))} +.warehouse-measurement-photo-item{width:100%;height:180px} .warehouse-template-actions{flex-wrap:wrap;justify-content:flex-end}.warehouse-import-preview{margin:0 0 16px;padding:14px;border:1px solid #cfe0f5;border-radius:5px;background:#f8fbff}.warehouse-import-preview .warehouse-section-heading{margin-bottom:10px}.warehouse-import-preview .warehouse-section-heading>div{display:flex;align-items:center;gap:10px}.warehouse-import-preview p{margin:0;color:#7d8797;font-size:12px}.warehouse-binding-preview-table{min-width:980px;background:#fff} .warehouse-transfer-relabel .warehouse-labeling-heading>div:first-child{min-width:0}.warehouse-transfer-relabel .warehouse-labeling-heading h3{margin-bottom:6px}.warehouse-transfer-relabel .warehouse-labeling-heading p{margin:0 0 16px;color:#7d8797;font-size:12px}.warehouse-transfer-relabel-fields{margin-bottom:18px}.warehouse-transfer-file-groups{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(360px,.85fr);align-items:stretch;gap:14px;margin-bottom:14px}.warehouse-transfer-total-files,.warehouse-transfer-template-group{height:100%;margin:0}.warehouse-transfer-mark-table{min-width:1420px}.warehouse-transfer-mark-table td{vertical-align:top}.warehouse-transfer-mark-table :deep(.el-textarea){min-width:220px}.warehouse-transfer-mark-table :deep(.el-input-number){width:150px}.warehouse-count-error{color:#d64545} .warehouse-transfer-form{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.warehouse-transfer-form--relabel{grid-template-columns:minmax(0,1fr) minmax(0,1.35fr);align-items:start}.warehouse-transfer-form--relabel .warehouse-transfer-main-card{grid-column:1;grid-row:1}.warehouse-transfer-form--relabel .warehouse-transfer-cargo-card{grid-column:1;grid-row:2}.warehouse-transfer-form--relabel .warehouse-transfer-relabel{grid-column:2;grid-row:1 / span 2;margin-bottom:0}.warehouse-transfer-form--relabel .warehouse-transfer-submit-bar{grid-column:1 / -1}.warehouse-transfer-form :deep(.el-input__wrapper),.warehouse-transfer-form :deep(.el-textarea__inner),.warehouse-transfer-form :deep(.el-select__wrapper),.warehouse-transfer-form :deep(.el-date-editor.el-input__wrapper){min-height:32px}.warehouse-transfer-form .warehouse-form-section{padding:16px}.warehouse-transfer-form .warehouse-form-section h3{margin-bottom:12px;padding-bottom:9px;font-size:14px}.warehouse-transfer-form .warehouse-form-grid{gap:12px 16px}.warehouse-transfer-form .warehouse-choice-field{min-height:34px;padding:8px 10px;grid-template-columns:118px minmax(0,1fr);gap:10px}.warehouse-transfer-form .warehouse-choice-field>span{font-size:12px}.warehouse-transfer-form .warehouse-receiving-card{gap:12px;padding:10px 12px}.warehouse-transfer-form .warehouse-section-heading{margin-bottom:8px}.warehouse-transfer-form .warehouse-edit-table{font-size:11px}.warehouse-transfer-form .warehouse-edit-table th{height:36px;padding:0 9px}.warehouse-transfer-form .warehouse-edit-table td{height:42px;padding:6px 7px}.warehouse-transfer-form .warehouse-transfer-mark-table :deep(.el-textarea){min-width:150px}.warehouse-transfer-form .warehouse-transfer-mark-table :deep(.el-input-number){width:112px}.warehouse-transfer-form .warehouse-hint{margin-top:5px;font-size:11px}.warehouse-transfer-form .warehouse-labeling-mode{gap:9px;margin-bottom:10px;padding:9px 10px}.warehouse-transfer-form .warehouse-labeling-mode>span{font-size:12px}.warehouse-transfer-form .warehouse-labeling-mode :deep(.el-radio-button__inner){padding:6px 9px}.warehouse-transfer-form .warehouse-template-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:5px;padding-bottom:8px}.warehouse-transfer-form .warehouse-template-actions :deep(.el-button){padding:6px 8px;font-size:11px}.warehouse-transfer-form .warehouse-submit-bar{padding:4px 0 18px}.warehouse-transfer-form .warehouse-submit-bar :deep(.el-button){min-height:32px;padding:7px 14px}.warehouse-transfer-form .warehouse-transfer-relabel{overflow:hidden}.warehouse-transfer-form .warehouse-transfer-relabel-fields{margin-bottom:10px}.warehouse-step-heading{align-items:flex-start}.warehouse-step-heading h3{display:flex;align-items:center;gap:9px}.warehouse-step-number{display:inline-grid;flex:0 0 auto;width:24px;height:24px;border-radius:50%;background:#3478c8;color:#fff;font-size:12px;place-items:center}.warehouse-heading-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}.warehouse-transfer-template-group{margin:0 0 14px;padding:12px 14px;border:1px solid #dde5f0;border-radius:7px;background:#fbfcfe}.warehouse-binding-heading{margin-top:14px;padding:12px 14px;border:1px solid #dde5f0;border-radius:7px;background:#f8faff}.warehouse-binding-heading strong{display:flex;align-items:center;gap:8px;color:#34445c;font-size:13px} @@ -1482,6 +1589,11 @@ function selectRelabelInventory(inboundOrderId: string) { @media(max-width:1180px){.warehouse-transfer-file-groups{grid-template-columns:minmax(0,1fr)}} .warehouse-transfer-form .pickup-address-field{grid-column:span 1} .warehouse-transfer-main-card .warehouse-form-grid{grid-template-columns:repeat(2,minmax(280px,640px))}.warehouse-transfer-cargo-card .warehouse-section-heading,.warehouse-transfer-cargo-card .warehouse-table-scroll{width:100%;max-width:1296px} +.warehouse-service-page--detail .warehouse-transfer-cargo-card .warehouse-section-heading,.warehouse-service-page--detail .warehouse-transfer-cargo-card .warehouse-table-scroll{max-width:none} +.warehouse-service-page--detail .warehouse-transfer-overview-grid{grid-template-columns:repeat(4,minmax(180px,1fr));gap:6px 28px;padding:10px 16px 12px}.warehouse-service-page--detail .warehouse-transfer-overview-grid>div{min-height:26px;padding:2px 0;border:0}.warehouse-service-page--detail .warehouse-transfer-overview-grid span{flex:0 0 auto;min-width:76px}.warehouse-service-page--detail .warehouse-transfer-overview-grid :deep(.el-tag){height:24px} +.warehouse-service-page--detail>.warehouse-detail-header+.warehouse-card .warehouse-detail-grid{grid-template-columns:repeat(4,minmax(180px,1fr));gap:6px 28px;padding:10px 16px 12px}.warehouse-service-page--detail>.warehouse-detail-header+.warehouse-card .warehouse-detail-grid>div{min-height:26px;padding:2px 0;border:0}.warehouse-service-page--detail>.warehouse-detail-header+.warehouse-card .warehouse-detail-grid span{flex:0 0 auto;min-width:76px} +.warehouse-service-page--detail .warehouse-transfer-overview-grid>.warehouse-overview-wide{grid-column:span 2} +@media(max-width:1100px){.warehouse-service-page--detail .warehouse-transfer-overview-grid{grid-template-columns:repeat(2,minmax(180px,1fr))}}@media(max-width:720px){.warehouse-service-page--detail .warehouse-transfer-overview-grid{grid-template-columns:1fr;gap:4px;padding:10px 14px}} /* Keep the count and controls visible together on narrow list pages. */ .warehouse-pagination { justify-content: flex-end; } .warehouse-pagination-total { color: #526176; font-size: 14px; } diff --git a/src/modules/overseas-warehouse/views/relabelAttachments.test.mjs b/src/modules/overseas-warehouse/views/relabelAttachments.test.mjs index 1d17eae..cc8b815 100644 --- a/src/modules/overseas-warehouse/views/relabelAttachments.test.mjs +++ b/src/modules/overseas-warehouse/views/relabelAttachments.test.mjs @@ -208,10 +208,11 @@ test('贴标详情展示箱唛、SKU 子明细和独立FBA目的仓', () => { assert.match(view, /item\.skuItems\?\.length/) }) -test('客户在换标列表确认完工且照片使用二进制下载', () => { +test('客户可在换标列表或详情确认完工且照片使用二进制下载', () => { assert.match(view, /item\.__status === 'CUSTOMER_CONFIRM_PENDING'/) assert.match(view, /confirmRelabelFromList\(item\['0'\]\)/) - assert.doesNotMatch(view, /selectedRelabelDetail\?\.application\.status === 'CUSTOMER_CONFIRM_PENDING'/) + assert.match(view, /selectedRelabelDetail\?\.application\.status === 'CUSTOMER_CONFIRM_PENDING'/) + assert.match(view, /@click="confirmSelectedRelabel">确认换标完成/) assert.match(api, /overseasBlobRequest/) }) @@ -224,6 +225,14 @@ test('客户详情进入页面后直接回显完工照片并支持大图预览', }) test('进入复核后客户订单回显换标完成状态', () => { - assert.match(view, /value === 'CUSTOMER_CONFIRM_PENDING'\) return '待客户确认'/) - assert.match(view, /\['READY_FOR_OUTBOUND', 'COMPLETED'\]\.includes\(value\) \? '换标完成'/) + assert.match(view, /CUSTOMER_CONFIRM_PENDING: '待客户确认'/) + assert.match(view, /READY_FOR_OUTBOUND: '换标完成'/) +}) + +test('审核驳回后展示原因并允许替换资料后重新提交', () => { + assert.match(api, /export function resubmitRelabel\(/) + assert.match(view, /贴标单审核已驳回/) + assert.match(view, /application\.reviewRemark/) + assert.match(view, /修改后重新提交/) + assert.match(view, /await resubmitRelabel\(/) }) diff --git a/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs b/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs index 25675e9..c06a0a9 100644 --- a/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs +++ b/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs @@ -40,6 +40,17 @@ test('仓储转运支持无目的地、FBA 仓和私人仓并显式选择是否 assert.doesNotMatch(view, /relabelChargeAmount:/) }) +test('换标详情待客户确认时提供确认入口并刷新详情', () => { + assert.match(view, /application\.status === 'CUSTOMER_CONFIRM_PENDING'[\s\S]*@click="confirmSelectedRelabel">确认换标完成/) + assert.match(view, /async function confirmSelectedRelabel\(\)[\s\S]*await confirmRelabelCompletion\(relabelNo\)[\s\S]*await openDetail\(\{ '0': relabelNo \}\)/) +}) + +test('仓储转运换标绑定明细自动同步只读的旧 SKU', () => { + assert.match(view, /watch\(\(\) => cargoRows\.value\.map\(\(row\) => row\.sku\)/) + assert.match(view, /if \(sourceSku\) row\.sku = sourceSku/) + assert.match(view, /transferRelabelRows\.value\.push\(\{ \.\.\.createCargoRow\(\), sku: source\?\.sku \|\| '' \}\)/) +}) + test('仓储转运换标整票只允许一个目的仓', () => { assert.match(view, /props\.service === 'transfer' && transferDestinationType === 'FBA'[^\n]+FBA 目的仓/) assert.match(view, /FBA 货件编号<\/span> assert.match(view, /uploadRelabelFile\(relabelNo, 'CARTON_TOTAL', cartonTotalFile\.value\)/) assert.match(view, /uploadRelabelFile\(relabelNo, 'SKU_TOTAL', skuTotalFile\.value\)/) assert.match(view, /转运单已提交,附件待补传/) + assert.match(transferRelabelSection, /native-type="button"[^>]+chooseCreateTotalFile\('CARTON_TOTAL'\)/) + assert.match(transferRelabelSection, /native-type="button"[^>]+chooseCreateTotalFile\('SKU_TOTAL'\)/) }) test('仓储转运换标总文件与明细模板在桌面端左右排列', () => { @@ -155,13 +168,17 @@ test('仓储转运列表操作仓使用海外仓接口数据', () => { assert.doesNotMatch(view, /美西洛杉矶仓/) }) -test('仓储转运预计发货时间可选并只读展示实际发货时间', () => { - assert.match(view, /预计发货时间/) +test('仓储转运期望发货时间可选并在订单概览回填发货时间', () => { + assert.doesNotMatch(view, /预计发货时间/) + assert.match(view, /期望发货时间/) assert.match(view, /v-model="form\.estimatedShipAt"/) assert.match(view, /v-model="form\.estimatedShipAt"[^>]+placeholder="选填"/) - assert.doesNotMatch(view, /!form\.estimatedShipAt && '预计发货时间'/) + assert.doesNotMatch(view, /!form\.estimatedShipAt/) assert.match(view, /estimatedShipAt: form\.estimatedShipAt/) - assert.match(view, /实际发货时间由仓库出库或 PDA 完成后回填/) + assert.match(view, /期望发货时间<\/span>\{\{ formatDateTime\(selectedDetail\?\.estimatedShipAt\) \}\}<\/strong>/) + assert.match(view, /实际发货时间<\/span>\{\{ formatDateTime\(selectedDetail\?\.actualShipAt\) \}\}<\/strong>/) + assert.doesNotMatch(view, /class="warehouse-card warehouse-form-section warehouse-transfer-times"/) + assert.match(view, /estimatedArrivalAt: detail\.estimatedArrivalAt \|\| '', estimatedShipAt: detail\.estimatedShipAt \|\| ''/) assert.match(api, /estimatedShipAt\?: string/) }) @@ -176,6 +193,66 @@ test('客户仓储转运展示每个实际流转状态的中文名称', () => { assert.match(view, /OUTBOUNDED: '已出库'/) }) +test('仓储转运列表展示换标单号、换标状态并为两类状态着色', () => { + const transferConfig = view.match(/transfer:\s*\{[^\n]+/)?.[0] || '' + assert.match(transferConfig, /'是否换标', '换标单号', '换标状态', '状态'/) + assert.match(api, /relabelNo\?: string; relabelStatus\?: string; relabelFulfillmentStatus\?: string/) + assert.match(view, /item\.needRelabel \? '是' : '否', item\.relabelNo, relabelStatus\.label/) + assert.match(view, /if \(!item\.relabelNo\) return \{ code: 'WAIT_RELABEL', label: '待换标' \}/) + assert.match(view, /READY_FOR_OUTBOUND: '已换标', COMPLETED: '已换标'/) + assert.match(view, /#transferRelabelStatus=.*transferRelabelStatusTagType/) + assert.match(view, /#transferStatus=.*transferStatusTagType/) + assert.doesNotMatch(view, /是 · 待绑定换标单/) +}) + +test('仓储转运详情订单概览使用详情接口命名字段回填', () => { + assert.match(view, /v-else-if="props\.service === 'transfer'" class="warehouse-detail-grid warehouse-transfer-overview-grid"/) + assert.doesNotMatch(view, /入库箱数/) + assert.match(view, /'转运单号', '箱数', '海外仓'/) + for (const field of ['transferNo', 'packageCount', 'operatingWarehouseId', 'deliveryMethod', 'needRelabel', 'relabelNo', 'status', 'createdAt']) { + assert.match(view, new RegExp(`selectedDetail\\?\\.${field}`)) + } + assert.match(view, /selectedDetail\?\.destinationWarehouseCode \|\| selectedDetail\?\.fbaWarehouseCode/) + assert.match(view, /transferRelabelStatus\(selectedDetail \|\| \{\}\)/) + assert.match(view, /transferStatusTagType\(selectedDetail\?\.status \|\| ''\)/) +}) + +test('仓储转运和贴标申请详情先展示主数据再串行补充仓库名称', () => { + const loader = view.match(/async function loadDetailPage\(\) \{[\s\S]*?\n\}/)?.[0] || '' + assert.match(loader, /await openDetail\(\{ '0': detailNumber\.value \}\)/) + assert.match(loader, /await loadWarehouses\(\)/) + assert.ok(loader.indexOf('await openDetail') < loader.indexOf('await loadWarehouses')) + assert.match(view, /props\.service === 'labeling' \? await getRelabel\(value\['0'\]\)/) + assert.match(view, /: props\.service === 'disposal' \? await getDiscard\(value\['0'\]\) : await getTransfer\(value\['0'\]\)/) +}) + +test('仓储转运详情展示目的去向和对应目的仓资料并让货物明细铺满', () => { + assert.match(view, />目的去向<\/span>\{\{ destinationTypeLabel\(selectedDetail\?\.destinationType, selectedDetail\?\.fbaWarehouseCode\) \}\}/) + assert.match(view, />FBA 目的仓<\/span>.*selectedDetail\?\.destinationWarehouseCode \|\| selectedDetail\?\.fbaWarehouseCode/) + assert.match(view, />FBA 货件号<\/span>\{\{ selectedDetail\?\.fbaShipmentNo \|\| '—' \}\}/) + for (const field of ['privateDestinationAddress', 'privateDestinationPostalCode', 'privateDestinationContactName', 'privateDestinationContactPhone']) { + assert.match(view, new RegExp(`selectedDetail\\?\\.${field}`)) + } + assert.match(view, /warehouse-service-page--detail \.warehouse-transfer-cargo-card \.warehouse-section-heading,[^}]+\.warehouse-table-scroll\{max-width:none\}/) +}) + +test('仓储转运订单概览使用紧凑左右信息布局', () => { + assert.match(view, /warehouse-detail-grid warehouse-transfer-overview-grid/) + assert.match(view, /warehouse-transfer-overview-grid">
状态<\/span>.*目的去向<\/span>/) + assert.match(view, /class="warehouse-overview-wide">私人仓地址/) + assert.match(view, /warehouse-transfer-overview-grid>\.warehouse-overview-wide\{grid-column:span 2\}/) + assert.match(view, /warehouse-transfer-overview-grid>div\{min-height:26px;padding:2px 0;border:0\}/) + assert.match(view, /warehouse-transfer-overview-grid span\{flex:0 0 auto;min-width:76px\}/) + assert.match(view, /warehouse-transfer-overview-grid\{grid-template-columns:1fr;gap:4px/) +}) + +test('贴标申请详情使用紧凑订单概览并中文展示换标状态', () => { + assert.match(view, /WAIT_APPROVAL: '待审核'/) + assert.match(view, /WAIT_PROCESSING: '待仓库处理'/) + assert.match(view, /relabelFulfillmentLabel\(selectedRelabelDetail\?\.application\.fulfillmentStatus\)/) + assert.match(view, />\.warehouse-detail-header\+\.warehouse-card \.warehouse-detail-grid\{grid-template-columns:repeat\(4,minmax\(180px,1fr\)\)/) +}) + test('PDA 打托完成后客户可从列表或详情确认并查看实测数据', () => { assert.match(view, /item\.__status === 'WAIT_CUSTOMER_CONFIRMATION'/) assert.match(view, /confirmTransferFromList\(item\['0'\]\)/) @@ -189,6 +266,20 @@ test('PDA 打托完成后客户可从列表或详情确认并查看实测数据' assert.doesNotMatch(view, /pallet\.items/) }) +test('仓储转运详情按订单概览、PDA 实测数据、货物明细排序并放大测量照片', () => { + const detailStart = view.indexOf('
') + const detailSource = view.slice(detailStart) + const overviewIndex = detailSource.indexOf('

订单概览

') + const measurementIndex = detailSource.indexOf('

PDA 打托实测数据

') + const cargoIndex = detailSource.indexOf('

货物明细

') + assert.ok(overviewIndex >= 0 && overviewIndex < measurementIndex) + assert.ok(measurementIndex < cargoIndex) + assert.match(view, /class="warehouse-photo-grid warehouse-measurement-photo-grid"/) + assert.match(view, /class="warehouse-photo-item warehouse-measurement-photo-item"/) + assert.match(view, /warehouse-measurement-photo-grid\{flex:1;grid-template-columns:repeat\(auto-fill,minmax\(240px,1fr\)\)\}/) + assert.match(view, /warehouse-measurement-photo-item\{width:100%;height:180px\}/) +}) + test('仓储转运详情自动加载 PDA 测量照片并释放 Blob URL', () => { assert.match(api, /downloadTransferMeasurementPhoto/) assert.match(api, /measurement-photos\/files\/\$\{encodeURIComponent\(fileId\)\}\/download/) @@ -229,6 +320,12 @@ test('仓储转运可在新建页直接提交,也可保存草稿后编辑提 assert.doesNotMatch(view, /

箱唛明细<\/h3>/) }) +test('转运贴标单在PDA确认库存前不误报附件上传失败', () => { + assert.match(view, /if \(!relabelNo\) \{[\s\S]*PDA 入库确认后将自动生成关联换标单/) + assert.match(view, /届时请进入关联换标单详情上传贴标总文件/) + assert.doesNotMatch(view, /if \(!relabelNo\) throw new Error\('转运单已提交,但未返回关联换标单号'\)/) +}) + test('仓储转运基础货物不强制 SKU 和中文品名,只换箱唛不强制旧 SKU', () => { assert.match(view, /商品 SKU\{\{ props\.service === 'transfer' \? '' : ' \*' \}\}/) assert.match(view, /中文品名\{\{ props\.service === 'transfer' \? '' : ' \*' \}\}/)

换标模式我方箱唛新 FBA 箱唛FBA 目的仓任务箱数商品 SKUSKU 换标明细FBA 货件编号贴标要求备注