714 lines
48 KiB
TypeScript
714 lines
48 KiB
TypeScript
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="selectedTab === 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 v-else 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("orderDialogTitle('审核订单', approvalOrder?.inboundCode)")
|
||
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("orderDialogTitle('编辑订单备注', noteOrder?.inboundCode)")
|
||
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('<OrderFulfillmentTreePanel')
|
||
expect(source).not.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); loadStatusCounts()"')
|
||
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"')
|
||
})
|
||
})
|