新加额度池配置功能

This commit is contained in:
luojiaquan
2026-09-20 10:26:30 +08:00
parent f529067ae3
commit d40adf1318
28 changed files with 1669 additions and 414 deletions
@@ -86,12 +86,20 @@ export function listCreditApply(query) {
})
}
/** 审批通过额度调整申请 */
export function approveCreditApply(applyId, approveRemark) {
/** 审批通过额度调整申请(data: { approveRemark, approveRoleId },非超管净增须带 approveRoleId) */
export function approveCreditApply(applyId, data) {
return request({
url: '/customersupplier/customermoney/credit/apply/approve/' + applyId,
method: 'put',
data: { approveRemark }
data: data || {}
})
}
/** 当前登录用户审批时可选择的角色额度池(超管返回空数组) */
export function listMyCreditPools() {
return request({
url: '/customersupplier/customermoney/credit/quota/my-pools',
method: 'get'
})
}
+17
View File
@@ -117,3 +117,20 @@ export function deptTreeSelect(roleId) {
method: 'get'
})
}
// 查询全部角色额度池(总额度/已分配/剩余)
export function listRoleCreditQuota() {
return request({
url: '/system/role/creditQuota/list',
method: 'get'
})
}
// 设置角色可分配额度总额
export function setRoleCreditQuota(data) {
return request({
url: '/system/role/creditQuota',
method: 'put',
data: data
})
}
@@ -129,7 +129,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="信用额度" prop="creditEd">
<el-input v-model="form.creditEd" :disabled="!isAdd" placeholder="请输入信用额度">
<el-input v-model="form.creditEd" :disabled="!isAdd" placeholder="默认为0,授信需在客户余额中申请审批">
<template #append v-if="!isAdd">
<span style="color: #e6a23c">请在客户余额-额度管理中调整</span>
</template>
@@ -772,7 +772,7 @@ function buildEmptyForm() {
foreignCurrencyBalance: null,
weightThreshold: null,
throwRatio: null,
creditEd: 1000,
creditEd: 0,
productIds: null,
attachmentList: [],
companyList: [],
@@ -48,6 +48,7 @@
v-hasPermi="['customer:credit:adjust']">调整</el-button>
</div>
<div class="balance-value">{{ formatMoney(currentAccount.totalCredit) }}</div>
<div class="balance-sub" :style="creditPeriodExpired ? 'color: var(--el-color-danger);' : ''">{{ creditPeriodText }}</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
@@ -58,7 +59,7 @@
v-hasPermi="['customer:credit:adjust']">调整</el-button>
</div>
<div class="balance-value">{{ formatMoney(currentAccount.extraCredit) }}</div>
<div class="balance-sub">{{ tempCreditPeriodText }}</div>
<div class="balance-sub" :style="tempCreditPeriodExpired ? 'color: var(--el-color-danger);' : ''">{{ tempCreditPeriodText }}</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
@@ -423,6 +424,9 @@
<el-table-column label="审批人" align="center" prop="approveBy" width="100">
<template #default="scope">{{ scope.row.approveBy || '-' }}</template>
</el-table-column>
<el-table-column label="额度池角色" align="center" prop="approveRoleName" width="110">
<template #default="scope">{{ scope.row.approveRoleName || '超管' }}</template>
</el-table-column>
<el-table-column label="审批时间" align="center" prop="approveTime" width="150">
<template #default="scope">{{ scope.row.approveTime ? formatDateTime(scope.row.approveTime) : '-' }}</template>
</el-table-column>
@@ -768,7 +772,7 @@
<el-col :span="12">
<el-form-item label="额度类型" prop="creditType">
<el-radio-group v-model="creditDialog.form.creditType" @change="onCreditTypeChange">
<el-radio value="CREDIT">信用额度(长期)</el-radio>
<el-radio value="CREDIT">信用额度</el-radio>
<el-radio value="TEMP">临时额度</el-radio>
</el-radio-group>
</el-form-item>
@@ -794,32 +798,34 @@
/>
</el-form-item>
</el-col>
<template v-if="creditDialog.form.creditType === 'TEMP'">
<el-col :span="12">
<el-form-item label="生效日期" prop="effectiveDate">
<el-date-picker
v-model="creditDialog.form.effectiveDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="默认今天"
:disabled-date="afterToday"
style="width: 100%;"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="失效日期" prop="expireDate">
<el-date-picker
v-model="creditDialog.form.expireDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="不填为长期有效"
style="width: 100%;"
/>
<div class="rate-tip">失效日当天仍有效,次日访问账户时自动清零并记到期流水</div>
</el-form-item>
</el-col>
</template>
<el-col :span="12">
<el-form-item label="生效日期" prop="effectiveDate">
<el-date-picker
v-model="creditDialog.form.effectiveDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="默认今天"
:disabled-date="afterToday"
style="width: 100%;"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="creditDialog.form.creditType === 'TEMP' ? '失效日期' : '失效日期'" prop="expireDate">
<el-date-picker
v-model="creditDialog.form.expireDate"
type="date"
value-format="YYYY-MM-DD"
:placeholder="creditDialog.form.creditType === 'TEMP' ? '不填为长期有效' : '必选,到期自动清零'"
style="width: 100%;"
/>
<div class="rate-tip">
{{ creditDialog.form.creditType === 'TEMP'
? '失效日当天仍有效,次日访问账户时自动清零并记到期流水'
: '信用额度失效日期必填;失效日当天仍有效,次日自动清零,已用额度未回款时将形成欠费拦截' }}
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="调整原因" prop="reason">
<el-input
@@ -859,6 +865,33 @@
</template>
</el-dialog>
<!-- ====== 审批选择角色额度池弹窗 ====== -->
<el-dialog title="审批通过 - 选择额度池" v-model="approveDialog.visible" width="520px" append-to-body>
<el-alert type="warning" :closable="false" class="rate-tip" style="margin-bottom: 12px;">
本次为净增额度 {{ formatMoney(approveDialog.netDelta) }} CNY,需占用您名下角色的可分配额度总额;额度调减或到期后自动释放回池。
</el-alert>
<el-form label-width="100px">
<el-form-item label="额度池角色" required>
<el-select v-model="approveDialog.roleId" placeholder="请选择角色额度池" style="width: 100%;">
<el-option
v-for="p in approveDialog.pools"
:key="p.roleId"
:label="`${p.roleName}(总额 ${formatMoney(p.totalQuota)} / 已分配 ${formatMoney(p.usedQuota)} / 剩余 ${formatMoney(p.availableQuota)})`"
:value="p.roleId"
:disabled="Number(p.availableQuota) <= 0"
/>
</el-select>
</el-form-item>
<el-form-item label="本次需分配">
<span class="money-out">{{ formatMoney(approveDialog.netDelta) }} CNY</span>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="approveDialog.loading" @click="submitApproveWithPool">确认审批通过</el-button>
<el-button @click="approveDialog.visible = false">取 消</el-button>
</template>
</el-dialog>
<!-- ====== 余额核销账单弹窗 ====== -->
<el-dialog title="余额核销账单" v-model="woDialog.visible" width="560px" append-to-body>
<el-descriptions :column="1" border size="small" v-if="woDialog.bill">
@@ -934,7 +967,7 @@
import axios from 'axios'
import { saveAs } from 'file-saver'
import { getCustomer } from "@/api/customersuppliermanagement/customerinfo/customer.js"
import { listAccount, listReceipt, getReceipt, addReceipt, confirmReceipt, cancelReceipt, listFlow, listCreditFlow, applyCreditAdjust, listCreditApply, approveCreditApply, rejectCreditApply, cancelCreditApply } from "@/api/customersuppliermanagement/customermoney/customermoney.js"
import { listAccount, listReceipt, getReceipt, addReceipt, confirmReceipt, cancelReceipt, listFlow, listCreditFlow, applyCreditAdjust, listCreditApply, approveCreditApply, rejectCreditApply, cancelCreditApply, listMyCreditPools } from "@/api/customersuppliermanagement/customermoney/customermoney.js"
import { listUnsettledBill, listBalanceWriteoff, balanceWriteoff, cancelBalanceWriteoff } from "@/api/customersuppliermanagement/customermoney/customerbill.js"
import { convertToBase } from "@/api/data/exchangeRate"
import { getToken } from "@/utils/auth"
@@ -1087,6 +1120,19 @@ const tempCreditPeriodText = computed(() => {
if (a.extraExpireDate < today) return '已过期'
return '有效期至 ' + a.extraExpireDate
})
const tempCreditPeriodExpired = computed(() => tempCreditPeriodText.value === '已过期')
/** 信用额度有效期文案 */
const creditPeriodText = computed(() => {
const a = currentAccount.value
const total = Number(a.totalCredit) || 0
if (total <= 0) return ''
if (!a.creditExpireDate) return '长期有效'
const today = proxy.parseTime(new Date(), '{y}-{m}-{d}')
if (a.creditExpireDate < today) return '已过期'
return '有效期至 ' + a.creditExpireDate
})
const creditPeriodExpired = computed(() => creditPeriodText.value === '已过期')
/* ============ 额度调整弹窗 ============ */
const creditFormRef = ref(null)
@@ -1095,12 +1141,22 @@ const creditDialog = reactive({
visible: false,
form: {}
})
const creditRules = {
const creditRules = computed(() => ({
creditType: [{ required: true, message: '请选择额度类型', trigger: 'change' }],
adjustMode: [{ required: true, message: '请选择调整方式', trigger: 'change' }],
changeAmount: [{ required: true, message: '调整金额必须大于0', trigger: 'blur' }],
reason: [{ required: true, message: '请填写调整原因', trigger: 'blur' }]
}
reason: [{ required: true, message: '请填写调整原因', trigger: 'blur' }],
expireDate: [{
validator: (rule, value, callback) => {
if (creditDialog.form.creditType === 'CREDIT' && !value) {
callback(new Error('请选择信用额度失效日期'))
} else {
callback()
}
},
trigger: 'change'
}]
}))
/** 禁止选择晚于今天的日期(不允许预约未来生效) */
function afterToday(date) {
@@ -1176,11 +1232,13 @@ function submitAdjustCredit() {
proxy.$modal.msgError('调整金额必须大于0')
return
}
if (f.creditType === 'TEMP') {
if (f.expireDate && f.effectiveDate && f.expireDate <= f.effectiveDate) {
proxy.$modal.msgError('失效日期必须晚于生效日期')
return
}
if (f.creditType === 'CREDIT' && !f.expireDate) {
proxy.$modal.msgError('请选择信用额度失效日期')
return
}
if (f.expireDate && f.effectiveDate && f.expireDate <= f.effectiveDate) {
proxy.$modal.msgError('失效日期必须晚于生效日期')
return
}
if (creditAfter.value < 0) {
proxy.$modal.msgError('调整后额度不能为负数')
@@ -1206,10 +1264,8 @@ function submitAdjustCredit() {
proxy.$modal.msgError('缺少客户ID,请从客户列表进入客户余额页')
return
}
if (f.creditType === 'TEMP') {
payload.effectiveDate = f.effectiveDate || null
payload.expireDate = f.expireDate || null
}
payload.effectiveDate = f.effectiveDate || null
payload.expireDate = f.expireDate || null
creditSubmitLoading.value = true
applyCreditAdjust(payload).then(() => {
proxy.$modal.msgSuccess('申请已提交,等待审批')
@@ -1561,13 +1617,76 @@ function afterApplyHandled() {
getCreditFlowList()
}
function isApplyNetIncrease(row) {
const a = currentAccount.value
const before = Number(row.creditType === 'TEMP' ? a.extraCredit : a.totalCredit) || 0
const input = Number(row.changeAmount) || 0
let after = before
if (row.adjustMode === 'SET') after = input
else if (row.adjustMode === 'ADD') after = before + input
else after = before - input
return after - before
}
function approveApply(row, approveRoleId) {
return approveCreditApply(row.applyId, { approveRemark: '审批通过', approveRoleId: approveRoleId || null })
}
function handleApproveApply(row) {
proxy.$modal.confirm(`确认通过额度调整申请「${row.applyNo}」?通过后将立即调整客户额度并写入额度流水。`)
.then(() => approveCreditApply(row.applyId, '审批通过'))
.then(() => {
proxy.$modal.msgSuccess('已审批通过')
afterApplyHandled()
}).catch(() => {})
const netDelta = isApplyNetIncrease(row)
listMyCreditPools().then(res => {
const pools = res.data || []
if (netDelta > 0 && pools.length > 0) {
// 非超管且名下有角色额度池:净增审批必须选池占用
approveDialog.apply = row
approveDialog.pools = pools
approveDialog.roleId = pools.length === 1 ? pools[0].roleId : null
approveDialog.netDelta = netDelta
approveDialog.visible = true
return
}
const poolTip = (netDelta > 0 && pools.length === 0)
? '(提示:净增额度需占用角色额度池,您名下未配置额度池,提交后将由后端校验拦截)'
: ''
proxy.$modal.confirm(`确认通过额度调整申请「${row.applyNo}」?通过后将立即调整客户额度并写入额度流水。${poolTip}`)
.then(() => approveApply(row, null))
.then(() => {
proxy.$modal.msgSuccess('已审批通过')
afterApplyHandled()
}).catch(() => {})
}).catch(() => {})
}
/** 审批选角色额度池弹窗 */
const approveDialog = reactive({
visible: false,
loading: false,
apply: null,
pools: [],
roleId: null,
netDelta: 0
})
const selectedPool = computed(() => approveDialog.pools.find(p => p.roleId === approveDialog.roleId))
function submitApproveWithPool() {
if (!approveDialog.roleId) {
proxy.$modal.msgError('请选择本次额度分配占用的角色额度池')
return
}
const pool = selectedPool.value
if (pool && Number(pool.availableQuota) < Number(approveDialog.netDelta)) {
proxy.$modal.msgError(`角色【${pool.roleName}】额度池剩余 ${pool.availableQuota},本次需分配 ${formatMoney(approveDialog.netDelta)}`)
return
}
approveDialog.loading = true
approveApply(approveDialog.apply, approveDialog.roleId).then(() => {
proxy.$modal.msgSuccess('已审批通过')
approveDialog.visible = false
afterApplyHandled()
}).finally(() => {
approveDialog.loading = false
})
}
function handleRejectApply(row) {
+99 -1
View File
@@ -71,6 +71,9 @@
<el-tooltip content="分配用户" placement="top" v-if="row.roleId !== 1">
<el-button link type="primary" icon="User" @click="handleAuthUser(row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
<el-tooltip content="额度池" placement="top" v-if="row.roleId !== 1">
<el-button link type="primary" icon="Wallet" @click="handleQuota(row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
</template>
</basic-table>
@@ -173,11 +176,50 @@
</div>
</template>
</el-dialog>
<!-- 角色额度池设置对话框 -->
<el-dialog title="角色可分配额度总额" v-model="quotaOpen" width="480px" append-to-body>
<el-form :model="quotaForm" label-width="110px">
<el-form-item label="角色名称">
<el-input v-model="quotaForm.roleName" disabled />
</el-form-item>
<el-form-item label="可分配总额度">
<el-input-number
v-model="quotaForm.totalQuota"
:min="0"
:precision="2"
:step="1000"
controls-position="right"
style="width: 220px"
/>
<span style="margin-left: 8px; color: #909399">CNY</span>
</el-form-item>
<el-form-item label="已分配额度">
<span>{{ quotaForm.usedQuota }} CNY</span>
</el-form-item>
<el-form-item label="剩余可分配">
<span :style="{ color: quotaForm.availableQuota < 0 ? '#f56c6c' : '#67c23a' }">{{ quotaForm.availableQuota }} CNY</span>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="quotaForm.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
<el-alert
title="该角色用户审批客户额度净增时占用此池;额度调减或到期自动释放回池。总额不能小于已分配额度。"
type="info"
:closable="false"
/>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" :loading="quotaSaving" @click="submitQuota">确 定</el-button>
<el-button @click="quotaOpen = false">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Role">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role"
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect, listRoleCreditQuota, setRoleCreditQuota } from "@/api/system/role"
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu"
import { nextTick } from "vue"
@@ -294,6 +336,62 @@ function handleAuthUser(row) {
router.push("/system/role-auth/user/" + row.roleId)
}
/** 额度池设置 */
const quotaOpen = ref(false)
const quotaSaving = ref(false)
const quotaForm = reactive({
roleId: undefined,
roleName: "",
totalQuota: 0,
usedQuota: 0,
availableQuota: 0,
remark: ""
})
function recalcQuotaAvailable() {
quotaForm.availableQuota = Number((Number(quotaForm.totalQuota || 0) - Number(quotaForm.usedQuota || 0)).toFixed(2))
}
function handleQuota(row) {
listRoleCreditQuota().then(response => {
const pool = (response.data || []).find(item => item.roleId === row.roleId)
quotaForm.roleId = row.roleId
quotaForm.roleName = row.roleName
quotaForm.totalQuota = pool ? Number(pool.totalQuota) : 0
quotaForm.usedQuota = pool ? Number(pool.usedQuota) : 0
quotaForm.remark = pool && pool.remark ? pool.remark : ""
recalcQuotaAvailable()
quotaOpen.value = true
})
}
watch(() => quotaForm.totalQuota, () => {
recalcQuotaAvailable()
})
function submitQuota() {
if (quotaForm.totalQuota === undefined || quotaForm.totalQuota === null || quotaForm.totalQuota < 0) {
proxy.$modal.msgError("可分配额度总额不能小于0")
return
}
if (Number(quotaForm.totalQuota) < Number(quotaForm.usedQuota)) {
proxy.$modal.msgError("可分配额度总额不能小于已分配额度:" + quotaForm.usedQuota)
return
}
quotaSaving.value = true
setRoleCreditQuota({
roleId: quotaForm.roleId,
totalQuota: quotaForm.totalQuota,
remark: quotaForm.remark
}).then(() => {
proxy.$modal.msgSuccess("额度池设置成功")
quotaOpen.value = false
tableRef.value.refresh()
}).finally(() => {
quotaSaving.value = false
})
}
/** 查询菜单树结构 */
function getMenuTreeselect() {
menuTreeselect().then(response => {