Files
admin-web/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts
T
2026-09-30 14:01:58 +08:00

713 lines
47 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { unifiedOrderStatusOptions } from '../api/unifiedOrders'
const source = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/pages/UnifiedOrdersView.vue'), 'utf8').replace(/\r\n/g, '\n')
const routeSource = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/routes.ts'), 'utf8').replace(/\r\n/g, '\n')
const densitySource = readFileSync(resolve(process.cwd(), 'src/styles/adminDensity.css'), 'utf8')
describe('UnifiedOrdersView contract', () => {
it('imports old-system workbooks only for the fixed test customer', () => {
expect(source).toContain("can('platform:order:import')")
expect(source).toContain('导入旧系统订单')
expect(source).toContain('固定归属客户')
expect(source).toContain('IT测试公司')
expect(source).toContain('accept=".xlsx"')
expect(source).toContain('legacyImportResult.items.filter((item) => !item.success)')
})
it('exposes desktop pallet printing only for one-click dropship orders', () => {
expect(source).toContain("detail.value?.summary.fulfillmentType === 'ONE_CLICK_DROPSHIP'")
expect(source).toContain("{ key: 'dropship-print', label: '托盘打印', icon: '🖨️' }")
expect(source).toContain("activeDetailTab === 'dropship-print'")
expect(source).toContain('<DropshipPalletPrintPanel')
expect(source).toContain("can('platform:firstleg:inbound:list')")
})
it('loads the reference unit price and lets the backend enforce the combined final lock', () => {
const openDialogSource = source.slice(source.indexOf('async function openFeeConfirmDialog()'), source.indexOf('async function executeConfirmAndSendFee()'))
const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange'))
expect(openDialogSource).toContain('getLatestInboundFee')
expect(sendFeeSource).not.toContain('getLatestInboundFee')
expect(sendFeeSource).toContain('adjustPublishedInboundFee')
expect(source).toContain('价格表参考单价')
expect(source).toContain('本次对客单价')
expect(openDialogSource).toContain("feeConfirmPriceMissing.value = true")
expect(sendFeeSource).toContain('createManualInboundQuote')
expect(source).toContain("feeConfirmPriceMissing ? '未命中'")
expect(source).toContain("feeConfirmPriceMissing ? '人工定价原因'")
expect(source).not.toContain('<dt>固定附加费</dt>')
expect(sendFeeSource).toContain("ElMessage.error(getApiErrorDetails(error, '确认费用发送失败').message)")
})
it('locks measurement changes as soon as the customer confirms the fee', () => {
expect(source).toContain('&& !measurementDetail.value.customerFeeConfirmed')
expect(source).toContain('费用已由客户确认,测量数据已锁定')
})
it('prevents duplicate confirm-and-send chains and reuses one attempt idempotency key set', () => {
const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange'))
expect(sendFeeSource).toContain('if (confirmFeeSending.value) return')
expect(sendFeeSource).toContain('const attempt = currentFeeConfirmAttempt(selectedWeightKg, selectedVolumeCbm, selectedUnitPrice)')
expect(sendFeeSource).toContain('attempt.calculateKey')
expect(sendFeeSource).toContain('attempt.adjustKey')
expect(sendFeeSource).toContain('attempt.confirmKey')
expect(sendFeeSource).toContain('attempt.sendKey')
expect(sendFeeSource).toContain("if (snapshot.feeStatus === 'SENT')")
expect(source).toContain('feeConfirmAttempt = null\n feeConfirmOpen.value = true')
})
it('exposes the product main statuses including dropship palletizing', () => {
expect(unifiedOrderStatusOptions.map((item) => item.label)).toEqual([
'待审核', '待测量数据', '待一件代发组托', '待分柜', '装柜中', '装柜完成', '派送完成', '已完成',
])
for (const legacyLabel of ['待客户确认', '差异处理中', '派送中', '进入分柜']) {
expect(source).not.toContain(legacyLabel)
}
})
it('renders accessible current-status tabs with keyboard navigation', () => {
expect(source).toContain('role="tablist"')
expect(source).toContain('role="tab"')
expect(source).toContain(':aria-selected="selectedStatus === tab.value"')
expect(source).toContain("event.key === 'ArrowRight'")
expect(source).toContain("event.key === 'ArrowLeft'")
expect(source).toContain("event.key === 'Home'")
expect(source).toContain("event.key === 'End'")
})
it('provides labeled prototype-aligned advanced filters backed by real query fields', () => {
for (const key of [
'receivingWarehouseId', 'destinationWarehouseId', 'inboundCode', 'expectedArrivalDate',
'feeReviewStatus', 'containerNo', 'cabinetNo', 'channelName', 'sailingDate', 'loaded', 'fullContainer',
'measurementReviewStatus', 'fbaCode', 'createdRange',
]) expect(source).toContain(`key: '${key}'`)
for (const label of ['定位仓库', '交货仓', '费用审核状态', '是否已配载', '是否整柜', '测量数据审核状态', '商品 SKU 编码']) {
expect(source).toContain(`label: '${label}'`)
}
expect(source).toContain('listAllPlatformWarehouses')
expect(source).toContain("{ status: 'ACTIVE' }")
expect(source).toContain("warehouse.warehouseType === 'DOMESTIC'")
expect(source).toContain("loaded: optionalBoolean(activeFilters.loaded)")
expect(source).toContain("fullContainer: optionalBoolean(activeFilters.fullContainer)")
expect(source).toContain("{ value: 'UNRECORDED', label: '未录入' }")
expect(source).not.toContain("{ value: 'DRAFT', label: '草稿' }")
expect(source).not.toContain("{ value: 'CANCELLED', label: '已取消' }")
expect(source.match(/key: 'inboundCode', type: 'input'/g)).toHaveLength(1)
expect(source).not.toContain("key: 'orderNo', type:")
expect(source).not.toContain('internalOrderStatus: filters.value.internalOrderStatus')
expect(source).not.toContain("key: 'keyword'")
const prototypeFieldOrder = [
'receivingWarehouseId', 'destinationWarehouseId', 'inboundCode', 'expectedArrivalDate',
'feeReviewStatus', 'tenantId', 'containerNo', 'cabinetNo', 'channelName', 'sailingDate', 'loaded',
'fullContainer', 'measurementReviewStatus', 'fbaCode', 'createdRange',
]
const fieldPositions = prototypeFieldOrder.map((key) => source.indexOf(`key: '${key}'`))
expect(fieldPositions).toEqual([...fieldPositions].sort((left, right) => left - right))
expect(source).toContain('preference-key="unified-orders-query-v2"')
})
it('keeps unified filters aligned with the formal query contract and does not expose hold filters', () => {
const filterSource = source.slice(source.indexOf('const primaryQueryFields'), source.indexOf('const columns:'))
for (const key of ['expectedArrivalDate', 'feeReviewStatus', 'measurementReviewStatus', 'fbaCode', 'loaded', 'fullContainer']) {
expect(filterSource).toContain(`key: '${key}'`)
}
expect(filterSource).not.toContain("key: 'holdCargo'")
expect(filterSource).not.toContain("key: 'holdReason'")
expect(filterSource).toContain("key: 'fbaCode', type: 'input', width: 220, label: '商品 SKU 编码'")
expect(filterSource).not.toContain("label: '商品 FBA 编码'")
expect(source).toContain('expectedArrivalDate: activeFilters.expectedArrivalDate || undefined')
expect(source).toContain('feeReviewStatus: activeFilters.feeReviewStatus || undefined')
expect(source).toContain('measurementReviewStatus: activeFilters.measurementReviewStatus || undefined')
expect(source).toContain('fbaCode: trimmed(activeFilters.fbaCode)')
})
it('keeps hold state in the other tab and consumes only frozen server capabilities', () => {
for (const marker of [
'summary.holdFlag', 'summary.currentHoldReason', 'getPlatformInboundHold',
'holdDetail.value?.canHold', 'holdDetail.value?.canRelease', "can('platform:firstleg:inbound:hold')",
'扣货历史', '下载凭证', '订单尚未生成入库单,扣货状态仅可查看',
'uploadPlatformInboundHoldEvidence', 'downloadPlatformInboundHoldEvidence',
'仅支持 TXT、PDF、JPG、PNG,单文件不超过 10MB',
]) expect(source).toContain(marker)
expect(source).toContain("await Promise.all([loadDetail(true), loadOrders()])")
expect(source).not.toContain('detail.summary.holdCargo')
expect(source).not.toContain('detail.summary.holdReason')
expect(source).toContain('evidenceFileId: holdUploadedEvidenceFileId.value')
})
it('uses tabs as the only order-status filter and keeps advanced controls inside QueryToolbar', () => {
expect(source).toContain('mainStatus: selectedStatus.value || undefined')
expect(source).toContain('<template #actions>')
expect(source).toContain(':aria-expanded="showAdvancedFilters"')
expect(source).not.toContain('class="filter-mode-actions"')
expect(source).not.toContain('.filter-mode-actions')
expect(source).not.toContain('请选择订单状态')
})
it('covers real list states and keeps operation column first and left-fixed', () => {
expect(source).toMatch(/\{ key: 'actions', label: '操作', width: \d+, fixed: 'left'/)
expect(source).toContain("key: 'selection', type: 'selection'")
expect(source).not.toContain('批量审核接口待接入')
expect(source).toContain('@selection-change="handleOrderSelectionChange"')
expect(source).toContain('batchApprovePlatformUnifiedOrders')
expect(source).toContain('getPlatformUnifiedOrderBatchApprovalPreview')
expect(source).toContain('中文品名')
expect(source).toContain('确认批量审核')
expect(source).toContain('批量审核通过')
expect(source).toContain('title="订单列表暂时无法加载"')
expect(source).toContain('empty-text="暂无符合条件的订单"')
expect(source).toContain(':loading="loading"')
expect(source).toContain('v-model:page="page"')
expect(source).toContain('v-model:page-size="pageSize"')
const columnSource = source.slice(source.indexOf('const columns ='), source.indexOf('function emptyFilters'))
const expectedColumnKeys = [
'actions', 'inboundCode', 'orderType', 'mainStatus', 'customerName', 'receivingWarehouseName',
'destinationWarehouseName', 'channelName', 'totalPackageCount', 'totalPalletCount', 'requestedPickupAt', 'locationCodes', 'measurementReviewStatus', 'actualGrossWeightKg',
'actualVolumeCbm', 'totalNetWeightKg', 'chargeableWeightKg', 'chargeableVolumeCbm', 'expectedShipDate',
'estimatedVolumeCbm', 'expectedVolumeCbm', 'expectedArrivalAt', 'feeReviewStatus', 'skuNos', 'totalQuantity', 'expressCompany', 'expressTrackingNo',
'shipmentId', 'referenceId', 'containerNo', 'cabinetNo',
'effectiveSailingAt', 'effectivePortArrivalAt', 'customerRemark', 'latestInternalNote', 'createdAt', 'updatedAt',
]
for (const key of expectedColumnKeys) expect(columnSource).toContain(`key: '${key}'`)
for (const uncontractedKey of [
'internalOrderStatus', 'holdCargo', 'companyName', 'pickupMethod', 'associatedFiles', 'createdBy',
]) expect(columnSource).not.toContain(`key: '${uncontractedKey}'`)
const actionsColumnIndex = columnSource.indexOf("key: 'actions'")
const selectionColumnIndex = columnSource.indexOf("key: 'selection'")
const inboundCodeColumnIndex = columnSource.indexOf("key: 'inboundCode'")
expect(actionsColumnIndex).toBeGreaterThanOrEqual(0)
expect(selectionColumnIndex).toBeGreaterThan(actionsColumnIndex)
expect(inboundCodeColumnIndex).toBeGreaterThan(selectionColumnIndex)
expect(columnSource).not.toContain("key: 'coLoadGroup'")
expect(columnSource).not.toContain("key: 'orderNo'")
expect(columnSource).toContain("key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 160, slotName: 'inboundCode'")
expect(columnSource).toContain("key: 'orderType', label: '订单类型', minWidth: 120, slotName: 'orderType'")
expect(source).toContain("if (row.fulfillmentType === 'ONE_CLICK_DROPSHIP') return '一件代发'")
expect(source).toContain("if (row.businessType === 'FBA' || row.businessType === 'FBA_SEA') return 'FBA 头程'")
expect(source).toContain("if (row.businessType === 'OVERSEAS_SEA') return '海外中转'")
expect(columnSource).toContain("{ key: 'mainStatus', prop: 'mainStatus', label: '订单主状态'")
expect(columnSource).not.toContain("key: 'loadingStatus'")
expect(columnSource).not.toContain("key: 'businessDate'")
expect(columnSource).not.toContain("label: '业务日期'")
expect(columnSource).not.toContain("label: '配载状态'")
expect(columnSource).not.toContain("key: 'recommendedPalletType'")
expect(columnSource).toContain("key: 'requestedPickupAt', prop: 'requestedPickupAt', label: '预约提货时间'")
expect(columnSource.match(/defaultVisible: false/g)).toHaveLength(13)
expect(columnSource).toContain("key: 'expectedArrivalAt', prop: 'expectedArrivalAt', label: '预计到货时间', minWidth: 170, slotName: 'expectedArrival' }")
expect(columnSource).toContain("key: 'estimatedVolumeCbm', prop: 'estimatedVolumeCbm', label: '预估体积(m³)', minWidth: 120, slotName: 'estimatedVolume' }")
expect(columnSource).toContain("key: 'expectedVolumeCbm', prop: 'expectedVolumeCbm', label: '下单体积(m³)', minWidth: 130, slotName: 'field' }")
const planningColumnPositions = [
columnSource.indexOf("key: 'expectedArrivalAt'"),
columnSource.indexOf("key: 'estimatedVolumeCbm'"),
columnSource.indexOf("key: 'expectedVolumeCbm'"),
columnSource.indexOf("key: 'actualVolumeCbm'"),
]
expect(planningColumnPositions).toEqual([...planningColumnPositions].sort((left, right) => left - right))
expect(source).toContain("can('platform:order:declaration:edit')")
expect(source).toContain('updatePlatformUnifiedOrderPlanning(String(row.tenantId), String(row.orderId)')
expect(source).toContain('expectedVersion: row.orderVersion!')
expect(source).toContain('type="datetime"')
expect(source).toContain('value-format="YYYY-MM-DDTHH:mm:ss"')
expect(source).toContain('format="YYYY-MM-DD HH:mm"')
expect(source).toContain('ElMessage.error({')
expect(source).toContain('Request ID ${details.requestId}')
expect(source).not.toContain('class="planning-error"')
expect(columnSource).toContain("key: 'effectiveSailingAt', prop: 'effectiveSailingAt', label: '开船时间', minWidth: 180, slotName: 'field' }")
expect(columnSource).toContain("key: 'effectivePortArrivalAt', prop: 'effectivePortArrivalAt', label: '到港时间', minWidth: 180, slotName: 'field' }")
expect(source).toContain('preference-key="unified-orders-table-v2"')
expect(source).toContain("property === 'requestedPickupAt' && (row.pickupMethod !== 'PICKUP' || !value)")
expect(source).toContain("property === 'skuNos' ? value.join('、')")
expect(source).not.toContain('<dt>推荐托盘类型</dt>')
expect(source).toContain("estimated === true ? '预计 ' : ''")
expect(source).toContain('row.transport?.effectiveSailingAtEstimated')
expect(source).toContain('row.transport?.effectivePortArrivalAtEstimated')
expect(source).not.toContain("transport.sailingAt")
expect(source).not.toContain("transport.portArrivalAt")
expect(source).toMatch(/\.table-card \{[^}]*overflow: hidden;/)
expect(source).toContain('slotName: \'measurementReviewStatus\'')
expect(source).toContain('unifiedOrderReviewStatusLabel(value, \'未录入\')')
expect(source).toContain("{ key: 'customerRemark', prop: 'customerRemark', label: '客户备注'")
expect(source).toContain("{ key: 'latestInternalNote', prop: 'latestInternalNote', label: '内部备注'")
expect(source.match(/<LineClampText v-if="column\.key === 'remark'"/g)).toHaveLength(3)
expect(source).toContain("detail.summary.internalRemark || detail.summary.latestInternalNote || '—'")
})
it('keeps the title, search, errors and results in the integrated list', () => {
expect(source).toContain('<ListPageLayout ariaLabel="统一订单列表">')
expect(source).toContain('<template #filters>')
expect(source).toContain('<template #notices>')
expect(source).toContain('<template #default>')
expect(source).toContain(':results-visible="!loadError"')
expect(source).toContain('<template #toolbar-start><h1>订单列表</h1></template>')
expect(source).toContain('appearance="integrated"')
expect(source).not.toContain('class="orders-load-error"')
expect(source).not.toContain('.orders-load-error { margin-top: 0; }')
})
it('counts detail in the three directly visible row actions', () => {
expect(source).toContain("{ key: 'detail', label: '详情', priority: 500 }")
expect(source).toContain('...unifiedOrderSecondaryActions(row).map')
expect(source).toContain('<RowActions text-actions class="density-action" :actions="unifiedOrderRowActions(row as UnifiedOrderResource)"')
expect(source).toContain("if (action === 'approve') return '审核'")
expect(source).not.toContain("if (action === 'approve') return '通过'")
})
it('opens smart loading from the order table toolbar with its own permission', () => {
expect(source).toContain("can('platform:firstleg:smart-loading:list')")
expect(source).toContain('#toolbar-actions')
expect(source).toContain('@click="smartLoadingOpen = true"')
expect(source).toContain('<SmartLoadingDialog v-model="smartLoadingOpen" />')
})
it('uses one asynchronous export entry for either selected orders or the applied filter snapshot', () => {
expect(source).toContain("exportType: 'DOMESTIC_WAREHOUSE_ORDER_LIST'")
expect(source).toContain("parameters: selectedOrderNos.length")
expect(source).toContain('? { selectedOrderNos }')
expect(source).toContain(': exportFilterParameters()')
expect(source).toContain("can('platform:order:export') && can('platform:download:list')")
expect(source).not.toContain('page: page.value,\n size: pageSize.value,\n })\n const task = await createPlatformExportTask')
})
it('states only the domestic warehouse and channel qualification without local destination or split blocking', () => {
const createSource = source.slice(source.indexOf('function openCoLoadCreate'), source.indexOf('async function batchApproveOrders'))
const dialogSource = source.slice(source.indexOf('v-model="coLoadCreateOpen"'), source.indexOf('<AppDrawer v-model="relationOpen"'))
expect(dialogSource).toContain('同一国内仓、同一渠道')
expect(dialogSource).toContain('目的仓或目的港可以不同')
expect(dialogSource).toContain('资格由服务端按完整入仓片段校验')
expect(dialogSource).not.toContain('拆子单')
expect(createSource).not.toContain('destinationWarehouse')
expect(createSource).not.toContain('destinationPort')
expect(createSource).not.toContain('splitStatus')
expect(createSource).not.toContain('relation')
expect(createSource).toContain('createPlatformCoLoadGroup(selectedOrders.value.map((row) => String(row.orderId)))')
expect(source).toContain(':disabled="loading || !canCreateCoLoadGroup"')
expect(source).toContain('所选订单的平台收货仓不同:')
expect(source).toContain('所选订单的渠道不同:')
expect(source).toContain('row.channelCode!.trim()')
expect(source).not.toContain('String(row.channelName || \'\').trim()')
expect(source).not.toContain('请至少勾选 2 票订单后再设为同柜组')
expect(source).not.toContain('当前仅选择 1 票:')
expect(source).toContain('以下入仓码已有同柜组:')
expect(source).toContain('selectable: () => !batchApprovalSaving.value && !coLoadSaving.value')
expect(source).not.toContain('isCoLoadRowSelectable')
expect(source).not.toContain('<small v-if="coLoadSelectionProblem"')
})
it('uses inbound code as the only list identifier and opens a business-only group dialog', () => {
expect(source).not.toContain("{ key: 'coLoadGroup', label: '同柜组'")
expect(source).not.toContain('<template #orderNo=')
expect(source).toContain('<template #inboundCode="{ row }">')
expect(source).toContain("inboundCode || '待生成入仓码'")
expect(source).toContain('class="coload-tag-trigger"')
expect(source).toContain('coLoadGroupSummary(coLoadGroup(row as UnifiedOrderResource)!)')
expect(source).not.toContain('coLoadDetailGroup')
expect(source).not.toContain('<span v-else>—</span></template>\n <template #orderStatus')
expect(source).toContain('v-for="member in coLoadDetailMembers"')
expect(source).toContain('<thead><tr><th>入仓码</th></tr></thead>')
expect(source).toContain('{{ member.inboundCode }}')
expect(source).toContain('同柜组成员入仓码尚未返回,请刷新后重试')
expect(source).toContain('coLoadDetailHasMissingInboundCodes')
expect(source).not.toContain('visibleInboundCode')
expect(source).not.toContain('visibleOrderNo')
expect(source).not.toContain('<th>订单编码</th>')
expect(source).not.toContain('客户 ID')
expect(source).toContain("coLoadDetailSummary?.status === 'ACTIVE'")
expect(source).toContain('解除同柜组')
expect(source).toContain('cancelPlatformCoLoadGroup(inboundCode')
expect(source).toContain('<ApiErrorMeta :details="coLoadCancelError"')
})
it('does not expose a generic relationship probe in list rows', () => {
expect(source).not.toContain('>履约详情</el-button>')
expect(source).not.toContain('>关联数据</el-button>')
expect(source).not.toContain('openInboundRelationship')
expect(source).not.toContain('inboundRelationshipDialogOpen')
expect(source).not.toContain('<InboundCodeRelationshipDialog')
})
it('keeps editable query values separate from the applied list request', () => {
expect(source).toContain('const appliedFilters = ref<FilterModel>(emptyFilters())')
expect(source).toContain('const activeFilters = appliedFilters.value')
expect(source).toContain('appliedFilters.value = cloneFilters(filters.value)')
expect(source).toContain('appliedFilters.value = emptyFilters()')
expect(source).not.toContain('filters.value.mainStatus')
})
it('keeps only confirmed business actions as explicit boundaries', () => {
expect(source).toContain('nextActionBoundary')
expect(source).not.toContain('class="action-boundary"')
expect(source).toContain("can('platform:firstleg:inbound:measure:view')")
expect(source).toContain('openMeasurement')
expect(source).toContain('<InboundMeasurementDialog')
expect(source).toContain('measurementDialogOpen.value = true')
expect(source).toContain('@updated="handleMeasurementUpdated"')
expect(source).toContain('@submitted="handleMeasurementSubmitted"')
expect(source).not.toContain('<InboundMeasurementPanel')
expect(source).not.toContain('class="inbound-task-dialog"')
expect(source).not.toContain("name: 'inbound-order-detail'")
expect(source).not.toContain('openInboundFromDetail')
expect(source).not.toContain('inbound-code-link')
expect(source).not.toContain('待接入:智能分柜')
expect(source).not.toContain('updateMainStatus')
expect(source).not.toContain('人工进入分柜')
expect(source).not.toContain('performPlatformUnifiedOrderAction')
expect(source).not.toContain('/actions/')
})
it('treats WAIT_SPLIT as a display/filter state without inventing a split action', () => {
expect(source).toContain('...unifiedOrderStatusOptions')
expect(source).toContain('function nextActionBoundary(row: UnifiedOrderResource)')
expect(source).toContain("return row.nextAction === 'CONFIRM_MEASUREMENT' ? '待完成测量' : ''")
expect(source).not.toContain('人工进入分柜')
expect(source).not.toContain("action === 'split'")
expect(source).not.toContain("nextAction === 'SMART_SPLIT'")
})
it('approves pending review orders without exposing an order carrier assignment', () => {
expect(source).toContain("row.nextAction === 'APPROVE_ORDER'")
expect(source).toContain("can('platform:order:approve')")
expect(source).toContain('await approvePlatformUnifiedOrder(tenantId, orderId)')
expect(source).toContain('title="审核订单"')
expect(source).toContain('confirm-text="审核通过"')
expect(source).not.toContain('assignPlatformUnifiedOrderCarrier')
expect(source).not.toContain('listPlatformPartners')
expect(source).not.toContain('指派承运商')
expect(source).not.toContain('待平台指派')
})
it('provides unified detail, history and internal note states', () => {
expect(source).toContain('getPlatformUnifiedOrder')
expect(source).toContain('aria-label="状态历史"')
expect(source).toContain('aria-label="内部备注"')
expect(source).toContain('订单详情暂时无法加载')
expect(source).toContain('一、客户下单数据')
expect(source).toContain('二、外箱规数据')
expect(source).toContain('三、实际测量数据')
expect(source).toContain('measurementCollapsed = reactive({ customer: false, outer: false, actual: false })')
expect(source).toContain('function toggleMeasurementSection(section: keyof typeof measurementCollapsed)')
expect(source).toContain(':aria-expanded="!measurementCollapsed.customer"')
expect(source).toContain(':aria-expanded="!measurementCollapsed.outer"')
expect(source).toContain(':aria-expanded="!measurementCollapsed.actual"')
expect(source).toContain('class="measurement-section-summary"')
expect(source).toContain('<ChevronUp v-if="!measurementCollapsed.customer"')
expect(source).toContain('<ChevronDown v-else :size="18" />')
expect(source).toContain("{ key: 'girthCm', label: '箱规周长(cm)' }")
expect(source).toContain('label="#" prop="itemNo"')
expect(source).toContain("const actualMeasuredColumns = measuredColumns.filter((column) => column.key !== 'itemNo')")
expect(source).toContain('v-for="column in actualMeasuredColumns"')
expect(source).toContain('仓库修改外箱(长)')
expect(source).toContain('仓库修改外箱(宽)')
expect(source).toContain('仓库修改外箱(高)')
expect(source.match(/class="measurement-outer-dimension-input"/g)).toHaveLength(3)
expect(source.match(/label="仓库修改外箱([长宽高])" width="190" align="center"/g)).toHaveLength(3)
expect(source).toContain('.measurement-outer-dimension-input { width: 128px; max-width: 100%; }')
expect(source).toContain('saveWarehouseOuterDimensions')
expect(source).toContain("'PREPARING', 'WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'")
expect(source.match(/v-if="measurementStrategyEditable"/g)).toHaveLength(4)
expect(source).toContain('updateMeasurementOuterDimensions')
expect(source).not.toContain('await saveInboundMeasurementDraft(String(summary.tenantId), String(summary.inboundId)')
expect(source).not.toContain('请至少输入一项需要修改的外箱尺寸')
expect(source).toContain(".map((value) => value == null || value === '' ? null : Number(value))")
expect(source).toContain('相关数值已重新计算')
expect(source).toContain('handleUnifiedOrderRowAction($event, row as UnifiedOrderResource)')
expect(source).toContain(':more-label="moreUnifiedOrderActionsLabel(row as UnifiedOrderResource)"')
expect(source).toContain("can('platform:order:note')")
expect(source).toContain('await loadOrders()')
expect(source).toContain('orderStatusLabel(detail.summary.internalOrderStatus || detail.summary.mainStatus)')
expect(source).toContain('feeReviewLabel(detail.summary.feeReviewStatus)')
expect(source).toContain('unifiedOrderLoadingStatusLabel(detail.summary.transport?.loadingStatus)')
expect(source).not.toContain("value.replaceAll('_', ' ')")
})
it('keeps customer remarks read-only and edits the three collaboration remarks together', () => {
expect(source).toContain('title="编辑订单备注"')
expect(source).toContain('label="客户备注(只读)"')
expect(source).toContain(':model-value="noteOrder?.customerRemark')
expect(source).toContain('v-model="noteForm.internalRemark"')
expect(source).toContain('v-model="noteForm.warehouseRemark"')
expect(source).toContain('v-model="noteForm.customerServiceRemark"')
expect(source).toContain('await updatePlatformUnifiedOrderRemarks')
})
it('keeps the customer order package count independent from measured inbound totals', () => {
expect(source).toContain("{ source: '客户下单数据', count: customerTotals.count")
expect(source).not.toContain("{ source: '客户下单数据', count: summary?.totalPackageCount")
})
it('uses the inbound measurement girth formula for cargo dimensions', () => {
expect(source).toContain('formatNumber(length + 2 * (width + height))')
expect(source).not.toContain('formatNumber(2 * (width + height))')
})
it('shows the matched dimension addition and enabled multiplier in calculation choices', () => {
expect(source).toContain('getCustomerDimensionConfig')
expect(source).toContain('matchingDimensionRule')
expect(source).toContain('|| rules[rules.length - 1]')
expect(source).toContain('`原尺寸 + ${Number(values[0]) * 2}cm(单边 +${values[0]}cm)`')
expect(source).toContain('`原尺寸 × ${rule.lengthFactor}`')
expect(source).toContain('label="箱规尺寸" value="ADD"')
expect(source).toContain('label="箱规系数" value="MULTIPLY"')
})
it('keeps preview modes independent and persists outer-carton strategy through its dedicated endpoint', () => {
expect(source).toContain("measurementSectionModes = reactive<Record<MeasurementSection, MeasurementCalculationMode>>")
expect(source).toContain(':model-value="measurementSectionModes.customer"')
expect(source).toContain(':model-value="measurementSectionModes.outer"')
expect(source).toContain(':model-value="measurementSectionModes.actual"')
expect(source).toContain("updateMeasurementSectionMode('customer', $event)")
expect(source).toContain("updateMeasurementSectionMode('actual', $event)")
expect(source).not.toContain("updateMeasurementSectionMode('outer', $event)")
expect(source.match(/@change="selectAndSaveOuterMeasurementMode\(\$event\)"/g)).toHaveLength(1)
expect(source).not.toContain('@visible-change="handleOuterModeDropdownVisibleChange($event)"')
expect(source.match(/:disabled="Boolean\(measurementStrategySavingId\) \|\| !measurementStrategyEditable"/g)).toHaveLength(1)
expect(source).toContain('const measurementStrategyEditable = computed')
expect(source).toContain('费用已由客户确认,测量数据已锁定')
expect(source).toContain('if (!measurementStrategyEditable.value)')
expect(source).toContain('const previousMode = measurementSectionModes.outer')
expect(source).toContain('if (!saved) measurementSectionModes.outer = previousMode')
expect(source).not.toContain('handleOuterModeDropdownVisibleChange')
expect(source).toContain('const saved = await updateMeasurementCalculationMode(null, mode, ruleId)')
expect(source).toContain('calculatedMeasurementRow(row, measurementSectionModes.customer, true)')
expect(source).toContain('calculatedMeasurementRow(source, measurementSectionModes.outer)')
expect(source).toContain('calculatedMeasurementRow(row as unknown as Record<string, unknown>, measurementSectionModes.actual, false, false)')
expect(source).toContain("return Number(hasOuterOverride && override != null && override !== '' ? override : value)")
expect(source).toContain("const hasOuterOverride = useWarehouseOuterOverrides && !customerSource")
expect(source).not.toContain("!customerSource && mode !== 'ACTUAL'")
expect(source).toContain('rawDimensions.map((value) => value + rule.edgeAdditionCm * 2)')
expect(source).toContain('matchedSpecificationCoefficient(chargeRule, calculationSource)')
expect(source).toContain(".find((item) => item.id === boundChargeRuleId && item.status === 'ACTIVE')")
expect(source).toContain('listCustomerChargeRules(customerId, tenantId)')
expect(source).toContain('matchedDimensions.map((value) => roundedUp(value * specificationCoefficient, 3))')
expect(source).toContain('outerLengthCm: dimensions[0]')
expect(source).toContain('outerWidthCm: dimensions[1]')
expect(source).toContain('outerHeightCm: dimensions[2]')
expect(source).not.toContain('[chargeRule.lengthFactor, chargeRule.widthFactor, chargeRule.heightFactor]')
expect(source).not.toContain('column.key === \'coefficientMode\'')
expect(source).not.toContain('@change="updateMeasurementCalculationMode(null, $event)"')
})
it('confirms customer billing from the saved outer-carton totals', () => {
expect(source).toContain('const outerTotals = measurementTotals(outerMeasurementRows.value)')
expect(source).toContain("ElMessage.warning('外箱规数据不完整,请先保存外箱规尺寸和计算规则')")
expect(source).toContain("weightSource: 'ACTUAL'")
expect(source).toContain("volumeSource: 'ACTUAL'")
expect(source).not.toContain('测量审核通过后才能确认并发送费用')
expect(source).not.toContain("reviewStatus !== 'APPROVED'")
expect(source).toContain('const hasActualMeasurementData = computed(() => (measurementDetail.value?.items?.length ?? 0) > 0)')
expect(source).toContain("暂无实际测量数据,不能确认并发送费用")
expect(source).not.toContain(':disabled="confirmFeeSending || measurementLoading || !hasActualMeasurementData"')
expect(source).toContain('selectAndSaveOuterMeasurementMode($event)')
expect(source).toContain('const saved = await updateMeasurementCalculationMode(null, mode, ruleId)')
expect(source).toContain('coefficientMode: mode')
expect(source).toContain('const savedDraft = await updateMeasurementCalculationStrategy')
expect(source).toContain('version: savedDraft.version')
expect(source).toContain('waitForSavedMeasurementVersion(')
expect(source).toContain('Number(refreshed.version) >= savedVersion')
expect(source).toContain('setTimeout(resolve, 250)')
expect(source).toContain("chargeRuleId: mode === 'MULTIPLY' ? activeRule!.id : null")
expect(source).toContain('calculatedOuterLengthCm: null')
expect(source).not.toContain('计算策略保存后回读不一致,请刷新后重试')
expect(source).toContain('已保存并绑定计算规则,体积已重新计算')
expect(source).toContain('const measuredGrossWeight = Number(measurementDetail.value?.actualGrossWeightKg)')
expect(source).toContain('const measuredNetWeight = Number(measurementDetail.value?.actualNetWeightKg)')
expect(source).not.toContain('const actualVolume = Number(measurementDetail.value?.actualVolumeCbm)')
expect(source).toContain('feeConfirmWeight.value = Number(actualTotals.gross) > 0')
expect(source).toContain('feeConfirmVolume.value = Number(actualTotals.volume) > 0')
expect(source).toContain("{ source: '实际测量数据', count: actualTotals.count, weight: actualWeight, volume: actualVolume }")
const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange'))
expect(sendFeeSource).not.toContain('saveInboundMeasurementDraft')
expect(sendFeeSource).not.toContain('updateMeasurementCalculationMode')
expect(source.slice(source.indexOf('async function openFeeConfirmDialog()'), source.indexOf('async function executeConfirmAndSendFee()'))).toContain('getLatestInboundFee')
})
it('clears an invalidated measurement loading state before refreshing order details', () => {
const detailLoaderSource = source.slice(source.indexOf('async function loadDetail(force = false)'), source.indexOf('watch([page, pageSize]'))
expect(detailLoaderSource).toMatch(/measurementRequestSerial \+= 1\r?\n\s+measurementLoading\.value = false/)
})
it('restores the saved outer-carton rule and server-calculated dimensions on re-entry', () => {
expect(source).toContain('const savedModes = [...new Set(response.items.map((item) => item.coefficientMode)')
expect(source).toContain('measurementSectionModes.outer = savedModes.length === 1')
expect(source).toContain('? savedModes[0]!')
expect(source).toContain("configuredMode === 'ADD' ? 'ADD' : 'ACTUAL'")
expect(source).toContain('row.calculatedOuterLengthCm')
expect(source).toContain('measurementCalculationModeForRow(source) === measurementSectionModes.outer')
expect(source).toContain("String(row.calculationRuleVersion || '').startsWith('WAREHOUSE_OUTER_OVERRIDE_')")
expect(source).toContain('warehouseOuterOverride')
expect(source).toContain('Math.abs(Number(value) - Number(recalculatedDimensions[index])) < 0.001')
expect(source).toContain('lengthCm: Number(savedDimensions[0])')
})
it('binds dimension and weight evidence to their formal server records without mixing galleries', () => {
expect(source).toContain('<el-table :data="actualMeasurementRows" row-key="id"')
expect(source).toContain('<el-table-column label="箱号" prop="boxCode"')
expect(source).toContain("row.boxCode || '—'")
expect(source).toContain("`箱号 ${row.boxCode || row.itemNo || '箱规编码待补充'} 的尺寸图片`")
expect(source).not.toContain('row.boxCode || row.itemNo || row.id')
expect(source).toContain('label="尺寸图片"')
expect(source).toContain(':file-id="row.intelligentImageFileId"')
expect(source).toContain('openMeasurementEvidence(row.intelligentImageFileId')
expect(source).toContain('v-for="weightResult in measurementDetail.weightResults"')
expect(source).toContain(':file-id="weightResult.fileId"')
expect(source).toContain('openMeasurementEvidence(weightResult.fileId')
expect(source).toContain('measurementDetail?.actualGrossWeightKg')
expect(source).toContain('weightResult.grossWeightKg')
expect(source).toContain('weightResult.netWeightKg')
expect(source).toContain("weightResult.palletType || '未记录类型'")
expect(source).not.toContain('weightResult.palletCode')
expect(source).not.toContain('measurementImageUrls')
expect(source).not.toContain('const fileIds = [...measurementDetail.value.items')
})
it('keeps authenticated evidence loading, original preview, permission and error states', () => {
expect(source).toContain('<MeasurementEvidenceThumbnail')
expect(source).toContain("downloadWeightImage(String(summary.tenantId), String(summary.inboundId), fileId)")
expect(source).toContain('measurementPreviewError')
expect(source).toContain('测量原图暂时无法读取')
expect(source).toContain("can('platform:firstleg:inbound:measure:view')")
})
it('shows parent-child markers only for related rows and keeps the aggregate in detail', () => {
expect(source).not.toContain("type: 'expand'")
expect(source).toContain('v-if="(row as UnifiedOrderResource).relation"')
expect(source).toContain('父单 · ${row.relation.childCount} 个子单')
expect(source).toContain('子单 · 第 ${row.relation.splitSequence} 次')
expect(source).toContain('title="父子订单关联"')
expect(source).toContain('@click="openOrderRelation(row as UnifiedOrderResource)"')
expect(source).toContain("{ key: 'fulfillment', label: '拆分履约', icon: '🧩' }")
expect(source).toContain("activeDetailTab === 'fulfillment'")
expect(source).toContain('根单、已完成分片与当前余量')
})
it('distinguishes overflow action buttons by row and keeps a stable missing-field fallback', () => {
expect(source).toContain('function moreUnifiedOrderActionsLabel(row: UnifiedOrderResource)')
expect(source).toContain("String(row.inboundCode || '').trim() || '当前入仓单'")
expect(source).not.toContain('compatibleOrder.orderNo')
expect(source).not.toContain('aria-label="更多订单操作"')
expect(source).not.toContain('title="更多操作"')
})
it('batch prints stable carton labels from the list toolbar instead of adding another row action', () => {
expect(source).toContain('downloadInboundCartonLabelsBatch')
expect(source).toContain('const selectedCartonLabelOrders = computed')
expect(source).toContain('@click="printSelectedCartonLabels"')
expect(source).toContain('批量箱唛-${selectedCartonLabelOrders.value.length}单.pdf')
expect(source).not.toContain("type UnifiedOrderRowAction = 'note' | 'approve' | 'printCartonLabels'")
})
it('renders order detail as a routed full page instead of a drawer', () => {
expect(source).toContain('<template v-if="isDetailRoute">')
expect(source).toContain('class="order-detail-header density-detail-header"')
expect(source).toContain('aria-label="订单详情模块"')
expect(source).toContain("{ key: 'basic', label: '基本信息'")
expect(source).toContain("{ key: 'measurement', label: '测量数据'")
expect(source).not.toContain('detailOpen')
expect(source).toContain('watch(detailRouteKey,')
expect(source).toContain('requestKey !== detailRouteKey.value')
})
it('keeps the basic information compact and maps available order fields', () => {
expect(source).toContain('grid-template-columns: repeat(4, minmax(0, 1fr))')
for (const label of ['业务类型', '买单 / 退税', '提货方式', '配载状态', '目的仓类型', '客户申报净重', '客户申报总体积', '商检验', '提货联系人', '目的地地址']) {
expect(source).toContain(`<dt>${label}</dt>`)
}
expect(source).toContain("['actualVolumeCbm', 'chargeableVolumeCbm'].includes(property)")
expect(source).toContain("partyValue(detail.order.parties, 'SENDER', 'phone')")
expect(source).toContain("partyValue(detail.order.parties, 'RECEIVER', 'phone')")
expect(source).not.toContain("partyValue(detail.order.parties, 'CONSIGNEE'")
expect(source).toContain("cargoTotal(detail.order.cargoes, 'netWeightKg')")
expect(source).toContain('InboundFeePanel')
expect(source).toContain('InboundFeeDetailManager')
expect(source).toContain(':customer-id="String(detail.summary.tenantId)"')
expect(source).toContain('compact')
expect(source).toContain('display-only')
expect(source).toContain(':key="`fee-${normalizedInboundId}-${feePanelRevision}`"')
expect(source).toContain('feePanelRevision.value += 1')
expect(source).not.toContain('<h3 class="fee-section-title">头程自动初算</h3>')
expect(source).toContain('其他费用明细')
expect(source).toContain('@changed="loadDetail(true)"')
expect(source).toContain('firstLegTransportModeLabel(detail.order.order?.transportMode)')
expect(source).toContain('firstLegPickupMethodLabel(detail.order.order?.pickupMethod)')
expect(source).toContain('firstLegDestinationTypeLabel(detail.order.order?.destinationType)')
})
it('does not mount fee consumers until a real inboundId exists', () => {
const feeSection = source.slice(source.indexOf("activeDetailTab === 'fees'"), source.indexOf("activeDetailTab === 'history'"))
expect(source).toContain('const normalizedInboundId = computed(() => normalizedIdentifier(detail.value?.summary.inboundId))')
expect(feeSection).toContain('v-if="!normalizedInboundId" description="订单尚未生成入库单,暂无入仓费用"')
expect(feeSection.match(/:inbound-id="normalizedInboundId"/g)).toHaveLength(2)
expect(feeSection).not.toContain('String(detail.summary.inboundId)')
})
it('keeps downstream work out of each order detail after the order is linked to a container', () => {
expect(source).not.toContain('UnifiedOrderWorkflowPanel')
expect(source).not.toContain("key: 'workflow'")
expect(source).not.toContain('后半程作业')
expect(source).not.toContain("activeDetailTab === 'workflow'")
expect(routeSource).not.toContain('unified-order-workflow')
})
it('only exposes declaration editing before loading and marks channel changes', () => {
expect(source).toContain("new Set(['DRAFT', 'REJECTED', 'SUBMITTED', 'APPROVED'])")
expect(source).toContain("planningEditableOrderStatuses.has(row.internalOrderStatus || '')")
expect(source).toContain('v-if="canEditPlanning(row as UnifiedOrderResource)"')
expect(source).toContain("new Set<UnifiedOrderMainStatus>(['PENDING_REVIEW', 'WAIT_MEASURE', 'WAIT_SPLIT'])")
expect(source).toContain('declarationEditableStatuses.has(detail.value.summary.mainStatus)')
expect(source).not.toContain("detail.value.summary.mainStatus === 'WAIT_MEASURE'")
expect(source).toContain("can('platform:order:declaration:edit')")
expect(source).toContain('编辑客户申报资料')
expect(source).toContain('<OrderDeclarationEditorDialog')
expect(source).toContain('曾变更渠道')
expect(source).toContain('@updated="handleDeclarationUpdated"')
})
it('supports the measurement-review deep link with an inbound-code lookup and recoverable return path', () => {
expect(routeSource).toContain("name: 'unified-order-detail-by-inbound-code'")
expect(routeSource.indexOf("path: 'orders/inbound/:inboundCode'")).toBeLessThan(routeSource.indexOf("path: 'orders/:orderId'"))
expect(source).toContain("route.query.tab === 'measurement'")
expect(source).toContain("route.query.from === 'measurement-review'")
expect(source).toContain('getPlatformInboundCodeAssociation(inboundCode)')
expect(source).toContain("activeDetailTab.value = 'measurement'")
expect(source).toContain('await loadMeasurementDetails()')
expect(source).toContain("router.push({ name: 'inbound-measurement-reviews' })")
expect(source).toContain('router.back()')
expect(source).toContain('可能无权查看或目标已不存在')
expect(source).toContain('返回测量审核列表')
})
it('keeps detail tabs compact on one horizontally scrollable row', () => {
expect(source).toContain('class="detail-tabs density-tabs"')
expect(densitySource).toContain('overflow-x: auto; overflow-y: hidden;')
expect(densitySource).toContain('min-height: var(--admin-tab-height)')
expect(source).not.toContain('grid-template-columns: repeat(6, minmax(124px, 1fr))')
expect(source).not.toContain('.detail-tabs { grid-template-columns: repeat(6, 132px); }')
})
it('matches the prototype table grid and distinguishes container identifiers', () => {
expect(source).toContain('row-key="orderId" border stripe')
expect(source).toContain('<el-table :data="customerMeasurementRows" border stripe')
expect(source).toContain('<el-table :data="outerMeasurementRows" border stripe')
expect(source).toContain('<el-table :data="actualMeasurementRows" row-key="id" border stripe')
expect(source).toContain('<el-table :data="customerMeasurementRows" border stripe empty-text="暂无客户下单数据"')
expect(densitySource).toContain('--el-table-border-color: var(--admin-table-border)')
expect(densitySource).toContain('--el-table-row-hover-bg-color: var(--admin-row-hover)')
expect(densitySource).toContain('.el-table__body tr.el-table__row--striped')
expect(source).toContain("label: '真实集装箱号'")
expect(source).toContain("label: '内部柜次号'")
expect(source).toContain('<dt>真实集装箱号</dt>')
expect(source).toContain('<dt>内部柜次号</dt>')
})
it('uses outlined semantic buttons for row business actions while keeping detail navigation explicit', () => {
const rowActions = source.slice(source.indexOf('<template #actions'), source.indexOf('<template #inboundCode'))
expect(rowActions).toContain('<RowActions')
expect(rowActions).toContain(':actions="unifiedOrderRowActions(row as UnifiedOrderResource)"')
expect(rowActions).toContain('@action="handleUnifiedOrderRowAction($event, row as UnifiedOrderResource)"')
expect(source).toContain('<el-button link class="back-button density-back"')
})
})