Files
admin-web/src/modules/overseas-business/components/StorageRentPanel.test.ts
T
2026-10-26 13:43:08 +08:00

104 lines
7.7 KiB
TypeScript

import { createRequire } from 'node:module'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
import * as api from '../api/storageRent'
import type { StorageRentLot } from '../api/storageRent'
import StorageRentPanel from './StorageRentPanel.vue'
const { default: ElementPlus } = createRequire(import.meta.url)('element-plus') as typeof import('element-plus')
vi.mock('../api/storageRent', () => ({ getStorageRentLot: vi.fn(), listStorageRentLots: vi.fn(), listStorageRentWarehouses: vi.fn() }))
const lot: StorageRentLot = {
id: '9223372036854775001', customerId: '1', customerName: '测试客户', businessType: 'LASTLEG_ORDER', businessNo: 'FL1',
warehouseId: '2', warehouseName: '测试仓', batchNo: 'B1', inboundAt: '2026-10-22T00:00:00Z', storageDays: 1,
originalQuantity: 10, remainingQuantity: 8, quantityUnit: 'PACKAGE', billingQuantity: 1, billingUnit: 'CBM',
ruleCode: 'TRANSIT_V1', status: 'IN_STOCK', generatedAmount: 0, generatedCurrency: null, generatedAmounts: [],
}
enableAutoUnmount(afterEach)
beforeEach(() => {
vi.resetAllMocks()
vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} })
vi.mocked(api.listStorageRentLots).mockResolvedValue({ records: [lot], total: 1, page: 1, size: 20, pages: 1 })
vi.mocked(api.listStorageRentWarehouses).mockResolvedValue([{ id: '2', warehouseName: '测试仓' }])
vi.mocked(api.getStorageRentLot).mockResolvedValue({ lot, charges: [{
id: '3', outboundNo: 'OUT1', businessNo: 'FL1', quantity: 2, storageDays: 1, originalAmount: 0, currency: 'USD',
settlementAmount: 0, currencyRate: 7, status: 'SETTLED', createdAt: '2026-10-22T01:00:00Z', outboundAt: '2026-10-22T01:00:00Z',
}] })
})
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); document.body.innerHTML = '' })
describe('仓租列表服务端契约', () => {
it('shows storage age and remaining quantity and traces each half pallet without financial actions', async () => {
const returned = { ...lot, businessType: 'RETURN_ORDER', businessNo: 'TH1', quantityUnit: 'PIECE', originalQuantity: 6, remainingQuantity: 2, billingQuantity: 1.5, billingUnit: 'PALLET', status: 'IN_STOCK', generatedAmounts: [{ currency: 'USD', amount: 1.5 }], settledAmount: 10, chargeCount: 2, settledChargeCount: 2 }
vi.mocked(api.listStorageRentLots).mockResolvedValue({ records: [returned], total: 1, page: 1, size: 20, pages: 1 })
vi.mocked(api.getStorageRentLot).mockResolvedValue({ lot: returned, charges: ['TH1', 'CD1'].map((businessNo, index) => ({ id: String(index), outboundNo: `OUT${index}`, businessNo, quantity: 2, storageDays: 1, originalAmount: 0.75, currency: 'USD', settlementAmount: 5, currencyRate: 6.67, status: 'SETTLED', createdAt: '', outboundAt: '' })) })
const wrapper = mount(StorageRentPanel, { attachTo: document.body, props: { customers: [] }, global: { plugins: [ElementPlus] } })
await flushPromises()
const body = wrapper.get('.el-table__body-wrapper')
expect(body.text()).toContain('2 件')
expect(wrapper.text()).toContain('库龄(天)')
expect(wrapper.text()).toContain('来源单号')
expect(body.text()).toContain('TH1')
expect(wrapper.text()).not.toContain('仓租 / 扣款')
await body.findAll('button').find(button => button.text().trim() === '详情')!.trigger('click'); await flushPromises()
const dialog = document.body.querySelector('[role="dialog"]')!
expect(dialog.textContent).toContain('0.5 托')
expect(dialog.textContent).toContain('OUT0')
expect(dialog.textContent).toContain('OUT1')
expect(dialog.textContent).not.toContain('USD')
expect(dialog.textContent).not.toContain('审核')
expect(dialog.textContent).toContain('CD1')
expect(dialog.textContent).toContain('来源单号')
expect(dialog.textContent).toContain('出库业务单号')
})
it('displays and filters first-leg lots using LASTLEG_ORDER and retains inbound and outbound evidence', async () => {
const wrapper = mount(StorageRentPanel, { attachTo: document.body, props: { customers: [] }, global: { plugins: [ElementPlus] } })
await flushPromises()
const toolbar = wrapper.getComponent(QueryToolbar)
expect(api.listStorageRentLots).toHaveBeenLastCalledWith(expect.objectContaining({ status: 'IN_STOCK', page: 1 }))
expect(toolbar.props('fields').some(field => field.key === 'status')).toBe(false)
const options = toolbar.props('fields').find(field => field.key === 'businessType')?.options
expect(options).toContainEqual({ value: 'LASTLEG_ORDER', label: '头程入仓' })
expect(options).not.toContainEqual({ value: 'FIRST_LEG', label: '头程入仓' })
expect(wrapper.get('.el-table__body-wrapper').text()).toContain('头程入仓')
toolbar.vm.$emit('search', { businessType: 'LASTLEG_ORDER' })
await flushPromises()
expect(api.listStorageRentLots).toHaveBeenLastCalledWith(expect.objectContaining({ businessType: 'LASTLEG_ORDER', status: 'IN_STOCK', page: 1 }))
toolbar.vm.$emit('search', {})
await flushPromises()
expect(api.listStorageRentLots).toHaveBeenLastCalledWith(expect.objectContaining({ status: 'IN_STOCK', page: 1 }))
const detailButton = wrapper.findAll('.el-table__body-wrapper button').find(button => button.text().trim() === '详情')
await detailButton!.trigger('click'); await flushPromises()
expect(api.getStorageRentLot).toHaveBeenCalledWith(lot.id)
const dialog = document.body.querySelector('[role="dialog"]')!
expect(dialog.textContent).toContain('2026-10-22')
expect(dialog.textContent).toContain('8 箱')
expect(dialog.textContent).toContain('OUT1')
expect(dialog.textContent).not.toContain('实扣金额')
})
it('keeps inventory focused on age and outbound history even when charges use different currencies', async () => {
const mixedLot = { ...lot, generatedAmount: null, generatedCurrency: null, generatedAmounts: [{ currency: 'USD', amount: 12 }, { currency: 'CNY', amount: 23 }] }
vi.mocked(api.listStorageRentLots).mockResolvedValue({ records: [mixedLot], total: 1, page: 1, size: 20, pages: 1 })
vi.mocked(api.getStorageRentLot).mockResolvedValue({ lot: mixedLot, charges: [
{ id: '3', outboundNo: 'OUT1', businessNo: 'FL1', quantity: 1, storageDays: 8, originalAmount: 12, currency: 'USD', settlementAmount: 84, currencyRate: 7, status: 'SETTLED', createdAt: '2026-10-22T01:00:00Z', outboundAt: '2026-10-22T01:00:00Z' },
{ id: '4', outboundNo: 'OUT2', businessNo: 'FL1', quantity: 1, storageDays: 9, originalAmount: 23, currency: 'CNY', settlementAmount: 23, currencyRate: 1, status: 'SETTLED', createdAt: '2026-10-22T02:00:00Z', outboundAt: '2026-10-22T02:00:00Z' },
] })
const wrapper = mount(StorageRentPanel, { attachTo: document.body, props: { customers: [] }, global: { plugins: [ElementPlus] } })
await flushPromises()
const body = wrapper.get('.el-table__body-wrapper')
expect(body.text()).toContain('8 箱')
expect(body.text()).not.toContain('USD')
expect(body.text()).not.toContain('CNY')
await body.findAll('button').find(button => button.text().trim() === '详情')!.trigger('click'); await flushPromises()
const dialog = document.body.querySelector('[role="dialog"]')!
expect(dialog.textContent).toContain('OUT1')
expect(dialog.textContent).toContain('OUT2')
expect(dialog.textContent).toContain('计租天数')
const rows = Array.from(dialog.querySelectorAll('.el-table__body-wrapper tbody tr'))
expect(rows[0]?.textContent).toContain('8')
expect(rows[1]?.textContent).toContain('9')
expect(dialog.textContent).not.toContain('USD')
expect(dialog.textContent).not.toContain('CNY')
})
})