This commit is contained in:
2026-08-18 16:09:43 +08:00
parent f9ae2d94cb
commit 21249d1dde
52 changed files with 980 additions and 698 deletions
@@ -161,6 +161,16 @@ describe('InboundFeePanel', () => {
expect(document.body.textContent).toContain('没有查看入库费用的权限')
})
it.each(['', ' ', 'null', 'undefined'])('does not request a fee for invalid inboundId %j', async (inboundId) => {
const pinia = setupPermissions(['firstleg:fba-pricing:view'])
mount(InboundFeePanel, {
props: { customerId: '10001', inboundId },
global: { plugins: [pinia, ElementPlus] },
})
await flushPromises()
expect(getLatestInboundFee).not.toHaveBeenCalled()
})
it('treats A0404 as a neutral not-yet-generated state', async () => {
vi.mocked(getLatestInboundFee).mockRejectedValue(new ApiResponseError('费用快照不存在', 'A0404'))
mountPanel(['firstleg:fba-pricing:view'])
+9 -5
View File
@@ -66,6 +66,10 @@ const adjustmentRules: FormRules = {
const canView = computed(() => access.can('firstleg:fba-pricing:view'))
const canAdjust = computed(() => access.can('firstleg:fba-pricing:adjust'))
const customerContextId = computed(() => props.customerId || props.tenantId || '')
const normalizedInboundId = computed(() => {
const identifier = String(props.inboundId ?? '').trim()
return identifier && identifier !== 'null' && identifier !== 'undefined' ? identifier : ''
})
const adjustmentEligible = computed(() => (
snapshot.value?.feeStatus === 'CALCULATED'
&& snapshot.value?.nextProcessStatus === 'WAIT_SPLIT'
@@ -126,14 +130,14 @@ async function loadFee(options: { preserveAdjustment?: boolean } = {}) {
const sequence = ++loadSequence
loadError.value = null
notFound.value = false
if (!customerContextId.value || !props.inboundId || !canView.value) {
if (!customerContextId.value || !normalizedInboundId.value || !canView.value) {
snapshot.value = null
loading.value = false
return
}
loading.value = true
try {
const result = await getLatestInboundFee(customerContextId.value, props.inboundId)
const result = await getLatestInboundFee(customerContextId.value, normalizedInboundId.value)
if (sequence !== loadSequence) return
snapshot.value = result
emit('loaded', result)
@@ -197,7 +201,7 @@ async function submitAdjustment() {
try {
const result = await adjustPublishedInboundFee(
customerContextId.value,
props.inboundId,
normalizedInboundId.value,
payload,
getAdjustmentKey(payload),
)
@@ -219,7 +223,7 @@ async function submitAdjustment() {
}
watch(
() => [customerContextId.value, props.inboundId, access.permissionSignature],
() => [customerContextId.value, normalizedInboundId.value, access.permissionSignature],
() => {
adjustOpen.value = false
snapshot.value = null
@@ -247,7 +251,7 @@ defineExpose({ reload: loadFee })
circle
aria-label="刷新费用快照"
:loading="loading"
:disabled="!customerContextId || !inboundId || !canView"
:disabled="!customerContextId || !normalizedInboundId || !canView"
@click="loadFee()"
><RefreshCw :size="16" /></el-button>
</el-tooltip>
+5
View File
@@ -23,6 +23,11 @@ const snapshot = {
describe('inbound fee API', () => {
beforeEach(() => vi.clearAllMocks())
it.each(['', ' ', 'null', 'undefined'])('rejects invalid inboundId %j without issuing HTTP', async (inboundId) => {
await expect(getLatestInboundFee('10001', inboundId)).rejects.toThrow('inboundId is required')
expect(http.get).not.toHaveBeenCalled()
})
it('loads the latest immutable fee snapshot with string IDs', async () => {
vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: snapshot })
+3 -1
View File
@@ -2,7 +2,9 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { AdjustPublishedInboundFeeRequest, InboundFeeSnapshot } from './types'
function feeBasePath(inboundId: string) {
return `/platform/v1/inbounds/${encodeURIComponent(inboundId)}/fees`
const identifier = String(inboundId ?? '').trim()
if (!identifier || identifier === 'null' || identifier === 'undefined') throw new TypeError('inboundId is required')
return `/platform/v1/inbounds/${encodeURIComponent(identifier)}/fees`
}
export async function getLatestInboundFee(tenantId: string, inboundId: string) {