Files
admin-web/src/modules/overseas-business/components/ProcurementFinanceFields.vue
T

75 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, watch } from 'vue'
import type { ProcurementCommand } from '../api/procurementCommands'
import type { PlatformProcurement, ProcurementFile } from '../procurementTypes'
import { chargeDraft, feeRetryPermission, refundAvailable, targetPreview, replacementCandidates, replacementPreview, type FinanceForm } from '../procurementFinanceForm'
import { money, decimal, feePurposes } from '../procurementPresentation'
import { commandKey } from '../procurementPending'
import ProcurementChargeFields from './ProcurementChargeFields.vue'
const props = defineProps<{ command: ProcurementCommand; detail: PlatformProcurement; files: ProcurementFile[]; can: (permission: string) => boolean }>()
const form = defineModel<FinanceForm>({ required: true })
const candidates = computed(() => props.detail.fees.filter(fee => {
if (props.command === 'fees/replace-failed') return replacementCandidates(props.detail).some(row => row.id === fee.id)
if (props.command === 'fees/retry') return fee.active && fee.walletStatus.endsWith('_FAILED') && props.can(feeRetryPermission(fee))
if (props.command === 'fees/terminate') return fee.active && fee.walletStatus.endsWith('_FAILED')
if (props.command === 'fees/release-estimate') return fee.active && fee.purpose === 'INTERNATIONAL_ESTIMATE' && ['FROZEN', 'ZERO'].includes(fee.walletStatus)
if (props.command === 'fees/correct-target') return fee.active && !['INTERNATIONAL_ESTIMATE', 'ADJUSTMENT', 'REFUND'].includes(fee.purpose)
return fee.walletStatus === 'SETTLED' && !!fee.walletLedgerId && fee.purpose !== 'INTERNATIONAL_ESTIMATE'
}))
watch(candidates, rows => { if (form.value.feeId && !rows.some(row => row.id === form.value.feeId)) form.value.feeId = '' })
const selected = computed(() => props.detail.fees.find(row => row.id === form.value.feeId))
const preview = computed(() => {
if (!selected.value) return null
try {
if (props.command === 'fees/replace-failed' && form.value.targetOriginalAmount) return { ...replacementPreview(props.detail, selected.value, form.value.targetOriginalAmount), error: '' }
if (props.command === 'fees/correct-target' && form.value.targetOriginalAmount) return { ...targetPreview(props.detail, selected.value, form.value.targetOriginalAmount), error: '' }
if (props.command === 'fees/refund') return { refundable: decimal(refundAvailable(props.detail, selected.value)), error: '' }
} catch (error) { return { error: error instanceof Error ? error.message : '请核对原费用' } }
return null
})
function addOther() { form.value.otherDetermination = 'ITEMIZED'; const row = chargeDraft('OTHER', undefined, `OTHER_${commandKey().slice(0, 20).toUpperCase()}`); row.name = ''; form.value.others.push(row) }
</script>
<template>
<div class="admin-density">
<template v-if="command === 'charges/confirm'">
<el-alert v-if="detail.operations.some(row => row.action === 'CONFIRM_CHARGES')" title="本单已完成过核费,本次只引用已有费用版本;新增费用或重新核定请使用财务操作。" type="info" :closable="false" />
<el-alert title="一次确认实际国际运费、关税、国内运费和其他费用。正额确认后发起资金处理;确认成功不代表已扣款或可交寄。" type="info" :closable="false" />
<section v-for="(row, index) in form.fixed" :key="row.purpose" class="charge-section"><h3>{{ row.name }}</h3><ProcurementChargeFields v-model="form.fixed[index]" :detail="detail" :files="files" /></section>
<section class="charge-section"><h3>其他费用</h3><el-form-item label="其他费用适用情况" required><el-radio-group v-model="form.otherDetermination"><el-radio value="NOT_APPLICABLE" :disabled="detail.fees.some(row => row.active && row.purpose === 'OTHER')">不适用</el-radio><el-radio value="ITEMIZED">逐项核定</el-radio></el-radio-group></el-form-item>
<template v-if="form.otherDetermination === 'ITEMIZED'"><section v-for="(row, index) in form.others" :key="row.code" class="other-charge"><div class="charge-heading"><el-form-item label="费用名称" required><el-input v-model="row.name" :readonly="row.mode === 'EXISTING'" maxlength="128" placeholder="填写独立发生的费用名称" /></el-form-item><el-button v-if="row.mode === 'NEW'" class="density-action" data-tone="danger" @click="form.others.splice(index, 1)">移除新费用</el-button></div><ProcurementChargeFields v-model="form.others[index]" :detail="detail" :files="files" /></section><el-button v-if="!detail.operations.some(row => row.action === 'CONFIRM_CHARGES')" class="density-action" @click="addOther">增加其他费用</el-button></template>
</section>
</template>
<template v-else-if="command === 'fees/additional'">
<el-alert title="登记独立新发生的正额费用,使用该业务日期的汇率。与原费用金额更正分开处理;已完成订单保留原履约状态。" type="info" :closable="false" />
<el-form-item label="独立费用名称" required><el-input v-model="form.additional.name" maxlength="128" placeholder="填写独立发生的费用名称" /></el-form-item>
<el-form-item label="新增费用原币金额" required><el-input v-model="form.additional.amount" placeholder="填写正额金额,最多两位小数" /></el-form-item>
<el-form-item label="新增费用币种" required><el-select v-model="form.additional.currency"><el-option value="CNY" label="人民币 CNY" /><el-option value="USD" label="美元 USD" /></el-select></el-form-item>
<el-form-item label="新增费用业务日期" required><el-date-picker v-model="form.additional.businessDate" type="date" value-format="YYYY-MM-DD" /></el-form-item>
<el-form-item label="新增费用凭证" required><el-select v-model="form.additional.voucherFileId" placeholder="选择本单已上传的费用凭证"><el-option v-for="file in files.filter(row => row.category === 'VOUCHER')" :key="file.fileId" :value="file.fileId" :label="file.fileName" /></el-select></el-form-item>
</template>
<template v-else>
<el-alert v-if="command === 'fees/replace-failed'" title="原费用须已明确失败并完成终止及冻结释放。本次按核定总额创建新的实际费用版本,沿用原汇率和业务日期;货款仍受客户预算限制。" type="warning" :closable="false" />
<el-alert v-if="command === 'fees/correct-target'" title="输入这条原费用链正确的原币总额。沿用原汇率,按目标与净实扣差额生成追加或逐笔退款;历史扣款不覆盖。" type="info" :closable="false" />
<el-alert v-if="command === 'fees/refund'" title="退款回到客户资金账户,关联选中的原成功扣款;不以普通充值替代,不自动退款其他费用。" type="warning" :closable="false" />
<el-alert v-if="command === 'fees/terminate'" title="仅终止明确失败的费用版本;仍有有效冻结时先释放,释放成功后才可重新核定,不能将未决结果当作失败。" type="warning" :closable="false" />
<el-alert v-if="command === 'fees/release-estimate'" title="释放有效预估冻结不产生退款或实际收费,后续交寄须重新满足预估核对条件。" type="warning" :closable="false" />
<el-alert v-if="command === 'fees/retry'" title="仅重试已明确失败的原资金请求,金额与费用版本不改变;处理中请刷新结果。" type="info" :closable="false" />
<el-form-item label="原费用" required><el-select v-model="form.feeId" placeholder="选择本单符合操作条件的费用"><el-option v-for="fee in candidates" :key="fee.id" :value="fee.id" :label="`${fee.feeNo} · ${feePurposes[fee.purpose] || fee.feeItemName} · ${money(fee.amount, fee.currency)}`" /></el-select></el-form-item>
<dl v-if="selected" class="density-fields"><div><dt>原币金额</dt><dd>{{ money(selected.originalAmount, selected.originalCurrency) }}</dd></div><div><dt>原汇率及月份</dt><dd>{{ selected.exchangeRate }} / {{ selected.exchangeRateMonth }}</dd></div></dl>
<el-form-item v-if="['fees/correct-target', 'fees/replace-failed'].includes(command)" :label="`正确原币总额(${selected?.originalCurrency || '先选择费用'})`" required><el-input v-model="form.targetOriginalAmount" placeholder="填写原费用链正确总额,最多两位小数" /></el-form-item>
<el-form-item v-if="command === 'fees/refund'" label="本次退款金额(CNY)" required><el-input v-model="form.refundAmount" placeholder="填写本次正额退款金额" /></el-form-item>
<el-alert v-if="preview?.error" :title="preview.error" type="warning" :closable="false" />
<dl v-else-if="preview" class="density-fields"><template v-if="'target' in preview"><div><dt>目标人民币金额</dt><dd>{{ money(preview.target, 'CNY') }}</dd></div><div><dt>当前净实扣</dt><dd>{{ money(preview.net, 'CNY') }}</dd></div><div><dt>本次差额</dt><dd>{{ money(preview.difference, 'CNY') }}</dd></div></template><div v-if="'refundable' in preview"><dt>该笔剩余可退</dt><dd>{{ money(preview.refundable, 'CNY') }}</dd></div></dl>
</template>
</div>
</template>
<style scoped>
.charge-section { padding-block: var(--admin-space-4); border-bottom: 1px solid var(--cc-border); }
.charge-section h3 { font-size: var(--admin-font-section); margin: 0 0 var(--admin-space-3); }
.charge-heading { display: flex; align-items: center; gap: var(--admin-space-3); }
.charge-heading :deep(.el-form-item) { flex: 1; }
.other-charge { padding-bottom: var(--admin-space-3); }
:deep(.el-select) { width: 100%; }
</style>