64 lines
4.8 KiB
Vue
64 lines
4.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
import BusinessOrderFees from './BusinessOrderFees.vue'
|
|
import { listBusinessFeeOptions, listBusinessOverseasFees, type BusinessFeeOption, type OverseasFeeDirection } from '../api/overseasFees'
|
|
import { feePermissions } from '../overseasFeePresentation'
|
|
|
|
interface DocumentIdentity { businessType: string; businessNo: string; customerId?: string; businessId?: string }
|
|
class DocumentIdentityError extends Error {}
|
|
const props = withDefaults(defineProps<DocumentIdentity & { relatedDocuments?: DocumentIdentity[]; canCreate?: boolean; readonly?: boolean; direction?: OverseasFeeDirection; hideWhenEmpty?: boolean; heading?: string }>(), { canCreate: true, readonly: false, direction: 'RECEIVABLE', hideWhenEmpty: false })
|
|
const emit = defineEmits<{ changed: [] }>()
|
|
const access = useAccessPreviewStore()
|
|
const canRead = computed(() => access.can(feePermissions.list) || access.can('platform:finance:customer-arap:list'))
|
|
const business = ref<BusinessFeeOption | null>(null), related = ref<BusinessFeeOption[]>([])
|
|
const loading = ref(false), error = ref<ApiErrorDetails | null>(null)
|
|
const panel = ref<InstanceType<typeof BusinessOrderFees>>()
|
|
let generation = 0
|
|
async function load() {
|
|
const request = ++generation
|
|
business.value = null; related.value = []; error.value = null; loading.value = false
|
|
if (!canRead.value) return
|
|
loading.value = true
|
|
try {
|
|
const identities: DocumentIdentity[] = [props, ...(props.relatedDocuments || [])]
|
|
const resolved = await Promise.all(identities.map(async (identity, index) => {
|
|
if ((!identity.customerId && !identity.businessId) || !identity.businessNo) throw new DocumentIdentityError('单据信息不完整,无法读取费用')
|
|
const options = await listBusinessFeeOptions({ businessType: identity.businessType, customerId: identity.customerId, keyword: identity.businessNo })
|
|
// 候选搜索是模糊查询,只有当前客户和完整单号一致时才能承载收费。
|
|
const matches = options.filter(option => (!identity.customerId || option.customerId === identity.customerId) && option.businessType === identity.businessType
|
|
&& option.businessNo === identity.businessNo && (!identity.businessId || option.businessId === identity.businessId))
|
|
// 尾程来源号可能只是外部参考号;没有内部业务实体时不能据此造出费用归属。
|
|
if (index > 0 && matches.length === 0) return null
|
|
if (matches.length !== 1) throw new DocumentIdentityError(`未找到唯一关联单据:${identity.businessNo}`)
|
|
return matches[0]!
|
|
}))
|
|
if (request === generation) { business.value = resolved[0]!; related.value = resolved.slice(1).filter((value): value is BusinessFeeOption => value !== null) }
|
|
} catch (failure) { if (request === generation) error.value = failure instanceof DocumentIdentityError ? { message: failure.message } : getApiErrorDetails(failure, '单据费用加载失败') }
|
|
finally { if (request === generation) loading.value = false }
|
|
}
|
|
watch(() => [props.businessType, props.businessNo, props.customerId, props.businessId, props.relatedDocuments, canRead.value], load, { immediate: true, deep: true })
|
|
async function refresh() { if (business.value) await panel.value?.refresh(); else await load() }
|
|
/** 新增前读取完整单据范围,重复项目提示不受当前表格分页影响。 */
|
|
async function getFees() {
|
|
if (!business.value) await load()
|
|
if (!business.value || error.value) throw new Error(error.value?.message || '单据费用尚未加载完成')
|
|
return (await listBusinessOverseasFees([business.value, ...related.value], { page: 1, size: Number.MAX_SAFE_INTEGER }, props.direction)).records
|
|
}
|
|
defineExpose({ refresh, getFees })
|
|
</script>
|
|
|
|
<template>
|
|
<section aria-label="单据费用">
|
|
<el-alert v-if="!canRead" title="暂无费用查看权限" type="info" :closable="false" />
|
|
<LoadErrorState v-else-if="error" title="单据费用加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
|
<el-skeleton v-else-if="loading" :rows="3" animated />
|
|
<BusinessOrderFees v-else-if="business" ref="panel" :business="business" :related-businesses="related" :direction="direction" :can-create="canCreate" :readonly="readonly" :hide-when-empty="hideWhenEmpty" :heading="heading" @changed="emit('changed')">
|
|
<template v-if="$slots['toolbar-actions']" #toolbar-actions><slot name="toolbar-actions" /></template>
|
|
</BusinessOrderFees>
|
|
</section>
|
|
</template>
|