import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' import type { PlatformTenantResource } from '@/modules/iam' import { loadAllActiveCustomers } from '../task-list' function activeCustomer(id: string, tenantName: string, legalName: string | null, tenantCode: string): PlatformTenantResource { return { id, tenantName, legalName, tenantCode, status: 'ACTIVE', legalRepresentative: null, unifiedSocialCreditCode: null, contactName: null, contactPhone: null, contactEmail: null, servicePackage: null, contactWechat: null, salesOwnerName: null, paymentTermDays: null, enterpriseType: null, establishedDate: null, registeredCapital: null, registeredCapitalCurrency: null, registeredAddress: null, internalRemark: null, createdAt: null, approvedAt: null, } } describe('InboundTaskList measurement scope', () => { it('supports selecting only pending measurement reviews for batch approval', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain("key: 'selection', type: 'selection'") expect(source).toContain("row.measureReviewStatus === 'PENDING'") expect(source).toContain('@selection-change="handleReviewSelectionChange"') expect(source).toContain('batchApproveMeasurements') expect(source).toContain('批量审核通过') expect(source).toContain('getMeasurementReviewDetail') expect(source).toContain('approveMeasurement') expect(source).toContain('expectedVersion: detail.inboundVersion') expect(source).not.toContain('expectedVersion: detail.version') expect(source).toContain('approval.initialPricingSucceeded') expect(source).toContain('费用初算未生成,可补价后重新计算') }) it('uses the inbound tenant isolation key for measurement and review requests', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain(':tenant-id="selectedInbound.tenantId"') expect(source).toContain(':customer-id="selectedInbound.tenantId"') expect(source).toContain(':customer-name="selectedInbound.customerName || selectedInbound.companyName"') expect(source).toContain(':channel-name="selectedInbound.channelName"') expect(source).not.toContain('selectedInbound.customerId || selectedInbound.tenantId') }) it('opens the unique shared measurement dialog and refreshes without duplicating its workflow', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain("import InboundMeasurementDialog from '../measurement/InboundMeasurementDialog.vue'") expect(source).toContain(' { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') const queryFieldsSource = source.slice(source.indexOf('const queryFields'), source.indexOf('const columns')) expect(source).toContain('loadAllActiveCustomers') expect(queryFieldsSource).toContain("type: 'select'") expect(queryFieldsSource).toContain('filterable: true') expect(queryFieldsSource).toContain('label: customer.legalName?.trim() || customer.tenantName') expect(queryFieldsSource).not.toContain('tenantCode') expect(queryFieldsSource).not.toContain('客户编码') }) it('clears date conditions safely and uses explicit select placeholders', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') const queryFieldsSource = source.slice(source.indexOf('const queryFields'), source.indexOf('const columns')) expect(source).toContain('Array.isArray(filters.value.expectedArrivalRange)') expect(queryFieldsSource).toContain("placeholder: customerLoadError.value ? '客户列表加载失败' : '请选择客户'") expect(queryFieldsSource).toContain("placeholder: '请选择仓库'") }) it('requests every active-customer page and merges name-only options', async () => { const calls: Array<{ page: number; size: number; status: 'ACTIVE' }> = [] const records = await loadAllActiveCustomers(async (query) => { calls.push(query) if (query.page === 1) { return { pages: 2, records: [activeCustomer('10001', '客户一', '客户一有限公司', 'CUS-001')], } } return { pages: 2, records: [activeCustomer('10002', '客户二', null, 'CUS-002')], } }) expect(calls).toEqual([ { page: 1, size: 200, status: 'ACTIVE' }, { page: 2, size: 200, status: 'ACTIVE' }, ]) expect(records.map((customer) => customer.legalName?.trim() || customer.tenantName)).toEqual(['客户一有限公司', '客户二']) expect(records.map((customer) => customer.legalName?.trim() || customer.tenantName).join(' ')).not.toContain('CUS-') }) it('opens the shared unified order detail from the view action', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain("name: 'unified-order-detail', params: { orderId: row.orderId }, query: { tenantId: row.tenantId }") expect(source).toContain("access.can('platform:firstleg:inbound:view') && (row as InboundOrderSummary).orderId") expect(source).not.toContain("name: 'inbound-order-detail', params: { inboundId: row.id }") }) it('keeps relationship data entry out of measurement and review task lists', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).not.toContain('关联数据') expect(source).not.toContain('InboundCodeRelationshipDialog') expect(source).not.toContain('openRelationship') }) it('closes only after navigating an approved review to the measurement tab', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') const handler = source.slice(source.indexOf('async function handleTaskChanged'), source.indexOf('function handleReviewSelectionChange')) expect(handler).toContain("name: 'unified-order-detail-by-inbound-code'") expect(handler).toContain('params: { inboundCode }') expect(handler).toContain("query: { tab: 'measurement', from: 'measurement-review' }") expect(handler).toContain('await router.push') expect(handler.indexOf('await router.push')).toBeLessThan(handler.lastIndexOf('reviewDialogOpen.value = false')) expect(handler).toContain("getApiErrorDetails(error, '订单详情打开失败,请重试')") expect(handler).toContain("name: 'unified-order-detail'") expect(handler).toContain("query: { tenantId: target.tenantId, tab: 'measurement', from: 'measurement-review' }") expect(source).toContain(':post-approve-loading="reviewNavigationLoading"') expect(source).toContain(':approval-completed="reviewApproved"') expect(source).toContain(':close-on-click-modal="!reviewNavigationLoading"') expect(source).toContain('reviewNavigationError') expect(source).toContain('重新打开订单详情') expect(source).not.toContain('params: { inboundId: target.id }') }) it('shows authoritative measurement totals with operation first and no internal IDs as primary columns', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') const columnsSource = source.slice(source.indexOf('const columns'), source.indexOf('function text')) expect(columnsSource.indexOf("key: 'actions'")).toBeLessThan(columnsSource.indexOf("key: 'inboundCode'")) expect(columnsSource).toContain("key: 'customerName', label: '客户'") expect(columnsSource).toContain("key: 'channelName', label: '渠道'") expect(columnsSource).toContain("key: 'totalPackageCount', label: '总箱数'") expect(columnsSource).toContain("key: 'palletCount', label: '托盘数'") expect(columnsSource).toContain("key: 'actualGrossWeightKg', label: '实测毛重(kg)'") expect(columnsSource).toContain("key: 'actualNetWeightKg', label: '货物重量(kg)'") expect(columnsSource).toContain("key: 'actualVolumeCbm', label: '实测体积(m³)'") expect(columnsSource).toContain("key: 'measureCompletedAt', label: '提交时间'") expect(columnsSource).not.toContain("key: 'id'") expect(columnsSource).not.toContain("key: 'orderId'") expect(source).toContain('{{ formatNumber((row as InboundOrderSummary).actualGrossWeightKg) }}') expect(source).toContain('{{ formatNumber((row as InboundOrderSummary).actualNetWeightKg) }}') expect(source).not.toContain('actualGrossWeightKg ||') expect(source).not.toContain('actualNetWeightKg ||') }) it('renders missing list facts as an explicit dash instead of guessing values', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain("value === null || value === undefined || value === '' ? '—'") expect(source).toContain("value === null || value === undefined ? '—'") expect(source).toContain("if (!value) return '—'") }) it('renders first-leg business types with the shared Chinese labels', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain("import { firstLegBusinessTypeLabel } from '@/shared/utils/firstLegBusinessLabels'") expect(source).toContain('firstLegBusinessTypeLabel((row as InboundOrderSummary).businessType)') expect(source).not.toContain('text((row as InboundOrderSummary).businessType)') }) it('refreshes cached task lists when users return from measurement work', () => { const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8') expect(source).toContain('onActivated(() => {') expect(source).toContain('if (!activatedOnce)') expect(source).toContain('activatedOnce = true') expect(source).toContain('void loadTasks()') expect(source).toContain('onMounted(() => { void loadOptions(); void loadTasks() })') }) })