This commit is contained in:
2026-08-13 12:22:41 +08:00
parent 835ac0ad12
commit 8a8c4fa190
26 changed files with 572 additions and 148 deletions
@@ -6,11 +6,12 @@ import {
downloadTenantQualification,
getPlatformTenant,
listPlatformTenantMembers,
listPlatformTenantOrderCounts,
listTenantQualifications,
type PlatformTenantResource,
} from '@/modules/iam'
import { listPlatformFirstLegOrders, type PlatformFirstLegOrderResource } from '@/modules/first-leg-orders'
import { getPlatformTenantOrderStatistics, listPlatformFirstLegOrders, type PlatformFirstLegOrderResource } from '@/modules/first-leg-orders'
import { getFinanceCustomerAccountSummary } from '@/modules/finance'
import { getCustomerContainerTransitSummary } from '@/modules/container-loading'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import CustomerDetailView from './CustomerDetailView.vue'
@@ -36,15 +37,22 @@ vi.mock('@/modules/iam', () => ({
downloadTenantQualification: vi.fn(),
getPlatformTenant: vi.fn(),
listPlatformTenantMembers: vi.fn(),
listPlatformTenantOrderCounts: vi.fn(),
listTenantQualifications: vi.fn(),
qualificationFileName: vi.fn((documentUrl: string) => documentUrl.split('/').at(-1) || '资质文件'),
}))
vi.mock('@/modules/first-leg-orders', () => ({
getPlatformTenantOrderStatistics: vi.fn(),
listPlatformFirstLegOrders: vi.fn(),
}))
vi.mock('@/modules/finance', () => ({
CustomerStatementsPanel: { template: '<div>财务账单</div>' },
getFinanceCustomerAccountSummary: vi.fn(),
}))
vi.mock('@/modules/container-loading', () => ({ getCustomerContainerTransitSummary: vi.fn() }))
class ResizeObserverStub implements ResizeObserver {
observe() {}
unobserve() {}
@@ -77,7 +85,7 @@ describe('CustomerDetailView', () => {
setActivePinia(pinia)
const auth = useAuthStore()
auth.token = 'token'
auth.permissions = ['platform:customer:view', 'platform:user:list', 'platform:order:list']
auth.permissions = ['platform:customer:list', 'platform:user:list', 'platform:order:list', 'platform:finance:customer-account:list', 'platform:firstleg:container:list']
useAccessPreviewStore().initializeAfterLogin()
vi.mocked(getPlatformTenant).mockResolvedValue(tenant)
vi.mocked(listTenantQualifications).mockResolvedValue([{
@@ -88,7 +96,9 @@ describe('CustomerDetailView', () => {
membershipId: 'member-1', userId: 'user-1', authUserId: 'auth-1', username: 'merchant_admin', displayName: '张三',
phone: '13800138000', email: 'ops@example.com', memberType: 'OWNER', memberNo: null, status: 'ACTIVE', roleIds: [], roleNames: ['企业管理员'],
}])
vi.mocked(listPlatformTenantOrderCounts).mockResolvedValue([{ tenantId: tenant.id, orderCount: 12 }])
vi.mocked(getPlatformTenantOrderStatistics).mockResolvedValue({ tenantId: String(tenant.id), totalSubmittedOrders: 12, currentMonthSubmittedOrders: 3, monthStart: '2026-08-01T00:00:00+08:00', nextMonthStart: '2026-09-01T00:00:00+08:00', zoneId: 'Asia/Shanghai', asOf: '2026-08-13T12:00:00+08:00' })
vi.mocked(getFinanceCustomerAccountSummary).mockResolvedValue({ customerId: String(tenant.id), currency: 'CNY', totalReceivable: '123456.78', receivedAmount: '100000', outstandingReceivable: '23456.78', pendingBilling: '0', outstandingPayable: '0', asOf: '2026-08-13T04:00:00Z', lastChangedAt: null })
vi.mocked(getCustomerContainerTransitSummary).mockResolvedValue({ customerId: String(tenant.id), containerCount: 2, totalTeu: '2.25', unknownContainerCount: 0, asOf: '2026-08-13T12:00:00', definition: '已开船未到仓' })
vi.mocked(listPlatformFirstLegOrders).mockResolvedValue({
records: [{
id: 'order-1', tenantId: tenant.id, orderNo: 'BY20260807001', customerId: null, customerName: tenant.tenantName,
@@ -103,20 +113,25 @@ describe('CustomerDetailView', () => {
vi.mocked(downloadTenantQualification).mockResolvedValue(new Blob(['image'], { type: 'image/png' }))
})
it('renders enterprise users and includes the owner in customer statistics', async () => {
it('renders three operating metrics and keeps enterprise users in detail content', async () => {
mount(CustomerDetailView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
expect([...document.querySelectorAll('.detail-tabs button')].map((button) => button.getAttribute('aria-label'))).toEqual([
'基本信息', '业务概况', '运单记录', '财务账单', '计费规则', '操作日志',
])
expect(document.body.textContent).toContain('企业用户1人')
expect(document.body.textContent).toContain('1 人正常使用')
expect(document.body.textContent).toContain('累计运单12票本月 3 票')
expect(document.body.textContent).toContain('累计应收12.345678万元未结清 2.345678 万元')
expect(document.body.textContent).toContain('在途柜量2.25TEU2 柜')
expect([...document.querySelectorAll('.overview-strip article')]).toHaveLength(3)
expect(document.querySelector('.overview-strip')?.textContent).not.toContain('企业用户')
expect(document.body.textContent).toContain('merchant_admin')
expect(document.body.textContent).toContain('企业管理员')
expect(listPlatformTenantMembers).toHaveBeenCalledWith(tenant.id)
expect(document.body.textContent).toContain('已替换')
expect(document.body.textContent).toContain('12票')
expect(getPlatformTenantOrderStatistics).toHaveBeenCalledWith(tenant.id)
expect(getFinanceCustomerAccountSummary).toHaveBeenCalledWith(tenant.id)
expect(getCustomerContainerTransitSummary).toHaveBeenCalledWith(tenant.id)
const ordersTab = document.querySelector<HTMLButtonElement>('.detail-tabs button[aria-label="运单记录"]')
ordersTab?.click()
@@ -190,7 +205,7 @@ describe('CustomerDetailView', () => {
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(getPlatformTenantOrderStatistics).mockImplementation(async (tenantId) => ({ tenantId, totalSubmittedOrders: 0, currentMonthSubmittedOrders: 0, monthStart: '2026-08-01', nextMonthStart: '2026-09-01', zoneId: 'Asia/Shanghai', asOf: '2026-08-13' }))
vi.mocked(listPlatformFirstLegOrders).mockResolvedValue({ records: [], page: 1, size: 10, total: 0, pages: 0 })
mount(CustomerDetailView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
@@ -206,4 +221,31 @@ describe('CustomerDetailView', () => {
expect(document.body.textContent).toContain('远航供应链')
expect(document.body.textContent).not.toContain('柏悦物流')
})
it('does not request metrics without their independent permissions', async () => {
const auth = useAuthStore()
auth.permissions = ['platform:customer:list', 'platform:user:list']
useAccessPreviewStore().initializeAfterLogin()
mount(CustomerDetailView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
expect(getPlatformTenantOrderStatistics).not.toHaveBeenCalled()
expect(getFinanceCustomerAccountSummary).not.toHaveBeenCalled()
expect(getCustomerContainerTransitSummary).not.toHaveBeenCalled()
expect(document.querySelector('.overview-strip')?.textContent?.match(/暂无查看权限/g)).toHaveLength(3)
})
it('keeps metric failures independent and never renders them as zero', async () => {
vi.mocked(getFinanceCustomerAccountSummary).mockRejectedValueOnce(new Error('finance unavailable'))
mount(CustomerDetailView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
const cards = [...document.querySelectorAll('.overview-strip article')]
expect(cards[0]?.textContent).toContain('12票')
expect(cards[1]?.textContent).toContain('—加载失败')
expect(cards[1]?.textContent).not.toContain('0万元')
expect(cards[2]?.textContent).toContain('2.25TEU')
})
})