104 lines
7.7 KiB
TypeScript
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')
|
|
})
|
|
})
|