diff --git a/src/modules/customers/components/CustomerBillingRulesPanel.test.ts b/src/modules/customers/components/CustomerBillingRulesPanel.test.ts index 591326b3..34c67461 100644 --- a/src/modules/customers/components/CustomerBillingRulesPanel.test.ts +++ b/src/modules/customers/components/CustomerBillingRulesPanel.test.ts @@ -2,25 +2,28 @@ import ElementPlus from 'element-plus' import { createPinia, setActivePinia } from 'pinia' import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - getCustomerDimensionConfig, - updateCustomerDimensionConfig, - type CustomerDimensionConfig, -} from '@/modules/dimension-config' +import { getCustomerDimensionConfig, updateCustomerDimensionConfig, type CustomerDimensionConfig } from '@/modules/dimension-config' import { useAccessPreviewStore } from '@/core/access/accessPreview' import { useAuthStore } from '@/core/auth/auth' import CustomerBillingRulesPanel from './CustomerBillingRulesPanel.vue' -vi.mock('@/modules/dimension-config', () => ({ - getCustomerDimensionConfig: vi.fn(), - updateCustomerDimensionConfig: vi.fn(), -})) +vi.mock('@/modules/dimension-config', () => ({ getCustomerDimensionConfig: vi.fn(), updateCustomerDimensionConfig: vi.fn() })) -const rule: CustomerDimensionConfig = { +const boxes = [ + ['SMALL', '小箱', 30, 20, 20, '标准小箱'], + ['MEDIUM', '中箱', 40, 30, 30, '标准中箱'], + ['LARGE', '大箱', 50, 40, 40, '标准大箱'], + ['EXTRA_LARGE', '特大箱', 60, 50, 50, '特大箱'], +] as const +const config: CustomerDimensionConfig = { tenantId: '8', - edgeAdditionCm: 2, - version: 3, - updatedAt: '2026-08-07T10:20:00', + rules: boxes.map(([boxType, boxTypeName, innerLengthCm, innerWidthCm, innerHeightCm, remark], index) => ({ + id: String(100 + index), + boxType, boxTypeName, innerLengthCm, innerWidthCm, innerHeightCm, edgeAdditionCm: 2, + outerLengthCm: innerLengthCm + 4, outerWidthCm: innerWidthCm + 4, outerHeightCm: innerHeightCm + 4, + volumeCbm: Number(((innerLengthCm + 4) * (innerWidthCm + 4) * (innerHeightCm + 4) / 1_000_000).toFixed(3)), + remark, version: 3, createdAt: '2026-08-07T10:00:00', updatedAt: '2026-08-07T10:20:00', + })), } enableAutoUnmount(afterEach) @@ -29,79 +32,47 @@ describe('CustomerBillingRulesPanel', () => { beforeEach(() => { vi.clearAllMocks() document.body.innerHTML = '' - const pinia = createPinia() - setActivePinia(pinia) - const auth = useAuthStore() - auth.token = 'token' - auth.permissions = ['firstleg:dimension-config:view', 'firstleg:dimension-config:edit'] + const pinia = createPinia(); setActivePinia(pinia) + const auth = useAuthStore(); auth.token = 'token'; auth.permissions = ['firstleg:dimension-config:view', 'firstleg:dimension-config:edit'] useAccessPreviewStore().initializeAfterLogin() - vi.mocked(getCustomerDimensionConfig).mockResolvedValue(rule) - vi.mocked(updateCustomerDimensionConfig).mockResolvedValue({ ...rule, version: 4 }) + vi.mocked(getCustomerDimensionConfig).mockResolvedValue(config) + vi.mocked(updateCustomerDimensionConfig).mockResolvedValue(config) }) - it('shows one customer dimension rule instead of the old pricing versions', async () => { - mount(CustomerBillingRulesPanel, { - attachTo: document.body, - props: { customerId: '8', customerName: '柏悦物流', customerCode: 'TEN8' }, - global: { plugins: [ElementPlus] }, - }) + it('renders the four fixed carton dimension rows and calculated values', async () => { + mount(CustomerBillingRulesPanel, { attachTo: document.body, props: { customerId: '8', customerName: '柏悦物流' }, global: { plugins: [ElementPlus] } }) await flushPromises() - expect(getCustomerDimensionConfig).toHaveBeenCalledWith('8') - expect(document.body.textContent).toContain('尺寸计算规则') - expect(document.body.textContent).toContain('当前每侧加宽') - expect(document.body.textContent).toContain('+2cm') - expect(document.body.textContent).toContain('34 × 24 × 24 cm') - expect(document.body.textContent).toContain('0.020 CBM') - expect(document.body.textContent).not.toContain('规则版本') - expect(document.body.textContent).not.toContain('小箱') + expect(document.body.textContent).toContain('按箱规尺寸计算') + expect(document.body.textContent).toContain('小箱') + expect(document.body.textContent).toContain('特大箱') + expect(document.body.textContent).toContain('34 × 24 × 24') + expect(document.body.textContent).toContain('0.020') + expect(document.body.textContent).toContain('标准中箱') }) - it('edits the single value and submits the optimistic lock version', async () => { - mount(CustomerBillingRulesPanel, { - attachTo: document.body, - props: { customerId: '8', customerName: '柏悦物流' }, - global: { plugins: [ElementPlus] }, - }) + it('submits all four rows in one update request', async () => { + mount(CustomerBillingRulesPanel, { attachTo: document.body, props: { customerId: '8', customerName: '柏悦物流' }, global: { plugins: [ElementPlus] } }) await flushPromises() - - const editButton = [...document.querySelectorAll('button')] - .find((button) => button.textContent?.trim() === '编辑规则') - editButton?.click() + ;[...document.querySelectorAll('button')].find((button) => button.textContent?.trim() === '编辑规则')?.click() await flushPromises() - - expect(document.body.textContent).toContain('编辑尺寸计算规则') - expect(document.body.textContent).toContain('实际内径 30 × 20 × 20 cm') - const saveButton = [...document.querySelectorAll('button')] - .find((button) => button.textContent?.trim() === '保存规则') - saveButton?.click() - await flushPromises() - - expect(updateCustomerDimensionConfig).toHaveBeenCalledWith('8', { - edgeAdditionCm: 2, - version: 3, - }) + const saveButton = [...document.querySelectorAll('button')].find((button) => button.textContent?.trim() === '保存规则') + saveButton?.click(); await flushPromises() + expect(updateCustomerDimensionConfig).toHaveBeenCalledWith('8', { rules: expect.arrayContaining([expect.objectContaining({ boxType: 'SMALL', version: 3 }), expect.objectContaining({ boxType: 'EXTRA_LARGE', version: 3 })]) }) + const submittedRules = vi.mocked(updateCustomerDimensionConfig).mock.calls[0][1].rules + expect(submittedRules).toHaveLength(4) + expect(Object.keys(submittedRules[0]).sort()).toEqual([ + 'boxType', 'edgeAdditionCm', 'innerHeightCm', 'innerLengthCm', 'innerWidthCm', 'remark', 'version', + ]) }) - it('keeps the editor open when the versioned save fails', async () => { - vi.mocked(updateCustomerDimensionConfig).mockRejectedValueOnce(new Error('规则已被其他人更新')) - mount(CustomerBillingRulesPanel, { - attachTo: document.body, - props: { customerId: '8', customerName: '柏悦物流' }, - global: { plugins: [ElementPlus] }, - }) + it('keeps the editor open when the whole-set save fails', async () => { + vi.mocked(updateCustomerDimensionConfig).mockRejectedValueOnce(new Error('规则版本冲突')) + mount(CustomerBillingRulesPanel, { attachTo: document.body, props: { customerId: '8', customerName: '柏悦物流' }, global: { plugins: [ElementPlus] } }) await flushPromises() - - const editButton = [...document.querySelectorAll('button')] - .find((button) => button.textContent?.trim() === '编辑规则') - editButton?.click() + ;[...document.querySelectorAll('button')].find((button) => button.textContent?.trim() === '编辑规则')?.click(); await flushPromises() + ;[...document.querySelectorAll('button')].find((button) => button.textContent?.trim() === '保存规则')?.click() await flushPromises() - const saveButton = [...document.querySelectorAll('button')] - .find((button) => button.textContent?.trim() === '保存规则') - saveButton?.click() - await flushPromises() - - expect(document.body.textContent).toContain('编辑尺寸计算规则') - expect(updateCustomerDimensionConfig).toHaveBeenCalledWith('8', expect.objectContaining({ version: 3 })) + expect(document.body.textContent).toContain('编辑箱规尺寸') }) }) diff --git a/src/modules/customers/components/CustomerBillingRulesPanel.vue b/src/modules/customers/components/CustomerBillingRulesPanel.vue index fd5d2964..0f7f2aee 100644 --- a/src/modules/customers/components/CustomerBillingRulesPanel.vue +++ b/src/modules/customers/components/CustomerBillingRulesPanel.vue @@ -1,64 +1,52 @@ diff --git a/src/modules/customers/pages/CustomerDetailView.test.ts b/src/modules/customers/pages/CustomerDetailView.test.ts index 2d8d64f5..e23c7fcb 100644 --- a/src/modules/customers/pages/CustomerDetailView.test.ts +++ b/src/modules/customers/pages/CustomerDetailView.test.ts @@ -15,11 +15,19 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview' import { useAuthStore } from '@/core/auth/auth' import CustomerDetailView from './CustomerDetailView.vue' -const { routerPush } = vi.hoisted(() => ({ routerPush: vi.fn() })) +const { routerPush, routeControl } = vi.hoisted(() => ({ + routerPush: vi.fn(), + routeControl: { setTenantId: (_tenantId: string) => {} }, +})) vi.mock('vue-router', async (importOriginal) => ({ ...await importOriginal(), - useRoute: () => ({ params: { tenantId: '8' } }), + ...await (async () => { + const { reactive } = await import('vue') + const route = reactive({ params: { tenantId: '8' } }) + routeControl.setTenantId = (tenantId: string) => { route.params.tenantId = tenantId } + return { useRoute: () => route } + })(), useRouter: () => ({ push: routerPush }), })) @@ -59,6 +67,7 @@ describe('CustomerDetailView', () => { beforeEach(() => { vi.clearAllMocks() + routeControl.setTenantId('8') vi.stubGlobal('ResizeObserver', ResizeObserverStub) Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', { configurable: true, value: vi.fn() }) Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: vi.fn(() => 'blob:qualification-preview') }) @@ -121,7 +130,7 @@ describe('CustomerDetailView', () => { .find((button) => button.textContent?.trim() === '计费规则') billingTab?.click() await flushPromises() - expect(document.body.textContent).toContain('暂无计费规则查看权限') + expect(document.body.textContent).toContain('暂无尺寸计算规则查看权限') expect(document.body.textContent).not.toContain('计费规则尚未接入') }) @@ -159,4 +168,28 @@ describe('CustomerDetailView', () => { expect(listPlatformFirstLegOrders).toHaveBeenCalledTimes(3) expect(listPlatformFirstLegOrders).toHaveBeenLastCalledWith({ tenantId: tenant.id, page: 1, size: 10 }) }) + + it('reloads a reused detail view and ignores the previous customer response', async () => { + const secondTenant = { ...tenant, id: '9', tenantCode: 'TEN9', tenantName: '远航供应链', legalName: '远航供应链有限公司' } + let resolveFirstTenant!: (value: PlatformTenantResource) => void + const firstTenantRequest = new Promise((resolve) => { resolveFirstTenant = resolve }) + vi.mocked(getPlatformTenant).mockImplementation((tenantId) => tenantId === '8' ? firstTenantRequest : Promise.resolve(secondTenant)) + vi.mocked(listTenantQualifications).mockResolvedValue([]) + vi.mocked(listPlatformTenantMembers).mockResolvedValue([]) + vi.mocked(listPlatformTenantOrderCounts).mockImplementation(async (tenantIds) => [{ tenantId: tenantIds[0], orderCount: 0 }]) + vi.mocked(listPlatformFirstLegOrders).mockResolvedValue({ records: [], page: 1, size: 10, total: 0, pages: 0 }) + + mount(CustomerDetailView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } }) + await flushPromises() + routeControl.setTenantId('9') + await flushPromises() + + expect(getPlatformTenant).toHaveBeenCalledWith('9') + expect(document.body.textContent).toContain('远航供应链') + + resolveFirstTenant(tenant) + await flushPromises() + expect(document.body.textContent).toContain('远航供应链') + expect(document.body.textContent).not.toContain('柏悦物流') + }) }) diff --git a/src/modules/customers/pages/CustomerDetailView.vue b/src/modules/customers/pages/CustomerDetailView.vue index 5d60b374..8dd29170 100644 --- a/src/modules/customers/pages/CustomerDetailView.vue +++ b/src/modules/customers/pages/CustomerDetailView.vue @@ -1,5 +1,5 @@