优化
This commit is contained in:
@@ -34,17 +34,19 @@ describe('inbound fee-detail API', () => {
|
||||
expect(http.delete).toHaveBeenCalledWith('/platform/v1/inbounds/88/fee-details/11', { params: { accountId: '100' } })
|
||||
})
|
||||
|
||||
it('fails closed when a fee item has no recognized supplier category', async () => {
|
||||
it('keeps an empty supplier category as null and rejects an unknown non-empty category', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', data: { records: [
|
||||
{ id: 31, feeTypeName: '拖车费', feeCode: 'TRUCKING', supplierCategory: 'TRUCKING', warehousePayable: true },
|
||||
{ id: 32, feeTypeName: '未知费用', feeCode: 'UNMAPPED', supplierCategory: 'UNKNOWN', warehousePayable: true },
|
||||
{ id: 33, feeTypeName: '缺少类别', feeCode: 'MISSING', warehousePayable: true },
|
||||
] } })
|
||||
await expect(listInboundFeeItemOptions()).resolves.toMatchObject([
|
||||
{ id: 31, feeCode: 'TRUCKING', supplierCategory: 'TRUCKING' },
|
||||
{ id: 32, feeCode: 'UNMAPPED', supplierCategory: null },
|
||||
{ id: 33, feeCode: 'MISSING', supplierCategory: null },
|
||||
])
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', data: { records: [
|
||||
{ id: 32, feeTypeName: '未知费用', feeCode: 'UNMAPPED', supplierCategory: 'UNKNOWN', warehousePayable: true },
|
||||
] } })
|
||||
await expect(listInboundFeeItemOptions()).rejects.toThrow('Unsupported supplierCategory: UNKNOWN')
|
||||
})
|
||||
|
||||
it('requests and retains only ACTIVE suppliers in the required category', async () => {
|
||||
@@ -60,4 +62,19 @@ describe('inbound fee-detail API', () => {
|
||||
page: 1, size: 200, category: 'TRUCKING', region: undefined, status: 'ACTIVE', keyword: undefined,
|
||||
} })
|
||||
})
|
||||
|
||||
it('requests all categories and retains every ACTIVE supplier when the category is empty', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', data: { records: [
|
||||
{ id: 41, tenantId: 0, name: '承运商A', category: 'TRUCKING', status: 'ACTIVE', version: 1, deleted: false },
|
||||
{ id: 42, tenantId: 0, name: '停用承运商', category: 'TRUCKING', status: 'INACTIVE', version: 1, deleted: false },
|
||||
{ id: 43, tenantId: 0, name: '报关行', category: 'CUSTOMS', status: 'ACTIVE', version: 1, deleted: false },
|
||||
], page: 1, size: 200, total: 3, pages: 1 } })
|
||||
await expect(listInboundFeeSupplierOptions(null)).resolves.toEqual([
|
||||
{ id: '41', supplierName: '承运商A', category: 'TRUCKING' },
|
||||
{ id: '43', supplierName: '报关行', category: 'CUSTOMS' },
|
||||
])
|
||||
expect(http.get).toHaveBeenCalledWith('/platform/v1/suppliers', { params: {
|
||||
page: 1, size: 200, category: undefined, region: undefined, status: 'ACTIVE', keyword: undefined,
|
||||
} })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,7 +54,9 @@ export interface InboundFeeSupplierOption { id: string; supplierName: string; ca
|
||||
const supplierCategories: SupplierCategory[] = ['BOOKING', 'CUSTOMS', 'TRUCKING', 'CERTIFICATE']
|
||||
|
||||
function supplierCategory(value: unknown): SupplierCategory | null {
|
||||
return supplierCategories.includes(value as SupplierCategory) ? value as SupplierCategory : null
|
||||
if (value == null || value === '') return null
|
||||
if (supplierCategories.includes(value as SupplierCategory)) return value as SupplierCategory
|
||||
throw new TypeError(`Unsupported supplierCategory: ${String(value)}`)
|
||||
}
|
||||
|
||||
function basePath(inboundId: string) {
|
||||
@@ -115,9 +117,9 @@ export async function listInboundFeeItemOptions() {
|
||||
}))
|
||||
}
|
||||
|
||||
export async function listInboundFeeSupplierOptions(category: SupplierCategory) {
|
||||
const page = await listSuppliers({ status: 'ACTIVE', category, page: 1, size: 200 })
|
||||
return page.records.filter((supplier) => supplier.status === 'ACTIVE' && supplier.category === category).map((supplier): InboundFeeSupplierOption => ({
|
||||
export async function listInboundFeeSupplierOptions(category: SupplierCategory | null) {
|
||||
const page = await listSuppliers({ status: 'ACTIVE', category: category ?? undefined, page: 1, size: 200 })
|
||||
return page.records.filter((supplier) => supplier.status === 'ACTIVE' && (!category || supplier.category === category)).map((supplier): InboundFeeSupplierOption => ({
|
||||
id: supplier.id, supplierName: supplier.name, category: supplier.category,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -181,8 +181,9 @@ describe('InboundFeeDetailManager', () => {
|
||||
expect(document.querySelector('.fee-form .el-select')?.classList.contains('is-disabled')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails closed when a payable fee item has no supplier category', async () => {
|
||||
it('allows any loaded ACTIVE supplier when a payable fee item has no supplier category', async () => {
|
||||
vi.mocked(listInboundFeeItemOptions).mockResolvedValue([{ id: '31', feeTypeName: '未映射费用', feeCode: 'UNMAPPED', supplierCategory: null, warehouseReceivable: false, warehousePayable: true }])
|
||||
vi.mocked(listInboundFeeSupplierOptions).mockResolvedValue([{ id: '41', supplierName: '测试供应商', category: 'CUSTOMS' }])
|
||||
const wrapper = mountManager([
|
||||
'firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:edit',
|
||||
'firstleg:fee-item:view', 'platform:supplier:list',
|
||||
@@ -191,10 +192,10 @@ describe('InboundFeeDetailManager', () => {
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('新增费用'))?.trigger('click')
|
||||
await flushPromises()
|
||||
const state = (wrapper.vm.$ as unknown as { setupState: { save(): Promise<void>; form: { direction: string; feeItemId: string; supplierId: string | null; amount: number } } }).setupState
|
||||
state.form.direction = 'PAYABLE'; state.form.feeItemId = '31'; state.form.supplierId = '41'; state.form.amount = 100; await flushPromises()
|
||||
state.form.direction = 'PAYABLE'; state.form.feeItemId = '31'; await flushPromises(); state.form.supplierId = '41'; state.form.amount = 100
|
||||
await state.save(); await flushPromises()
|
||||
expect(createInboundFeeDetail).not.toHaveBeenCalled()
|
||||
expect(document.body.textContent).toContain('该应付费用项目未配置供应商类别,已阻断保存')
|
||||
expect(listInboundFeeSupplierOptions).toHaveBeenCalledWith(null)
|
||||
expect(createInboundFeeDetail).toHaveBeenCalledWith('100', '88', expect.objectContaining({ direction: 'PAYABLE', supplierId: '41' }))
|
||||
})
|
||||
|
||||
it('refreshes fee rows and the order detail after every successful write', () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ const feeItems = ref<InboundFeeItemOption[]>([])
|
||||
const suppliers = ref<InboundFeeSupplierOption[]>([])
|
||||
const feeItemsLoading = ref(false)
|
||||
const suppliersLoading = ref(false)
|
||||
const loadedSupplierCategory = ref<string | null>(null)
|
||||
const loadedSupplierCategory = ref<string | null | undefined>(undefined)
|
||||
const feeItemsError = ref<ApiErrorDetails | null>(null)
|
||||
const suppliersError = ref<ApiErrorDetails | null>(null)
|
||||
const loading = ref(false)
|
||||
@@ -108,8 +108,8 @@ async function loadFeeItems() {
|
||||
async function loadSuppliers() {
|
||||
const sequence = ++supplierLoadSequence
|
||||
const item = selectedFeeItem.value
|
||||
suppliers.value = []; loadedSupplierCategory.value = null; suppliersError.value = null
|
||||
if (!canViewSuppliers.value || form.direction !== 'PAYABLE' || !item?.supplierCategory) return
|
||||
suppliers.value = []; loadedSupplierCategory.value = undefined; suppliersError.value = null
|
||||
if (!canViewSuppliers.value || form.direction !== 'PAYABLE' || !item) return
|
||||
const requestedCategory = item.supplierCategory
|
||||
suppliersLoading.value = true
|
||||
try {
|
||||
@@ -153,12 +153,11 @@ async function save() {
|
||||
if (!canViewFeeItems.value) { actionError.value = { message: '当前账号缺少费用项目查看权限,无法保存费用' }; return }
|
||||
const selectedItem = feeItems.value.find((item) => String(item.id) === String(form.feeItemId))
|
||||
if (form.direction === 'PAYABLE' && !canViewSuppliers.value) { actionError.value = { message: '当前账号缺少供应商查看权限,无法维护应付费用' }; return }
|
||||
if (form.direction === 'PAYABLE' && !selectedItem?.supplierCategory) { actionError.value = { message: '该应付费用项目未配置供应商类别,已阻断保存' }; return }
|
||||
if (form.direction === 'PAYABLE' && suppliersError.value) { actionError.value = { message: '供应商列表加载失败,无法校验应付费用供应商' }; return }
|
||||
if (form.direction === 'PAYABLE' && loadedSupplierCategory.value !== selectedItem?.supplierCategory) { actionError.value = { message: '供应商候选尚未按费用项目类别加载完成,无法保存' }; return }
|
||||
if (form.direction === 'PAYABLE' && !form.supplierId) { actionError.value = { message: '应付费用必须选择供应商' }; return }
|
||||
const selectedSupplier = suppliers.value.find((supplier) => supplier.id === String(form.supplierId))
|
||||
if (form.direction === 'PAYABLE' && (!selectedSupplier || selectedSupplier.category !== selectedItem?.supplierCategory)) { actionError.value = { message: '所选供应商与费用项目要求的供应商类别不匹配' }; return }
|
||||
if (form.direction === 'PAYABLE' && (!selectedSupplier || (selectedItem?.supplierCategory && selectedSupplier.category !== selectedItem.supplierCategory))) { actionError.value = { message: '所选供应商与费用项目要求的供应商类别不匹配' }; return }
|
||||
saving.value = true; actionError.value = null
|
||||
try {
|
||||
const payload = normalizePayload()
|
||||
@@ -201,10 +200,10 @@ async function voidRow(row: InboundFeeDetail) {
|
||||
|
||||
watch(() => [props.accountId, normalizedInboundId.value], () => void load())
|
||||
watch(() => form.direction, (direction) => {
|
||||
if (direction === 'RECEIVABLE') { supplierLoadSequence += 1; form.supplierId = null; suppliers.value = []; loadedSupplierCategory.value = null; suppliersError.value = null; suppliersLoading.value = false }
|
||||
if (direction === 'RECEIVABLE') { supplierLoadSequence += 1; form.supplierId = null; suppliers.value = []; loadedSupplierCategory.value = undefined; suppliersError.value = null; suppliersLoading.value = false }
|
||||
else void loadSuppliers()
|
||||
})
|
||||
watch(() => form.feeItemId, () => { form.supplierId = null; suppliers.value = []; loadedSupplierCategory.value = null; suppliersError.value = null; if (form.direction === 'PAYABLE') void loadSuppliers() })
|
||||
watch(() => form.feeItemId, () => { form.supplierId = null; suppliers.value = []; loadedSupplierCategory.value = undefined; suppliersError.value = null; if (form.direction === 'PAYABLE') void loadSuppliers() })
|
||||
onMounted(() => void load())
|
||||
</script>
|
||||
|
||||
@@ -265,7 +264,7 @@ onMounted(() => void load())
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="fee-form">
|
||||
<el-form-item label="应收/应付" prop="direction"><el-segmented v-model="form.direction" :options="directionOptions" /></el-form-item>
|
||||
<el-form-item label="费用项目" prop="feeItemId"><el-select v-model="form.feeItemId" filterable :loading="feeItemsLoading" :disabled="!canViewFeeItems"><el-option v-for="item in applicableItems" :key="item.id" :label="item.feeTypeName" :value="item.id" /></el-select></el-form-item>
|
||||
<el-form-item v-if="form.direction === 'PAYABLE'" label="供应商" required><el-select v-model="form.supplierId" filterable :loading="suppliersLoading" :disabled="!canViewSuppliers || !selectedFeeItem?.supplierCategory || !!suppliersError" placeholder="请先选择已配置供应商类别的费用项目"><el-option v-for="supplier in suppliers" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" /></el-select></el-form-item>
|
||||
<el-form-item v-if="form.direction === 'PAYABLE'" label="供应商" required><el-select v-model="form.supplierId" filterable :loading="suppliersLoading" :disabled="!canViewSuppliers || !selectedFeeItem || !!suppliersError" placeholder="请先选择费用项目"><el-option v-for="supplier in suppliers" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" /></el-select></el-form-item>
|
||||
<el-form-item label="业务发生日" prop="businessDate"><el-date-picker v-model="form.businessDate" type="date" value-format="YYYY-MM-DD" /></el-form-item>
|
||||
<el-form-item label="货币" prop="currency"><el-input model-value="CNY" readonly aria-label="货币 CNY" /></el-form-item>
|
||||
<el-form-item label="单价" prop="unitPrice"><el-input-number v-model="form.unitPrice" :min="0" :precision="4" /></el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user