支持私人地址人工报价及海外入库托盘打印和短少确认
This commit is contained in:
@@ -10,12 +10,18 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import {
|
||||
adjustPublishedInboundFee,
|
||||
calculateInboundFee,
|
||||
confirmInboundFee,
|
||||
createManualInboundQuote,
|
||||
getLatestInboundFee,
|
||||
previewInboundFee,
|
||||
sendInboundFee,
|
||||
} from './api'
|
||||
import type { InboundFeeSnapshot } from './types'
|
||||
import InboundFeePanel from './InboundFeePanel.vue'
|
||||
import { getInboundMeasurement } from '../measurement/api'
|
||||
|
||||
vi.mock('../measurement/api', () => ({ getInboundMeasurement: vi.fn() }))
|
||||
|
||||
vi.mock('./api', async () => {
|
||||
const actual = await vi.importActual<typeof import('./api')>('./api')
|
||||
@@ -23,6 +29,9 @@ vi.mock('./api', async () => {
|
||||
...actual,
|
||||
getLatestInboundFee: vi.fn(),
|
||||
adjustPublishedInboundFee: vi.fn(),
|
||||
calculateInboundFee: vi.fn(),
|
||||
createManualInboundQuote: vi.fn(),
|
||||
previewInboundFee: vi.fn(),
|
||||
confirmInboundFee: vi.fn(),
|
||||
sendInboundFee: vi.fn(),
|
||||
}
|
||||
@@ -66,11 +75,11 @@ function mountPanel(permissions = [
|
||||
'firstleg:fba-pricing:adjust',
|
||||
'firstleg:fba-pricing:confirm',
|
||||
'firstleg:inbound:send-fee',
|
||||
]) {
|
||||
], extraProps: { destinationType?: string | null } = {}) {
|
||||
const pinia = setupPermissions(permissions)
|
||||
return mount(InboundFeePanel, {
|
||||
attachTo: document.body,
|
||||
props: { customerId: '10001', inboundId: '88' },
|
||||
props: { customerId: '10001', inboundId: '88', ...extraProps },
|
||||
global: { plugins: [pinia, ElementPlus] },
|
||||
})
|
||||
}
|
||||
@@ -83,6 +92,85 @@ describe('InboundFeePanel', () => {
|
||||
vi.mocked(adjustPublishedInboundFee).mockResolvedValue({ ...snapshot, feeVersion: 3, feeNo: 'FBAF-88-3' })
|
||||
vi.mocked(confirmInboundFee).mockResolvedValue({ ...snapshot, feeStatus: 'CONFIRMED', confirmedAt: '2026-08-04T10:01:00', sentAt: null })
|
||||
vi.mocked(sendInboundFee).mockResolvedValue(snapshot)
|
||||
vi.mocked(previewInboundFee).mockResolvedValue({ chargeableQuantity: 64.128, chargeUnit: 'KG',
|
||||
currency: 'CNY', priceMatched: false } as Awaited<ReturnType<typeof previewInboundFee>>)
|
||||
})
|
||||
|
||||
it('saves a private-address manual quote through the existing fee dialog', async () => {
|
||||
vi.mocked(getLatestInboundFee).mockRejectedValueOnce(new ApiResponseError('费用快照不存在', 'A0404', 404))
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValue({
|
||||
items: [{ totalWeightKg: 70.5, totalVolumeCbm: 0.384 }],
|
||||
actualNetWeightKg: 62.5, actualGrossWeightKg: 70.5, actualVolumeCbm: 0.384,
|
||||
} as Awaited<ReturnType<typeof getInboundMeasurement>>)
|
||||
vi.mocked(createManualInboundQuote).mockRejectedValueOnce(new Error('网络暂时不可用'))
|
||||
.mockResolvedValue({ ...snapshot, feeStatus: 'CALCULATED', rateCode: 'MANUAL_QUOTE' })
|
||||
const wrapper = mountPanel(['firstleg:fba-pricing:view', 'firstleg:inbound:confirm-send-fee'], { destinationType: 'PRIVATE' })
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find(button => button.textContent?.includes('人工头程报价'))!.click()
|
||||
await flushPromises()
|
||||
const save = [...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find(button => button.textContent?.includes('保存人工报价'))!
|
||||
expect(document.body.textContent).toContain('64.128 KG')
|
||||
expect(document.querySelectorAll('.trial-source-grid input[type="radio"]')).toHaveLength(2)
|
||||
expect(save.disabled).toBe(true)
|
||||
const price = document.querySelector<HTMLInputElement>('input[aria-label="人工单价"]')!
|
||||
price.value = '1.5'
|
||||
price.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
price.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
const reason = document.querySelector<HTMLTextAreaElement>('textarea[aria-label="报价原因"]')!
|
||||
reason.value = '私人地址测试报价'
|
||||
reason.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
await flushPromises()
|
||||
expect(save.disabled).toBe(false)
|
||||
save.click()
|
||||
await flushPromises()
|
||||
expect(wrapper.emitted('changed')).toBeUndefined()
|
||||
const originalKey = vi.mocked(createManualInboundQuote).mock.calls[0]![3]
|
||||
save.click()
|
||||
await flushPromises()
|
||||
expect(createManualInboundQuote).toHaveBeenCalledWith('10001', '88', {
|
||||
selectedWeightKg: 62.5, selectedVolumeCbm: 0.384, unitPrice: 1.5, reason: '私人地址测试报价',
|
||||
}, expect.any(String))
|
||||
expect(calculateInboundFee).not.toHaveBeenCalled()
|
||||
expect(createManualInboundQuote).toHaveBeenCalledTimes(2)
|
||||
expect(vi.mocked(createManualInboundQuote).mock.calls[1]![3]).toBe(originalKey)
|
||||
expect(wrapper.emitted('changed')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('requires the manual quote permission for private-address pricing', async () => {
|
||||
mountPanel(['firstleg:fba-pricing:view', 'firstleg:fba-pricing:calculate'], { destinationType: 'PRIVATE' })
|
||||
await flushPromises()
|
||||
expect([...document.querySelectorAll('button')].some(button => button.textContent?.includes('人工头程报价'))).toBe(false)
|
||||
})
|
||||
|
||||
it('allows retrying a failed server quantity preview before a private quote', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValue({
|
||||
items: [{ totalWeightKg: 70.5, totalVolumeCbm: 0.384 }],
|
||||
actualNetWeightKg: 62.5, actualGrossWeightKg: 70.5, actualVolumeCbm: 0.384,
|
||||
} as Awaited<ReturnType<typeof getInboundMeasurement>>)
|
||||
vi.mocked(previewInboundFee).mockRejectedValueOnce(new Error('计费量暂时不可读'))
|
||||
mountPanel(['firstleg:fba-pricing:view', 'firstleg:inbound:confirm-send-fee'], { destinationType: 'PRIVATE' })
|
||||
await flushPromises()
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find(button => button.textContent?.includes('人工头程报价'))!.click()
|
||||
await flushPromises()
|
||||
const save = [...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find(button => button.textContent?.includes('保存人工报价'))!
|
||||
expect(save.disabled).toBe(true)
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find(button => button.textContent?.includes('刷新计费量'))!.click()
|
||||
await flushPromises()
|
||||
expect(previewInboundFee).toHaveBeenCalledTimes(2)
|
||||
expect(document.body.textContent).toContain('64.128 KG')
|
||||
expect(createManualInboundQuote).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps confirmed private-address fees locked for repricing', async () => {
|
||||
vi.mocked(getLatestInboundFee).mockResolvedValue({ ...snapshot, customerConfirmationStatus: 'CONFIRMED' })
|
||||
mountPanel(['firstleg:fba-pricing:view', 'firstleg:inbound:confirm-send-fee'], { destinationType: 'PRIVATE' })
|
||||
await flushPromises()
|
||||
expect([...document.querySelectorAll('button')].some(button => button.textContent?.includes('人工头程报价'))).toBe(false)
|
||||
})
|
||||
|
||||
it('shows the immutable calculation basis, versions, amount, and timestamps', async () => {
|
||||
|
||||
Reference in New Issue
Block a user