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
@@ -87,6 +87,18 @@ describe('InboundFeeDetailManager', () => {
expect(document.body.textContent).toContain('没有查看费用明细的权限')
})
it.each(['', ' ', 'null', 'undefined'])('does not request fee rows for invalid inboundId %j', async (inboundId) => {
const pinia = createPinia(); setActivePinia(pinia)
const auth = useAuthStore(); auth.token = 'token'; auth.permissions = ['firstleg:inbound:fee-detail:view']
useAccessPreviewStore().initializeAfterLogin()
mount(InboundFeeDetailManager, {
props: { accountId: '100', inboundId },
global: { plugins: [pinia, ElementPlus] },
})
await flushPromises()
expect(listInboundFeeDetails).not.toHaveBeenCalled()
})
it('approves a pending fee then refreshes rows and emits changed', async () => {
const wrapper = mountManager(['firstleg:inbound:fee-detail:view', 'firstleg:inbound:fee-detail:review'])
await flushPromises()
@@ -55,6 +55,10 @@ const rules: FormRules = {
currency: [{ required: true, pattern: /^[A-Z]{3}$/, message: '货币须为三个大写字母', trigger: 'blur' }],
}
const canView = computed(() => access.can('firstleg:inbound:fee-detail:view'))
const normalizedInboundId = computed(() => {
const identifier = String(props.inboundId ?? '').trim()
return identifier && identifier !== 'null' && identifier !== 'undefined' ? identifier : ''
})
const canViewFeeItems = computed(() => access.can('firstleg:fee-item:view'))
const canViewPartners = computed(() => access.can('platform:counterparty:list'))
const directionOptions = computed(() => [
@@ -73,10 +77,13 @@ function reviewLabel(value: string) { return ({ PENDING: '待审核', APPROVED:
function directionLabel(value: string) { return value === 'RECEIVABLE' ? '应收' : '应付' }
async function load() {
if (!canView.value) return
if (!canView.value || !normalizedInboundId.value) {
rows.value = []
return
}
loading.value = true; loadError.value = null
try {
rows.value = await listInboundFeeDetails(props.accountId, props.inboundId)
rows.value = await listInboundFeeDetails(props.accountId, normalizedInboundId.value)
const approved = rows.value.filter((row) => isManual(row) && row.reviewStatus === 'APPROVED' && row.publicationStatus !== 'VOIDED' && row.settlementIncluded)
emit('summary', {
currency: approved.find((row) => row.currency)?.currency || 'CNY',
@@ -142,9 +149,9 @@ async function save() {
saving.value = true; actionError.value = null
try {
const payload = normalizePayload()
if (editorMode.value === 'create') await createInboundFeeDetail(props.accountId, props.inboundId, payload)
else if (editorMode.value === 'edit' && target.value) await updateInboundFeeDetail(props.accountId, props.inboundId, String(target.value.id), payload)
else if (target.value) await adjustInboundFeeDetail(props.accountId, props.inboundId, String(target.value.id), target.value.sourceFeeVersion, payload)
if (editorMode.value === 'create') await createInboundFeeDetail(props.accountId, normalizedInboundId.value, payload)
else if (editorMode.value === 'edit' && target.value) await updateInboundFeeDetail(props.accountId, normalizedInboundId.value, String(target.value.id), payload)
else if (target.value) await adjustInboundFeeDetail(props.accountId, normalizedInboundId.value, String(target.value.id), target.value.sourceFeeVersion, payload)
editorOpen.value = false
await finishAction(editorMode.value === 'adjust' ? '费用调整已提交审核' : '费用明细已保存')
} catch (error) { actionError.value = getApiErrorDetails(error, '费用明细保存失败') }
@@ -154,12 +161,12 @@ async function save() {
async function remove(row: InboundFeeDetail) {
try {
await ElMessageBox.confirm('删除后无法恢复,是否继续?', '删除费用明细', { type: 'warning', confirmButtonText: '删除' })
await deleteInboundFeeDetail(props.accountId, props.inboundId, String(row.id)); await finishAction('费用明细已删除')
await deleteInboundFeeDetail(props.accountId, normalizedInboundId.value, String(row.id)); await finishAction('费用明细已删除')
} catch (error) { if (error !== 'cancel' && error !== 'close') actionError.value = getApiErrorDetails(error, '费用明细删除失败') }
}
async function approve(row: InboundFeeDetail) {
try { await reviewInboundFeeDetail(props.accountId, props.inboundId, String(row.id), 'approve'); await finishAction('费用审核通过') }
try { await reviewInboundFeeDetail(props.accountId, normalizedInboundId.value, String(row.id), 'approve'); await finishAction('费用审核通过') }
catch (error) { actionError.value = getApiErrorDetails(error, '费用审核失败') }
}
@@ -167,7 +174,7 @@ function openReject(row: InboundFeeDetail) { target.value = row; rejectReason.va
async function reject() {
if (!target.value || !rejectReason.value.trim()) { actionError.value = { message: '请填写驳回原因' }; return }
saving.value = true
try { await reviewInboundFeeDetail(props.accountId, props.inboundId, String(target.value.id), 'reject', rejectReason.value.trim()); rejectOpen.value = false; await finishAction('费用已驳回') }
try { await reviewInboundFeeDetail(props.accountId, normalizedInboundId.value, String(target.value.id), 'reject', rejectReason.value.trim()); rejectOpen.value = false; await finishAction('费用已驳回') }
catch (error) { actionError.value = getApiErrorDetails(error, '费用驳回失败') }
finally { saving.value = false }
}
@@ -175,11 +182,11 @@ async function reject() {
async function voidRow(row: InboundFeeDetail) {
try {
await ElMessageBox.confirm('作废会同步撤销尚未结算的财务费用来源,是否继续?', '作废费用明细', { type: 'warning', confirmButtonText: '作废' })
await voidInboundFeeDetail(props.accountId, props.inboundId, String(row.id), row.sourceFeeVersion); await finishAction('费用明细已作废')
await voidInboundFeeDetail(props.accountId, normalizedInboundId.value, String(row.id), row.sourceFeeVersion); await finishAction('费用明细已作废')
} catch (error) { if (error !== 'cancel' && error !== 'close') actionError.value = getApiErrorDetails(error, '费用作废失败') }
}
watch(() => [props.accountId, props.inboundId], () => void load())
watch(() => [props.accountId, normalizedInboundId.value], () => void load())
watch(() => form.direction, (direction) => {
if (direction === 'RECEIVABLE') { form.partnerId = null; form.partnerName = null; partnersError.value = null }
else void loadPartners()