Files
client-web/src/modules/first-leg/views/FirstLegOrdersView.test.mjs
T
2026-10-20 18:44:53 +08:00

788 lines
54 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const source = readFileSync(new URL('./FirstLegOrdersView.vue', import.meta.url), 'utf8')
const tableSource = readFileSync(new URL('../../../components/common/DataTable.vue', import.meta.url), 'utf8')
const querySource = readFileSync(new URL('../../../components/common/QueryToolbar.vue', import.meta.url), 'utf8')
const clientUiSource = readFileSync(new URL('../../../styles/client-ui.css', import.meta.url), 'utf8')
const routerSource = readFileSync(new URL('../routes.ts', import.meta.url), 'utf8')
const editorShellSource = readFileSync(new URL('../components/FirstLegOrderEditorShell.vue', import.meta.url), 'utf8')
const progressSource = readFileSync(new URL('../components/OrderProgressPanel.vue', import.meta.url), 'utf8')
const customsDialogSource = readFileSync(new URL('../components/CustomsDocumentsDialog.vue', import.meta.url), 'utf8')
const customsApiSource = readFileSync(new URL('../api/customs.ts', import.meta.url), 'utf8')
const orderStoreSource = readFileSync(new URL('../store/firstLegOrders.ts', import.meta.url), 'utf8')
const createHubSource = readFileSync(new URL('./FirstLegOrderCreateHubView.vue', import.meta.url), 'utf8')
test('routes the confirmed first-leg path to the implemented order view', () => {
assert.match(routerSource, /path: 'orders\/first-leg'[\s\S]*?component: \(\) => import\('\.\/views\/FirstLegOrdersView\.vue'\)[\s\S]*?release: 'active'/)
})
test('opens order creation as a dedicated protected page while keeping draft editing in a large dialog', () => {
assert.match(routerSource, /path: 'orders\/first-leg\/create'[\s\S]*?name: 'first-leg-order-create'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:create'[\s\S]*?keepAlive: false/)
assert.match(routerSource, /name: 'first-leg-order-create'[\s\S]*?FirstLegOrderCreateHubView\.vue/)
assert.match(createHubSource, /<FirstLegOrdersView v-if="activeMode === 'standard'" create-page \/>/)
assert.match(source, /router\.push\(\{ name: 'first-leg-order-create' \}\)/)
assert.match(source, /defineProps<\{ createPage\?: boolean; detailPage\?: boolean; showCreateAction\?: boolean \}>/)
assert.match(source, /const isCreatePage = computed\(\(\) => props\.createPage\)/)
assert.match(source, /<FirstLegOrderEditorShell[^>]*:page="isCreatePage"/)
assert.match(editorShellSource, /<section v-if="page" class="first-leg-editor-page">/)
assert.match(editorShellSource, /<el-dialog[\s\S]*v-else/)
assert.match(editorShellSource, /class="first-leg-editor-dialog"/)
assert.match(editorShellSource, /width="94vw"/)
assert.doesNotMatch(editorShellSource, /<el-drawer/)
assert.match(editorShellSource, /返回订单列表/)
assert.match(editorShellSource, /\.editor-page-footer :deep\(\.editor-actions\) \{[^}]*justify-content: center;/)
})
test('exposes real list, draft, submit, and detail workflow entries', () => {
assert.match(source, /function openCreate\(\)/)
assert.match(source, /async function saveDraft\(\)/)
assert.match(source, /function submitOrder\(\)/)
assert.match(source, /async function openDetail\(order: FirstLegOrder\)/)
assert.match(source, /Promise\.allSettled\(\[/)
assert.match(source, /const orderPageRequest = isCreatePage\.value \|\| isDetailPage\.value\s*\? store\.loadWarehouseType\('DOMESTIC', true\)\s*:\s*loadOrderPage\(\)/)
assert.match(source, /Promise\.allSettled\(\[\s*orderPageRequest,\s*companyRequest/)
assert.match(source, /tenantStore\.loadCompanyContext\(\)/)
assert.match(source, /await store\.loadDetail\(orderId\)/)
})
test('returns edited submissions to the refreshed order list instead of opening detail', () => {
const submitSource = source.slice(source.indexOf('async function submitOrder()'), source.indexOf('async function searchOrders'))
assert.match(submitSource, /if \(isCreatePage\.value\)[\s\S]*router\.push\(\{ name: 'first-leg-orders' \}\)[\s\S]*return/)
assert.match(submitSource, /await loadPageData\(\)/)
assert.doesNotMatch(submitSource, /first-leg-order-detail/)
})
test('copies an order into the dedicated create page without reusing order identity', () => {
assert.match(source, /key: 'copy', label: '复制下单'/)
assert.match(source, /name: 'first-leg-order-create'[\s\S]*copyFrom: order\.id/)
assert.match(source, /order\.status === 'DRAFT' \? \{ copyDraft: '1' \}/)
assert.match(source, /sourceIsDraft \? await store\.loadDraftDetail\(id\) : await store\.loadDetail\(id\)/)
assert.match(source, /const draft = draftFromOrder\(order\)[\s\S]*inboundCode: ''/)
assert.match(source, /id: `copied-cargo-\$\{\+\+cargoSequence\}`/)
assert.match(source, /editingId\.value = undefined/)
assert.match(source, /订单数据已复制,请核对后提交/)
})
test('keeps create-page support errors scoped to create-page requests', () => {
assert.doesNotMatch(source, /store\.draftLoadError/)
})
test('blocks order submission before company approval while preserving draft actions', () => {
assert.match(source, /companySubmissionBlocked/)
assert.match(source, /companyAuditLoaded\.value = true/)
assert.match(source, /企业资料待审核,当前仅可保存订单草稿/)
assert.match(source, /暂时无法提交订单/)
assert.match(source, /router\.push\('\/company\/profile'\)/)
assert.match(source, /auth\.hasPermission\('order:first-leg:submit'\)/)
})
test('defers approval checks to the server when the member cannot read the company profile', () => {
assert.match(source, /const canViewCompanyProfile = computed/)
assert.match(source, /const companySubmissionBlocked = computed\(\(\) => companyAuditLoaded\.value/)
assert.match(source, /const knownCompanyAuditStatus = companyAuditLoaded\.value \? tenantStore\.company\.auditStatus : undefined/)
assert.match(source, /store\.submitSaved\(savedOrder\.id, knownCompanyAuditStatus\)/)
})
test('renders dynamic cargo lines and a responsive editor dialog', () => {
assert.match(source, /v-for="\(item, index\) in form\.items"/)
assert.match(source, /function addCargoItem\(\)/)
assert.match(source, /function removeCargoItem\(index: number\)/)
assert.match(editorShellSource, /first-leg-editor-dialog\.el-dialog\) \{[^}]*height: 94vh;/)
assert.match(editorShellSource, /width: 100vw !important; height: 100vh;/)
assert.doesNotMatch(source, /class="mobile-order-(?:list|card|surface)"/)
assert.doesNotMatch(source, /\.prototype-order-table :deep\(> \.el-table\).*display: none/)
})
test('uses the full editor dialog width for cargo details and cost estimates', () => {
assert.match(source, /:class="\{ 'is-dialog-order-form': !isCreatePage \}"/)
assert.match(source, /\.order-form\.is-dialog-order-form \{ width: 100%; max-width: none; \}/)
assert.match(source, /\.is-dialog-order-form > \.cargo-section, \.is-dialog-order-form > \.cost-estimate-section \{ width: 100%; max-width: none; \}/)
assert.match(source, /\.price-estimate__result \{ display: grid; grid-template-columns:/)
assert.match(source, /\.cargo-table-wrap \{ width: 100%;/)
})
test('keeps primary mobile order actions touch sized', () => {
assert.match(source, /\.editor-actions :deep\(\.el-button\) \{ min-height: 44px;/)
})
test('uses the shared query toolbar and data table without metric cards', () => {
assert.match(source, /import QueryToolbar, \{ type QueryField, type QueryModel \}/)
assert.match(source, /import DataTable, \{ type DataTableColumn \}/)
assert.match(source, /const filters = ref<QueryModel>/)
assert.match(source, /<QueryToolbar v-model="filters" class="first-leg-query-toolbar" layout="grid" :fields="queryFields" ariaLabel="订单查询" @search="searchOrders" \/>/)
assert.match(source, /async function searchOrders\(model: QueryModel\)/)
assert.match(source, /await store\.load\(\{/)
assert.match(source, /<DataTable :page="tablePage" :page-size="tablePageSize" @update:page="changeOrderPage" @update:page-size="changeOrderPageSize" :data="pagedOrders" :columns="visibleOrderColumns"/)
assert.match(source, /\{ key: 'actions', label: '操作', width: 284, fixed: 'left', align: 'left', headerAlign: 'left', slotName: 'actions' \}/)
assert.doesNotMatch(source, /order-metrics|订单概览/)
})
test('adds download actions to the order-list toolbar', () => {
assert.match(source, /下载唛头/)
assert.match(source, /导出订单信息/)
assert.match(source, /function downloadShippingMarks\(\)/)
assert.match(source, /downloadMerchantOrderCartonLabelsBatch\(orders\.map\(\(order\) => order\.id\)\)/)
assert.match(source, /error\.traceId \? `,追踪号 \$\{error\.traceId\}`/)
assert.doesNotMatch(source, /order\.businessSummary\.inboundId/)
assert.match(source, /批量箱唛-\$\{orders\.length\}单\.pdf/)
assert.match(source, /function exportOrderInformation\(\)/)
assert.match(source, /selectedOrders\.value\.map/)
assert.match(source, /订单信息\.csv/)
assert.doesNotMatch(source, /exportOrderInformation[\s\S]{0,1200}loadDetail/)
})
test('selects orders before downloading marks or exporting last-mile data', () => {
assert.match(source, /\{ key: 'selection', type: 'selection', width: 48, fixed: 'left', reserveSelection: true \}/)
assert.match(source, /const selectedOrders = ref<FirstLegOrder\[\]>\(\[\]\)/)
assert.match(source, /const selectedOrderCount = computed\(\(\) => selectedOrders\.value\.length\)/)
assert.match(source, /@selection-change="handleOrderSelectionChange"/)
assert.match(source, /:disabled="selectedOrderCount === 0" @click="downloadShippingMarks"/)
assert.match(source, /:disabled="selectedOrderCount === 0" @click="exportOrderInformation"/)
assert.match(source, /const orders = selectedOrders\.value\s*$/m)
assert.match(source, /const rows = selectedOrders\.value\.map/)
assert.doesNotMatch(source, /const orders = filteredOrders\.value\.filter/)
assert.doesNotMatch(source, /const rows = filteredOrders\.value\.map/)
})
test('downloads selected carton labels with one array request', () => {
const downloadStart = source.indexOf('async function downloadShippingMarks()')
const downloadEnd = source.indexOf('function formatExportDateTime', downloadStart)
const downloadSource = source.slice(downloadStart, downloadEnd)
assert.match(downloadSource, /await downloadMerchantOrderCartonLabelsBatch\(orders\.map\(\(order\) => order\.id\)\)/)
assert.match(downloadSource, /saveBlob\(result\.blob/)
assert.match(downloadSource, /result\.failureCount/)
assert.doesNotMatch(downloadSource, /for \(const order of orders\)/)
assert.doesNotMatch(downloadSource, /Promise\.all\(/)
assert.doesNotMatch(downloadSource, /businessSummary\.inboundId\)/)
assert.doesNotMatch(downloadSource, /没有已生成的入库单|未生成入库单/)
})
test('exports every visible order field with normalized dates', () => {
assert.match(source, /function formatExportDateTime\(value: string \| null \| undefined\)/)
assert.match(source, /return `\$\{date\.getFullYear\(\)\}-\$\{pad\(date\.getMonth\(\) \+ 1\)\}-\$\{pad\(date\.getDate\(\)\)\} \$\{pad\(date\.getHours\(\)\)\}:\$\{pad\(date\.getMinutes\(\)\)\}:\$\{pad\(date\.getSeconds\(\)\)\}`/)
for (const label of ['入仓码', '渠道', '运输方式', '交货方式', '预约提货时间', '目的仓', '目的仓类型', '当前订单状态', '箱数', '快递公司', '快递单号', 'SKUS', '总毛重', '总净重', '总体积', '总数量', '计费单价', '计费体积', '计费重量', '备注', '货件编号', '跟踪编号', '国内仓入库时间', '创建时间']) {
assert.match(source, new RegExp(`'${label}'`))
}
assert.match(source, /formatExportDateTime\(order\.inboundedAt\)/)
assert.match(source, /order\.pickupMethod === 'PICKUP' \? formatExportDateTime\(order\.requestedPickupAt\) : '-'/)
assert.match(source, /formatListMetric\(order\.chargeableVolumeCbm, 'm³'\)/)
assert.doesNotMatch(source, /formatExportDateTime\(order\.businessDate\)/)
assert.match(source, /formatExportDateTime\(order\.createdAt\)/)
})
test('matches the prototype with selectable dense order-list columns', () => {
assert.match(source, /aria-label="设置订单列表显示列"/)
assert.match(source, /显示字段筛选/)
assert.match(source, /const defaultVisibleColumnKeys = \['status', 'progress', 'channel', 'transportMode', 'pickupMethod', 'receivingWarehouse', 'createdAt'\]/)
assert.match(source, /key: 'inboundCode', label: '入仓码', width: 230, fixed: 'left'/)
assert.match(source, /<SlidersHorizontal :size="18"/)
assert.match(source, /<el-checkbox-group v-model="visibleColumnKeys"/)
assert.match(source, /draggable="true"/)
assert.match(source, /@drop\.prevent="dropOrderColumn/)
assert.match(source, /function moveOrderColumn/)
assert.match(source, /function resetOrderColumns/)
assert.match(source, />恢复默认<\/el-button>/)
assert.match(source, /const visibleOrderColumns = computed/)
assert.match(source, /:columns="visibleOrderColumns"/)
for (const label of ['入仓码', '渠道', '运输方式', '交货方式', '预约提货时间', '目的仓', '目的仓类型', '当前订单状态', '箱数', '快递公司', '快递单号', 'SKUS', '总毛重', '总净重', '总体积', '总数量', '计费单价', '计费体积', '计费重量', '备注', '货件编号', '跟踪编号', '国内仓入库时间']) {
assert.match(source, new RegExp(`label: '${label}'`))
}
assert.match(source, /order\.businessSummary\.totalPackageCount/)
assert.match(source, /summarizeFirstLegCargo\(order\.items\)/)
assert.match(source, /order\.items\.reduce/)
assert.match(source, /<template #chargeableWeight>-<\/template>/)
assert.match(source, /<template #billingUnitPrice>-<\/template>/)
assert.match(source, /<template #chargeableVolume="\{ row \}">\{\{ formatListMetric\(\(row as FirstLegOrder\)\.chargeableVolumeCbm, 'm³'\) \}\}<\/template>/)
assert.match(source, /pickupMethod === 'PICKUP'[\s\S]{0,160}requestedPickupAt[\s\S]{0,160}: '-'/)
assert.match(source, /inboundedAt/)
assert.doesNotMatch(source, /label: '业务日期'/)
assert.doesNotMatch(source, /<template #businessDate>/)
assert.match(source, /<template #createdAt="\{ row \}">\{\{ formatDateTime\(\(row as FirstLegOrder\)\.createdAt\) \}\}<\/template>/)
assert.match(source, /order\.skuNos\.length/)
assert.doesNotMatch(source, /pagedOrders[\s\S]{0,100}loadDetail/)
})
test('keeps dense order identifiers on one line and aligns columns by content type', () => {
assert.match(source, /key: 'inboundCode'[\s\S]{0,180}showOverflowTooltip: true/)
assert.match(source, /key: 'status'[\s\S]{0,100}align: 'center'[\s\S]{0,100}headerAlign: 'center'/)
assert.match(source, /key: 'grossWeight'[\s\S]{0,100}align: 'right'[\s\S]{0,100}headerAlign: 'right'/)
assert.match(source, /class="inbound-code-primary table-ellipsis table-code"/)
assert.doesNotMatch(source, /<template #order="\{ row \}">/)
assert.match(source, /\.table-ellipsis, \.table-multivalue \{[^}]*text-overflow: ellipsis;[^}]*white-space: nowrap;/)
assert.match(source, /\.inbound-code-primary \{[^}]*text-overflow: ellipsis;[^}]*white-space: nowrap;/)
assert.match(tableSource, /height: var\(--client-table-row-height\)/)
})
test('shows one unified progress line and C02 business summaries without detail history', () => {
assert.match(source, /import OrderProgressPanel/)
assert.match(source, /<OrderProgressPanel :order="detailOrder">[\s\S]*?<template #cabinet>/)
assert.match(source, /<h3>关键业务摘要<\/h3>/)
assert.match(source, /progressSummaryItems/)
assert.match(source, /summary\.preloadStatus/)
assert.match(source, /summary\.expectedPickCount/)
assert.match(source, /label: '后续履约', value: orderProgressLabel\(store\.orderProgress\[order\.id\]\)/)
assert.doesNotMatch(source, /待海外同步/)
assert.match(progressSource, /FIRST_LEG_PROGRESS_STAGES/)
assert.match(progressSource, /订单主节点与关联柜次统一展示/)
assert.match(progressSource, /履约进度/)
assert.doesNotMatch(progressSource, /进度记录|el-timeline|timeline/)
assert.doesNotMatch(source, /<h3>报关回执<\/h3>/)
assert.match(source, /船期摘要/)
assert.match(source, /柜次明细/)
assert.match(source, /pickingStatusLabel: getFirstLegPickingStatusLabel\(summary\.loadingStatus\)/)
assert.match(source, /prop="pickingStatusLabel" label="拣货状态"/)
assert.doesNotMatch(source, /label="当前节点"/)
assert.match(source, /尚未分配柜次/)
assert.doesNotMatch(source, /暂无柜次分配信息/)
assert.doesNotMatch(source, /summary\.sealNo \|\|/)
assert.doesNotMatch(progressSource, /客户确认|差异处理中|派送中/)
})
test('uses only fields present on C02 rows before loading full detail', () => {
assert.match(source, /orderPackageCount/)
assert.match(source, /label: '渠道'/)
assert.match(source, /label: '目的仓'/)
assert.match(source, /displayListValue/)
assert.doesNotMatch(source, /key: 'operator'/)
assert.doesNotMatch(source, /key: 'transportMode', type: 'select'/)
assert.match(source, /:total="store\.total"/)
assert.match(source, /mainStatus: text\('status'\)/)
assert.match(source, /queue: activeOrderQueue\.value, page: requestedPage, size: tablePageSize\.value/)
assert.match(source, /createdFrom: range\[0\] \? `\$\{range\[0\]\}T00:00:00`/)
})
test('shows and searches the inbound code after a draft is submitted', () => {
assert.match(source, /label: '入仓码 \/ SKU'/)
assert.match(source, /order\.businessSummary\.inboundCode/)
assert.match(source, /<template #inboundCode="\{ row \}">[\s\S]*?class="inbound-code-primary table-ellipsis table-code"/)
assert.match(source, /@click="openDetail\(row as FirstLegOrder\)"/)
assert.match(source, /<dt>入仓码<\/dt><dd><button v-if="detailOrder\.businessSummary\.inboundCode"/)
assert.match(source, /提交后生成/)
assert.doesNotMatch(source, /label: '订单号'/)
})
test('keeps all progress stages and business summary usable on mobile', () => {
assert.match(progressSource, /grid-auto-columns: 82px/)
assert.match(progressSource, /overflow-x: auto/)
assert.match(source, /\.fulfillment-summary \{ grid-template-columns: 1fr; \}/)
})
test('keeps the first viewport compact and moves create into the table toolbar', () => {
const tableToolbarSource = source.slice(source.indexOf('#toolbar-actions'), source.indexOf('class="order-eligibility is-pending"'))
assert.match(source, /#toolbar-actions/)
assert.match(source, /class="order-queue-tabs"[\s\S]*v-for="queue in ORDER_QUEUES"[\s\S]*orderQueueCounts\[queue.key\]/)
assert.match(source, /activeOrderQueue\.value === 'draft'[\s\S]*order\.status === 'DRAFT'/)
assert.doesNotMatch(source, /共 \{\{ filteredOrders\.length \}\} 条/)
assert.match(source, /#toolbar-actions[\s\S]*order:first-leg:create[\s\S]*创建订单[\s\S]*class="order-eligibility is-pending"/)
assert.match(tableToolbarSource, /下载唛头[\s\S]*导出订单信息[\s\S]*批量提交草稿[\s\S]*创建订单/)
assert.doesNotMatch(source.slice(source.indexOf('<QueryToolbar'), source.indexOf('<div class="client-card order-table-card">')), /下载唛头|导出订单信息|创建订单/)
assert.match(source, /client-main:has\(> \.first-leg-page:not\(\.is-create-route\)\) > \.client-page-heading > div\) \{ position: absolute; width: 1px; height: 1px;/)
})
test('uses the prototype query grid and only shows the qualification notice before approval', () => {
assert.match(querySource, /\.query-toolbar--grid \{ display: block;/)
assert.match(querySource, /query-toolbar__filters \{ display: grid; grid-template-columns: repeat\(4,/)
assert.match(querySource, /query-toolbar__actions \{ margin-top: 12px;/)
assert.match(source, /v-if="companySubmissionBlocked && !supportingDataErrors\.some/)
assert.doesNotMatch(source, /企业资料已审核,可正常提交订单/)
assert.doesNotMatch(source, /order-eligibility\.is-approved/)
})
test('does not let order-list loading block the create form', () => {
assert.match(source, /v-loading="isCreatePage && supportingDataLoading"/)
assert.match(source, /const pageDataLoading = computed\(\(\) => supportingDataLoading\.value \|\| \(!isCreatePage\.value && store\.loading\)\)/)
})
test('does not expose arbitrary status mutation controls to customers', () => {
assert.doesNotMatch(source, /setStatusForPreview/)
assert.doesNotMatch(source, /状态更新|更新状态/)
})
test('requires an enabled platform receiving warehouse in the order form', () => {
assert.match(source, /store\.warehouses\.filter\(\(warehouse\) => warehouse\.warehouseType === 'DOMESTIC'\)/)
assert.match(source, /availableWarehouses\.value\.length === 0/)
assert.match(source, /label="国内收货仓"[^>]*required/)
assert.match(source, /v-model="form\.receivingWarehouseId"/)
assert.doesNotMatch(source, /selectedWarehouse\.businessHours/)
assert.doesNotMatch(source, /class="selected-warehouse" aria-label="所选收货仓详情"/)
assert.match(editorShellSource, /first-leg-editor-dialog \.el-dialog__header\) \{[^}]*border-bottom: 1px solid var\(--border\)/)
assert.match(editorShellSource, /first-leg-editor-dialog \.el-dialog__body\) \{[^}]*min-height: 0;[^}]*overflow-y: auto;[^}]*overscroll-behavior: contain/)
assert.match(editorShellSource, /first-leg-editor-dialog \.el-dialog__footer\) \{[^}]*border-top: 1px solid var\(--border\);[^}]*background: #fff/)
assert.match(source, /detailOrder\.receivingWarehouseSnapshot\?\.name/)
assert.match(source, /detailOrder\.receivingWarehouseSnapshot\.code/)
assert.match(source, /loadSelectedWarehouse\(detailedOrder\.receivingWarehouseId\)/)
})
test('selects platform destinations and uses customer-scoped server address books', () => {
assert.match(source, /v-if="form\.fulfillmentType !== 'ONE_CLICK_DROPSHIP'" value="FBA"/)
assert.match(source, /<el-radio-button value="OVERSEAS">海外仓<\/el-radio-button>/)
assert.match(source, /v-if="form\.fulfillmentType !== 'ONE_CLICK_DROPSHIP'" value="PRIVATE"/)
assert.match(source, /@change="changeFulfillmentType"/)
assert.match(source, /form\.destinationType = 'OVERSEAS'\s*\n\s*await changeDestinationType\(\)/)
assert.match(source, /warehouse\.warehouseType === form\.destinationType/)
assert.match(source, /await store\.loadWarehouseType\(type\)/)
assert.match(source, /await loadDestinationWarehouses\(form\.destinationType\)/)
assert.match(source, /:loading="destinationWarehousesLoading"/)
assert.match(source, /v-model="form\.destinationWarehouseId"/)
assert.match(source, /form\.amazonWarehouseCode = form\.destinationType === 'FBA' \? warehouse\.code : ''/)
assert.match(source, /getMerchantOrderAddresses\(\)/)
assert.match(source, /createMerchantOrderAddress\(payload\)/)
assert.match(source, /updateMerchantOrderAddress\(form\.(?:senderAddressId|privateDestinationAddressId), payload\)/)
assert.match(source, /disableMerchantOrderAddress\(id\)/)
assert.match(source, /function findAddressBookEntryId\(addressType: CustomerOrderAddress\['addressType'\], snapshot: FirstLegPartySnapshot \| null\)/)
assert.match(source, /senderAddressId: findAddressBookEntryId\('PICKUP', order\.shipperPartySnapshot\)/)
assert.match(source, /privateDestinationAddressId: findAddressBookEntryId\('PRIVATE_DESTINATION', order\.consigneePartySnapshot\)/)
assert.match(source, /function selectPickupAddress\(id: string \| null \| undefined\)[\s\S]*?form\.shipperPartySnapshot = createEmptyPartySnapshot\('SHIPPER'\)/)
assert.match(source, /function selectPrivateAddress\(id: string \| null \| undefined\)[\s\S]*?form\.consigneePartySnapshot = createEmptyPartySnapshot\('CONSIGNEE'\)/)
assert.match(source, /const existingAddressId = findAddressBookEntryId\('PICKUP', snapshot\)[\s\S]*?相同的企业提货地址已存在,已为您选中/)
assert.match(source, /const existingAddressId = findAddressBookEntryId\('PRIVATE_DESTINATION', snapshot\)[\s\S]*?相同的企业私人仓地址已存在,已为您选中/)
assert.match(source, /v-model="form\.senderAddressId"/)
assert.match(source, /v-model="form\.privateDestinationAddressId"/)
assert.doesNotMatch(source, /localStorage|cc:first-leg:private-addresses/)
assert.match(source, /私人地址不能引用平台公共仓库|form\.destinationWarehouseId = ''/)
})
test('lets the create-page instance own destination loading and exposes a recoverable error state', () => {
assert.match(source, /await router\.push\(\{ name: 'first-leg-order-create' \}\)\s*\n\}/)
assert.doesNotMatch(source, /await router\.push\(\{ name: 'first-leg-order-create' \}\)[\s\S]*?await loadDestinationWarehouses\(form\.destinationType\)/)
assert.match(source, /watch\(isCreatePage, \(isCreate, wasCreate\) => \{[\s\S]*?if \(wasCreate === false\) void loadPageData\(\)/)
assert.match(source, /if \(destinationResult\.status === 'rejected'\) supportingDataErrors\.value\.push\(`目的仓加载失败:\$\{errorMessage\(destinationResult\.reason\)\}`\)/)
assert.match(source, /下单配套数据加载失败/)
})
test('shows a loading state before the list can create an order', () => {
assert.match(source, /<el-button v-if="props\.showCreateAction && auth\.hasPermission\('order:first-leg:create'\)" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading \|\| availableWarehouses\.length === 0" @click="openCreate"><FilePlus2/)
assert.match(source, /pageDataLoading \? '加载收货仓中' : \(availableWarehouses\.length \? '创建第一笔订单'/)
})
test('auto-fills trusted warehouse and company data instead of asking customers to repeat it', () => {
assert.match(source, /async function selectDestinationWarehouse\(id: string\)/)
assert.match(source, /await store\.loadWarehouseDetail\(warehouse\.id\)/)
assert.match(source, /warehouse\.code\.toUpperCase\(\)\.includes\('YIWU'\)/)
assert.match(source, /warehouse\.name\.includes\('义乌'\)/)
assert.match(source, /availableDestinationWarehouses\.value\.length === 1/)
assert.match(source, /function createPickupPartySnapshot\(\)/)
assert.match(source, /contactName: tenantStore\.company\.contactName/)
assert.match(source, /selectedDestinationWarehouse\.address/)
})
test('collects the backend-required first-leg fields', () => {
for (const model of ['destinationCountryCode', 'destinationWarehouseName', 'channelCode', 'pricingTaxMode', 'pickupMethod', 'taxMode', 'destinationType', 'packageType', 'upc']) {
assert.match(source, new RegExp(`v-model="(?:form|item)\\.${model}"`))
}
assert.match(source, /form\.channelName = channel\?\.channelName/)
assert.match(source, /v-model="item\.woodenProduct"/)
assert.match(source, /form\.customsInspection = form\.items\.some\(\(item\) => item\.woodenProduct\) \? true : null/)
assert.match(source, /木制品<small>勾选后自动商检<\/small>/)
assert.doesNotMatch(source, /label="是否商检"/)
assert.match(source, /<small v-if="item\.woodenProduct">自动商检<\/small>/)
})
test('distills order creation into the confirmed prototype sections with server pricing', () => {
const cargoTableSource = source.slice(source.indexOf('<table class="cargo-edit-table">'), source.indexOf('</table>', source.indexOf('<table class="cargo-edit-table">')))
const cargoToolbarSource = source.slice(source.indexOf('<div class="cargo-table-toolbar">'), source.indexOf('<div class="cargo-table-wrap">', source.indexOf('<div class="cargo-table-toolbar">')))
assert.match(source, /<h3>目的地信息<\/h3>/)
assert.match(source, /<h3>客户下单<\/h3>/)
assert.match(source, /<h3>货物明细<\/h3>/)
assert.ok(source.indexOf('<h3>客户下单</h3>') < source.indexOf('<h3>目的地信息</h3>'))
assert.ok(source.indexOf('<h3>目的地信息</h3>') < source.indexOf('<h3>货物明细</h3>'))
assert.match(source, /class="segmented-options"/)
assert.match(source, /class="prototype-form-row prototype-form-row--tabs"/)
assert.match(source, /<table class="cargo-edit-table">/)
assert.match(source, /auth\.hasPermission\('order:first-leg:create'\)[\s\S]*批量导入订单/)
assert.doesNotMatch(source, /create-batch-import-entry/)
assert.match(cargoToolbarSource, /isCreatePage && !routeDraftId && canCreateOrder/)
assert.match(cargoToolbarSource, /downloadBatchTemplate[\s\S]*下载 Excel 模板/)
assert.match(cargoToolbarSource, /openBatchImport[\s\S]*Excel 批量导入/)
assert.doesNotMatch(cargoToolbarSource, /Excel 导入用于一次创建多条订单草稿/)
assert.match(source, /导入成功的数据只创建草稿,不会直接提交审核/)
assert.match(source, /下载正式模板[\s\S]*选择 Excel/)
assert.match(source, /仅支持 \.xlsx,文件不超过 5MB、最多 50 行/)
assert.match(source, /直接选择模板内的中文下拉项,不需要填写英文代码或内部编号/)
assert.match(source, /选择木制品时,模板会要求同步选择需要商检/)
assert.match(source, /下载 Excel 错误报告/)
assert.match(source, /\^excel-row-\(\\d\+\)\$[\s\S]*item\.index \+ 3/)
assert.match(source, /Excel 第 \{\{ batchImportExcelRow\(item\) \}\} 行/)
assert.doesNotMatch(source, /未填写行标识/)
assert.match(source, /batchImportError[\s\S]*requestId[\s\S]*traceId/)
assert.match(source, /添加行[\s\S]*删除所选行/)
assert.match(source, /添加 SKU/)
assert.match(source, /addSkuToPackingGroup/)
assert.match(source, /shared-carton-value">同箱/)
assert.match(source, /syncSkuQuantity/)
assert.match(source, /每个箱组默认装一个 SKU/)
assert.doesNotMatch(source, /v-model="item\.packingGroupNo"/)
assert.doesNotMatch(cargoTableSource, /<th[^>]*>混箱组合/)
assert.doesNotMatch(source, /货物明细导入模板\.xls/)
for (const heading of ['SKU NO.', '中文品名', '箱规长 cm', '总毛重', '报关 HS CODE', '货件编号', '跟踪编号', 'UPC NO.']) {
assert.ok(cargoTableSource.includes(heading))
}
assert.ok(cargoTableSource.indexOf('包装类型') < cargoTableSource.indexOf('危险品'))
assert.ok(cargoTableSource.indexOf('危险品') < cargoTableSource.indexOf('货件编号'))
assert.ok(cargoTableSource.indexOf('木制品') < cargoTableSource.indexOf('货件编号'))
assert.match(source, /参考运费/)
assert.match(source, /仅供参考,最终以实际结算为准/)
assert.doesNotMatch(source, /v-model="form\.(?:businessType|origin|destination|priority|tradeTerm)"/)
})
test('matches the prototype two-column create layout and reset action', () => {
assert.match(source, /class="order-form-section prototype-section order-basic-section"/)
assert.match(source, /<span class="order-step-badge">01<\/span><h3>客户下单<\/h3><span class="order-section-context">国内交货与运输信息<\/span>/)
assert.match(source, /<span class="order-step-badge">02<\/span><h3>目的地信息<\/h3><span class="order-section-context">美国目的仓与收件信息<\/span>/)
assert.match(source, /\.is-create-route \.prototype-order-form \{[^}]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\)/)
assert.match(source, /\.is-create-route \.cargo-section, \.is-create-route \.cost-estimate-section \{ grid-column: 1 \/ -1; \}/)
assert.match(source, /\.is-create-route \.prototype-form-row[^\n]*grid-template-columns: 108px minmax\(0, 1fr\)/)
assert.match(source, /\.is-create-route \.prototype-form-row :deep\(\.el-form-item__label\) \{[^}]*height: 36px[^}]*justify-content: flex-end[^}]*padding: 0 10px 0 0/)
assert.match(source, /\.is-create-route \.compact-transport-grid > :deep\(\.el-form-item__label\) \{[^}]*align-items: center[^}]*text-align: right/)
assert.match(source, /async function resetCreateForm\(\)/)
assert.match(source, /确认清空当前填写的订单内容并恢复默认值/)
assert.match(source, /v-if="isCreatePage"[^>]*@click="resetCreateForm"[^>]*><RotateCcw[^>]*\/>重置/)
assert.match(source, /class="submit-order-button"/)
assert.match(editorShellSource, /\.first-leg-editor-page \{[^}]*padding-bottom: 72px/)
assert.match(editorShellSource, /\.editor-page-footer \{[^}]*position: fixed[^}]*bottom: 0[^}]*left: 224px/)
assert.match(editorShellSource, /submit-gradient 5s ease-in-out infinite/)
assert.match(editorShellSource, /submit-shine 3\.2s ease-in-out infinite/)
assert.match(editorShellSource, /@media \(prefers-reduced-motion: reduce\)/)
assert.match(createHubSource, /<h1 class="create-hub-title">头程下单<\/h1>/)
assert.match(createHubSource, /<div class="create-mode-toolbar">[\s\S]*order-mode-switch[\s\S]*选择下单方式/)
assert.doesNotMatch(createHubSource, /填写货物信息,创建新的头程运输订单/)
assert.match(source, /\.is-create-route \.prototype-order-form \{[^}]*align-items: start/)
assert.doesNotMatch(source, /\.is-create-route \.order-basic-section, \.is-create-route \.destination-section \{ height: 100%; \}/)
assert.doesNotMatch(source, /\.is-create-route \.order-basic-section > \.order-section-heading, \.is-create-route \.destination-section > \.order-section-heading \{[^}]*(?:height|min-height):/)
assert.match(source, /\.is-create-route \.order-basic-section > \.order-section-heading h3, \.is-create-route \.destination-section > \.order-section-heading h3 \{[^}]*font-size: 17px[^}]*line-height: 24px/)
assert.match(source, /\.is-create-route \.order-step-badge \{[^}]*width: 30px[^}]*height: 30px/)
assert.match(source, /\.is-create-route \.order-section-context \{[^}]*margin-left: auto/)
assert.match(source, /\.is-create-route \.warehouse-options :deep\(\.el-radio\.is-checked\) \{[^}]*linear-gradient/)
})
test('shows an optional LX inbound order number for one-click dropship orders', () => {
assert.match(source, /v-if="form\.fulfillmentType === 'ONE_CLICK_DROPSHIP'" label="LX入库单号" prop="lxCode"/)
assert.match(source, /v-model="form\.lxCode"[^>]*placeholder=""/)
assert.doesNotMatch(source, /label="LX入库单号"[^>]*required/)
})
test('loads only active server channels for the selected domestic warehouse', () => {
assert.match(source, /getMerchantPricingChannels\(warehouseId\)/)
assert.match(source, /const channelOptions = ref<MerchantPricingChannel\[\]>\(\[\]\)/)
assert.match(source, /channelOptions\.value = \[\]/)
assert.match(source, /form\.channelCode = ''/)
assert.match(source, /sequence !== channelRequestSequence \|\| form\.receivingWarehouseId !== warehouseId/)
assert.match(source, /channel\.pricingTaxModes\.includes\(form\.pricingTaxMode\)/)
assert.match(source, /availablePricingTaxModes/)
assert.match(source, /:loading="channelLoading"/)
assert.match(source, /:disabled="!form\.receivingWarehouseId \|\| channelLoading"/)
assert.match(source, /label="报价包税方式" prop="pricingTaxMode" required class="pricing-tax-mode-field"/)
assert.match(source, /\.compact-transport-grid > :deep\(\.pricing-tax-mode-field\) \{ display: none; \}/)
assert.doesNotMatch(source, /\.compact-transport-grid > :deep\(\.el-form-item:nth-child\(2\)\)/)
assert.match(source, /当前收货仓暂无可用渠道/)
assert.match(source, /渠道加载失败/)
assert.match(source, /@click="retryPricingChannels"/)
assert.doesNotMatch(source, /SEA-US-GENERAL|SEA-US-SENSITIVE|SEA-US-EXPRESS|美线 FBA 直送/)
assert.doesNotMatch(source, /channelOptions\.some\(\(channel\) => channel\.code/)
})
test('estimates by warehouse, destination, tax mode, weight and volume without blocking saves', () => {
assert.match(source, /estimateMerchantFirstLegPrice\(request\)/)
assert.match(source, /originWarehouseId: form\.receivingWarehouseId/)
assert.match(source, /destinationCode: form\.destinationType === 'FBA' \? form\.amazonWarehouseCode : undefined/)
assert.match(source, /destinationWarehouseId: form\.destinationType === 'OVERSEAS'/)
assert.match(source, /pricingTaxMode: form\.pricingTaxMode/)
assert.match(source, /actualWeightKg: summary\.grossWeightKg/)
assert.match(source, /actualVolumeCbm: summary\.volumeCbm/)
assert.match(source, /setTimeout\(\(\) => void loadPriceEstimate\(\), 300\)/)
assert.match(source, /sequence === estimateRequestSequence/)
assert.match(source, /您仍可正常保存订单草稿/)
assert.match(source, /v-for="mode in availablePricingTaxModes"/)
assert.match(source, /pricingTaxModeLabel\(mode\)/)
assert.match(source, /priceEstimate\.chargeableQuantity/)
assert.match(source, /priceEstimateBreakdown\.actualLabel/)
assert.match(source, /priceEstimateBreakdown\.convertedLabel/)
assert.match(source, /priceEstimateBreakdown\.basisLabel/)
assert.match(source, /两者取大/)
assert.match(source, /priceEstimate\.unitPrice/)
assert.match(source, /priceEstimate\.estimatedAmount/)
assert.match(source, /priceEstimateDisclaimer/)
assert.doesNotMatch(source, /3,310|2,990|2,500|静态预估展示/)
assert.doesNotMatch(source, /当前价格表不提供不包税报价|历史草稿的不包税设置/)
const saveSource = source.slice(source.indexOf('async function saveDraft()'), source.indexOf('async function submitOrder()'))
assert.doesNotMatch(saveSource, /loadPriceEstimate|priceEstimateError|estimateMerchantFirstLegPrice/)
})
test('keeps the reference estimate readable on narrow screens', () => {
assert.match(source, /\.price-estimate__result \{ grid-template-columns: 1fr; \}/)
assert.match(source, /\.price-estimate__result > small \{ grid-column: auto; \}/)
})
test('shows field-level validation and concise guidance for required order inputs', () => {
assert.match(source, /ref="orderFormRef"[^>]*:model="form" :rules="orderFormRules"/)
assert.match(source, /prop="shipperPartySnapshot\.contactName" required/)
assert.match(source, /prop="consigneePartySnapshot\.address" required/)
assert.match(source, /:prop="`items\.\$\{index\}\.cargoNameCn`" required/)
assert.match(source, /form\.destinationType === 'FBA' \|\| form\.fulfillmentType === 'ONE_CLICK_DROPSHIP' \? '必填' : '选填'/)
assert.match(source, /SKU NO\. 和 UPC NO\. 均为选填项/)
assert.match(source, /一件代发必须填写 SKU/)
assert.match(source, /:prop="`items\.\$\{index\}\.grossWeightKg`" required/)
assert.match(source, /<span class="required-field-mark" aria-hidden="true">\*<\/span>总毛重/)
assert.match(source, /<span class="required-field-mark" aria-hidden="true">\*<\/span>总体积/)
assert.match(source, /:prop="`items\.\$\{index\}\.upc`">\s*<el-input v-model="item\.upc"[^>]*placeholder="UPC NO\.(选填)"/)
assert.doesNotMatch(source, /:prop="`items\.\$\{index\}\.upc`"[^>]*(?:required|:rules)/)
assert.match(source, /UPC NO\. 选填/)
assert.doesNotMatch(source, /UPC必填/)
assert.match(source, /v-model="item\.cargoNameCn"[^>]*placeholder="中文品名"/)
assert.match(source, /\.order-form :deep\(\.el-form-item__error\).*position: static/)
})
test('collects snapshots only for pickup and private destinations', () => {
assert.doesNotMatch(source, /firstLegPartySource|v-model="form\.(?:shipper|consignee)PartyId"/)
assert.match(source, /form\.pickupMethod === 'PICKUP'/)
assert.match(source, /form\.destinationType === 'PRIVATE'/)
assert.match(source, /form\.shipperPartySnapshot\.contactName/)
assert.match(source, /form\.consigneePartySnapshot\.address/)
assert.match(source, /syncPrivateDestinationName/)
assert.match(source, /changePickupMethod/)
})
test('shows fulfillment courier information as read-only client detail', () => {
assert.match(source, /履约快递信息/)
assert.match(source, /detailOrder\.expressCompanyName/)
assert.match(source, /detailOrder\.trackingNumber/)
assert.match(source, /快递公司由平台维护/)
assert.doesNotMatch(source, /v-model="form\.expressCompanyName"/)
})
test('orders pickup choices and offers an optional tracking number only for express pickup', () => {
const group = source.match(/<el-radio-group v-model="form\.pickupMethod"[\s\S]*?<\/el-radio-group>/)[0]
assert.deepEqual([...group.matchAll(/value="([A-Z_]+)"/g)].map(match => match[1]), ['SELF_TRUCK', 'PICKUP', 'SELF_EXPRESS'])
assert.match(group, /value="SELF_EXPRESS">快递自提/)
const field = source.match(/<el-form-item v-if="form\.pickupMethod === 'SELF_EXPRESS'"[^>]*label="快递单号"[\s\S]*?<\/el-form-item>/)[0]
assert.match(field, /v-model="form\.trackingNumber" maxlength="128"/)
assert.doesNotMatch(field, /\brequired\b|:rules=/)
assert.match(source, /trackingNumber: order\.pickupMethod === 'SELF_EXPRESS' \? order\.trackingNumber : ''/)
assert.match(source, /if \(form\.pickupMethod !== 'SELF_EXPRESS'\) form\.trackingNumber = ''/)
})
test('shows the full customer-safe order detail without platform-only controls', () => {
const detailSource = source.slice(source.indexOf('<section v-if="isDetailPage"'), source.indexOf('</section>', source.indexOf('<section v-if="isDetailPage"')))
for (const heading of ['订单与运输', '收发货信息', '关键业务摘要', '履约快递信息', '货物明细']) {
assert.match(source, new RegExp(`<h3>${heading}<\\/h3>`))
}
for (const field of [
'贸易条款', '发货方式', '报关方式', '运输渠道', '收货要求', '目的仓类型', '海关查验',
'提交时间', '完成时间', 'SKU NO.', 'UPC NO.', '报关 HS CODE', '清关 HS CODE', '每箱数量',
'总净重', '清关申报单价', '中英文材质', '中英文用途', '产品规格', '货件编号',
'跟踪编号', '危险品',
]) {
assert.match(source, new RegExp(field))
}
assert.doesNotMatch(detailSource, /承运商|待平台指派/)
assert.match(source, /detailOrder\.customerRemark/)
assert.doesNotMatch(detailSource, /内部备注|审核通过|添加备注/)
})
test('opens order detail as a protected workspace page and paginates cargoes as a read-only table', () => {
assert.match(routerSource, /path: 'orders\/first-leg\/:orderId\/detail'[\s\S]*?name: 'first-leg-order-detail'[\s\S]*?props: \{ detailPage: true \}[\s\S]*?permission: 'order:first-leg:list'/)
assert.match(source, /router\.push\(\{ name: 'first-leg-order-detail', params: \{ orderId: order\.id \} \}\)/)
assert.match(source, /<section v-if="isDetailPage" class="first-leg-detail-page" aria-label="订单详情">/)
assert.doesNotMatch(source, /<el-dialog v-model="detailOpen"/)
assert.match(source, /first-leg-page\.is-detail-route\)[^\n]*client-page-heading\) \{ display: none; \}/)
assert.doesNotMatch(source, /class="detail-page-actions"/)
assert.match(source, /\.order-detail-grid \{[^}]*grid-template-columns: repeat\(4, minmax\(0, 1fr\)\)/)
assert.match(source, /\.order-detail-grid div \{[^}]*grid-template-columns: 78px minmax\(0, 1fr\)/)
assert.match(source, /const detailCargoPage = ref\(1\)/)
assert.match(source, /const detailCargoPageSize = ref\(5\)/)
assert.match(source, /const pagedDetailCargoItems = computed/)
assert.match(source, /items\.slice\(start, start \+ detailCargoPageSize\.value\)/)
assert.match(source, /<el-table :data="pagedDetailCargoItems"[^>]*class="cargo-detail-table"/)
assert.match(source, /v-model:current-page="detailCargoPage"/)
assert.match(source, /v-model:page-size="detailCargoPageSize"/)
assert.match(source, /:total="detailOrder\.items\.length"/)
assert.doesNotMatch(source, /class="cargo-detail-list"/)
assert.match(source, /\.first-leg-detail-page \{[^}]*padding: 10px 14px 20px;[^}]*border: 1px solid var\(--client-border\);[^}]*border-radius: var\(--client-radius\);[^}]*background: #fff;/)
assert.match(source, /\.order-detail-grid div \{[^}]*min-height: 34px;[^}]*padding: 3px 8px;/)
})
test('keeps shared button hierarchy compact and consistent', () => {
assert.match(querySource, /class="query-toolbar__search" type="primary"/)
assert.match(querySource, /class="query-toolbar__reset"/)
assert.match(tableSource, /v-for="action in splitTableActions\(rowActions\(row\)\)\.visible"[\s\S]*?link type="primary"/)
assert.match(clientUiSource, /--client-control-height: 32px/)
assert.match(clientUiSource, /\.client-ui \.el-button \{[\s\S]*?min-width: 68px;[\s\S]*?font-weight: 600;/)
assert.match(clientUiSource, /\.client-ui \.el-button\.is-link, \.client-ui \.el-button\.is-text \{[\s\S]*?min-width: 0;[\s\S]*?background: transparent;/)
assert.match(clientUiSource, /\.client-ui \.el-button svg \{[\s\S]*?width: 16px; height: 16px;/)
})
test('loads merchant inbound billing as an isolated read-only detail section', () => {
assert.match(source, /import InboundBillingPanel/)
assert.match(source, /import \{ getMerchantInboundBilling \}/)
assert.match(source, /void loadInboundBilling\(detailedOrder\)/)
assert.match(source, /<InboundBillingPanel/)
assert.match(source, /@retry="retryInboundBilling"/)
assert.match(source, /error instanceof ApiError && error\.code === 'A0404'/)
assert.match(source, /error\.message \|\| unavailableMessage/)
assert.match(source, /order\.status === 'DRAFT'/)
assert.match(source, /feeConfirmationOpen\.value[\s\S]*\? '未推送费用'[\s\S]*: '订单提交后可查看入库结果'/)
assert.match(source, /订单提交后可查看入库结果/)
assert.doesNotMatch(source, /confirmMerchantInbound|adjustMerchantInbound|sendMerchantInbound/)
})
test('keeps platform enterprise scope and tenant terminology out of the customer order UI', () => {
const templateSource = source.slice(source.indexOf('<template>'), source.lastIndexOf('</template>'))
assert.doesNotMatch(templateSource, /租户|tenantId|所属企业|企业筛选/)
})
test('opens billing-weight confirmation independently from order details', () => {
const handler = source.slice(source.indexOf('function openFeeConfirmation'), source.indexOf('async function retryDetail'))
assert.match(handler, /feeConfirmationOpen\.value = true/)
assert.match(handler, /loadInboundBilling\(order, '未推送费用'\)/)
assert.doesNotMatch(handler, /openDetail\(/)
assert.match(source, /v-if="feeConfirmationOpen"[\s\S]*dialog-only[\s\S]*auto-open/)
assert.match(source, /unavailable-message="未推送费用"/)
})
test('only offers published-fee confirmation for pending orders with confirmation permission', () => {
const actions = source.slice(source.indexOf('function getOrderRowActions'), source.indexOf('function getOrderRowActions') + 1800)
assert.match(actions, /actions\.push\(\{ key: 'fee', label: '确认计费重', run: \(\) => openFeeConfirmation\(order\) \}\)/)
assert.match(actions, /if \(canConfirmInboundFee\.value && order\.businessSummary\.customerFeeConfirmationRequired\) \{\s*actions\.push\(\{ key: 'fee'/)
assert.doesNotMatch(actions, /label: '确认费用'|key: 'weight'/)
assert.doesNotMatch(source, /ChargeableWeightDialog|weightConfirmationOrderId/)
assert.match(source, /:row-actions="getOrderRowActions"/)
assert.match(orderStoreSource, /customerFeeConfirmationRequired: summary\?\.customerFeeConfirmationRequired \?\? false/)
})
test('hides confirmation for confirmed, unpushed and unauthorized orders and refreshes after confirmation', () => {
const start = source.indexOf('function getOrderRowActions')
const end = source.indexOf('\nasync function copyOrder', start)
const body = source.slice(start, end)
.replace('function getOrderRowActions(row: object): TableAction[]', 'function getOrderRowActions(row)')
.replace('const order = row as FirstLegOrder', 'const order = row')
.replace('const actions: TableAction[]', 'const actions')
const actionsFor = new Function('canConfirmInboundFee', 'canCreateOrder', 'canEditOrder',
'canOfferCancellation', 'eligibleCustomsDetail', 'openFeeConfirmation',
`${body}; return getOrderRowActions`)
for (const [permitted, pending] of [[true, true], [true, false], [false, true], [false, false]]) {
const opened = []
const order = { id: 'order-1', businessSummary: { customerFeeConfirmationRequired: pending } }
const getActions = actionsFor({ value: permitted }, { value: false }, { value: false },
() => false, () => false, row => opened.push(row.id))
const confirm = getActions(order).filter(action => action.key === 'fee')
assert.equal(confirm.length, permitted && pending ? 1 : 0)
if (confirm.length) {
confirm[0].run()
assert.deepEqual(opened, ['order-1'])
}
}
assert.match(source, /function handleInboundFeeConfirmed\(\) \{\s*if \(!isCreatePage\.value && !isDetailPage\.value\) void loadOrderPage\(\)/)
})
test('submits only selected draft orders in one batch action', () => {
assert.match(source, /selectedDraftOrders = computed\(\(\) => selectedOrders\.value\.filter\(\(order\) => order\.status === 'DRAFT'\)\)/)
assert.match(source, /Promise\.allSettled\(ids\.map\(id => store\.submitSaved/)
assert.doesNotMatch(source, /submitMerchantFirstLegOrder/)
assert.match(source, /v-if="canSubmitOrder"[\s\S]*批量提交草稿/)
})
test('discards stale inbound billing responses when billing views close or the user changes orders', () => {
assert.match(source, /let inboundBillingRequestSequence = 0/)
assert.match(source, /const sequence = \+\+inboundBillingRequestSequence/)
assert.match(source, /sequence === inboundBillingRequestSequence[\s\S]*inboundBillingOrderId\.value === orderId[\s\S]*isDetailPage\.value \|\| feeConfirmationOpen\.value/)
assert.doesNotMatch(source, /watch\(detailOpen/)
assert.match(source, /watch\(feeConfirmationOpen,[\s\S]*resetInboundBillingState\(\)/)
})
test('protects unsaved drafts and exposes recoverable load failures', () => {
assert.match(source, /useUnsavedWorkRegistration\(/)
assert.match(source, /routeKey: unsavedRouteKey/)
assert.doesNotMatch(source, /onBeforeRouteLeave|beforeunload/)
assert.match(source, /confirmDiscardChanges/)
assert.match(source, /:before-close="requestCloseEditor"/)
assert.match(source, /store\.loadError/)
assert.match(source, /订单数据加载失败/)
assert.match(source, /订单详情加载失败/)
assert.match(source, /部分订单配套数据加载失败/)
assert.doesNotMatch(source, /往来单位加载失败/)
assert.match(source, /企业资料加载失败/)
assert.match(source, /@click="loadPageData"/)
assert.match(source, /@click="retryDetail"/)
})
test('copies reactive order drafts into plain request data', () => {
assert.match(source, /function cloneDraft\(draft: FirstLegOrderDraft\)/)
assert.match(source, /items: draft\.items\.map\(\(item\) => \(\{ \.\.\.item \}\)\)/)
assert.match(source, /store\.saveDraft\(cloneDraft\(form\)/)
assert.match(source, /editingId\.value = savedOrder\.id/)
assert.match(source, /store\.submitSaved\(savedOrder\.id/)
assert.doesNotMatch(source, /structuredClone\((?:form|order\.)/)
})
test('opens the server-owned inbound-code association from the order detail', () => {
assert.match(source, /import InboundCodeAssociationDialog/)
assert.match(source, /function openInboundCodeAssociation\(inboundCode: string\)/)
assert.doesNotMatch(source, /#inboundCode[\s\S]*?openInboundCodeAssociation\(\(row as FirstLegOrder\)\.businessSummary\.inboundCode\)/)
assert.match(source, /<dt>入仓码<\/dt><dd><button[\s\S]*?openInboundCodeAssociation\(detailOrder\.businessSummary\.inboundCode\)/)
assert.match(source, /<InboundCodeAssociationDialog[\s\S]*?:inbound-code="inboundAssociationCode"/)
assert.doesNotMatch(source, /openInboundCodeAssociation\([^)]*\.id\)/)
})
test('opens customer customs documents only for server-confirmed tax-refund orders after loading', () => {
assert.match(source, /!isCustomsOrderCandidate\(order\)/)
assert.match(source, /item\.orderId === order\.id && item\.taxMode === 'TAX_REFUND'/)
assert.match(source, /getMerchantCustomsForOrder\(order\.id\)/)
assert.match(source, /if \(eligibleCustomsDetail\(order\)\) \{\s*actions\.push\(\{ key: 'customs', label: '报关资料'/)
assert.match(source, /if \(eligibleCustomsDetail\(order\)\) \{\s*actions\.push\(\{ key: 'customs', label: '报关资料'/)
assert.match(source, /<CustomsDocumentsDialog[\s\S]*?:can-edit="auth\.hasPermission\('order:first-leg:edit'\)"/)
assert.doesNotMatch(customsApiSource, /customs\/cases\//)
})
test('supports the frozen customs document workflow and customer-safe downloads', () => {
for (const path of [
'/merchant/v1/customs/orders/',
'/merchant/v1/customs/loading-plans/',
'/merchant/v1/customs/documents/',
'/inbounds',
'/receipt/download',
]) assert.match(customsApiSource, new RegExp(path.replaceAll('/', '\\/')))
assert.match(customsApiSource, /MAX_CUSTOMS_FILE_SIZE = 50 \* 1024 \* 1024/)
assert.match(customsApiSource, /data: \{ inboundIds, expectedVersion: document\.source\.version \}/)
assert.match(customsApiSource, /requestId: error\.requestId/)
assert.match(customsDialogSource, /客户原始资料已冻结,仍可确认或打回待确认的报关行回执/)
assert.match(customsDialogSource, /v-if="detail.status === 'COMPLETED'"[^>]*><strong>报关已完成/)
assert.match(customsDialogSource, /真实报关完税文件已齐全/)
assert.match(customsDialogSource, /item\.finalDocument/)
assert.match(customsDialogSource, /资料版本历史/)
assert.match(customsDialogSource, /尚未上传退税报关资料/)
assert.match(customsDialogSource, /退税报关资料加载失败/)
assert.doesNotMatch(customsDialogSource, /accept=/)
})
test('uses the global inbound-code policy to require old-system codes during parallel running', () => {
assert.match(source, /getMerchantInboundCodePolicy\(\)/)
assert.match(source, /async function validateEditorForm\(\)[\s\S]*?const policy = await getMerchantInboundCodePolicy\(\)/)
assert.match(source, /if \(policy\.mode === 'AUTO' && !editingId\.value\) form\.inboundCode = ''/)
assert.match(source, /const manualInboundCodeRequired = computed/)
assert.match(source, /v-if="manualInboundCodeRequired" label="旧系统入仓码"/)
assert.match(source, /当前全系统处于旧系统并跑模式/)
assert.match(source, /当前为旧系统并跑模式,请填写旧系统入仓码/)
})
test('shows authoritative warehouse, loading and sailing fields on desktop and narrow screens', () => {
for (const label of ['国内收货仓', '期望出运', '柜号 / 柜次', '船名 / 航次', '预计船期', '更新时间']) {
assert.match(source, new RegExp(label.replace('/', '\\/')))
}
assert.match(source, /businessSummary\.cabinetNo \|\| .*businessSummary\.containerNo/)
assert.match(source, /businessSummary\.vesselName.*businessSummary\.voyageNo/s)
assert.match(source, /businessSummary\.estimatedSailingAt/)
})
test('loads complete order detail before editing and rejects an unavailable historical warehouse', () => {
assert.match(source, /async function openEdit\(order: FirstLegOrder\)/)
assert.match(source, /const \[detailedOrder\] = await Promise\.all\(\[store\.loadDetail\(order\.id\)/)
assert.match(source, /draftFromOrder\(detailedOrder\)/)
assert.match(source, /draft\.receivingWarehouseId = ''/)
assert.match(source, /draft\.channelCode = ''/)
assert.match(source, /原平台收货仓当前不可用,请重新选择可用收货仓/)
assert.match(source, /原目的仓当前不可用,请重新选择平台维护的目的仓/)
})