Merge branch 'master' of http://git.lionquexpress.com/wuweihua/BMS
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
// 查询客户已绑定的套餐列表
|
||||
export function listCustomerPriceSet(customerId) {
|
||||
return request({
|
||||
url: '/customersupplier/customerinfo/priceset/bind/list',
|
||||
method: 'get',
|
||||
params: { customerId }
|
||||
})
|
||||
}
|
||||
|
||||
// 查询可绑定的生效套餐选项
|
||||
export function listPriceSetOption(query) {
|
||||
return request({
|
||||
url: '/customersupplier/customerinfo/priceset/option/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 保存客户套餐绑定(已绑定则更新/更换,未绑定则新增)
|
||||
export function saveCustomerPriceSet(data) {
|
||||
return request({
|
||||
url: '/customersupplier/customerinfo/priceset/bind',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 解绑客户套餐
|
||||
export function delCustomerPriceSet(customerPricesetIds) {
|
||||
return request({
|
||||
url: '/customersupplier/customerinfo/priceset/bind/' + customerPricesetIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/** 查询客户可核销账单(已确认/部分核销) */
|
||||
export function listUnsettledBill(customerId) {
|
||||
return request({
|
||||
url: '/customersupplier/customerbill/unsettled/' + customerId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询余额核销记录 */
|
||||
export function listBalanceWriteoff(query) {
|
||||
return request({
|
||||
url: '/customersupplier/customerbill/writeoff/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/** 用客户账户余额核销账单 */
|
||||
export function balanceWriteoff(data) {
|
||||
return request({
|
||||
url: '/customersupplier/customerbill/writeoff',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/** 反核销(取消一笔有效余额核销) */
|
||||
export function cancelBalanceWriteoff(writeoffId, cancelRemark) {
|
||||
return request({
|
||||
url: '/customersupplier/customerbill/writeoff/' + writeoffId + '/cancel',
|
||||
method: 'put',
|
||||
data: { cancelRemark }
|
||||
})
|
||||
}
|
||||
|
||||
/** 账单查看:账单列表(只读) */
|
||||
export function listCustomerBill(query) {
|
||||
return request({
|
||||
url: '/customersupplier/customerbill/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/** 账单查看:账单详情(含明细) */
|
||||
export function getCustomerBill(arBillId) {
|
||||
return request({
|
||||
url: '/customersupplier/customerbill/' + arBillId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -58,3 +58,21 @@ export function listFlow(query) {
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询额度流水列表 */
|
||||
export function listCreditFlow(query) {
|
||||
return request({
|
||||
url: '/customersupplier/customermoney/credit/flow/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/** 调整客户额度(信用额度/临时额度),调整过程留额度流水 */
|
||||
export function adjustCredit(data) {
|
||||
return request({
|
||||
url: '/customersupplier/customermoney/credit/adjust',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<template>
|
||||
<div class="app-container ar-bill-page">
|
||||
<basic-table ref="tableRef" :columns="columns" :search-columns="searchColumns" :query="queryList" row-key="arBillId" :selection="false">
|
||||
<basic-table ref="tableRef" :columns="columns" :search-columns="searchColumns" :search-label-width="82" :query="queryList" row-key="arBillId" :selection="false">
|
||||
<template #toolbar>
|
||||
<el-col :span="1.5"><el-button type="primary" plain icon="Plus" v-hasPermi="['ar:bill:generate']" @click="generateOpen = true">生成账单</el-button></el-col>
|
||||
</template>
|
||||
<template #search-customerId="{ model }">
|
||||
<customer-select v-model="model.customerId" placeholder="请选择客户" :page-size="1000" />
|
||||
</template>
|
||||
<template #col-status="{ row }"><el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag></template>
|
||||
<template #col-totalAmount="{ row }"><span class="amount">{{ money(row.totalAmount) }}</span></template>
|
||||
<template #col-writtenOffAmount="{ row }"><span class="written">{{ money(row.writtenOffAmount) }}</span></template>
|
||||
@@ -36,18 +39,18 @@ const detail = ref(null)
|
||||
const generateOpen = ref(false)
|
||||
const statusOptions = AR_BILL_STATUS_OPTIONS
|
||||
const columns = [
|
||||
{ label: '账单号', prop: 'billNo', minWidth: 180, showOverflowTooltip: true }, { label: '账单名称', prop: 'billName', minWidth: 180, showOverflowTooltip: true }, { label: '客户', prop: 'customerName', minWidth: 130, showOverflowTooltip: true },
|
||||
{ label: '账单号', prop: 'billNo', minWidth: 180, showOverflowTooltip: true }, { label: '账单名称', prop: 'billName', minWidth: 180, showOverflowTooltip: true }, { label: '客户名称', prop: 'customerName', minWidth: 130, showOverflowTooltip: true }, { label: '客户编码', prop: 'customerCode', minWidth: 120, showOverflowTooltip: true },
|
||||
{ label: '账单截止日', prop: 'billPeriodEnd', minWidth: 120, formatter: row => `截至 ${date(row.billPeriodEnd)}` }, { label: '订单数', prop: 'orderCount', width: 80 },
|
||||
{ label: '应收总额', prop: 'totalAmount', width: 120, align: 'right', slot: 'totalAmount' }, { label: '已核销', prop: 'writtenOffAmount', width: 110, align: 'right', slot: 'writtenOffAmount' },
|
||||
{ label: '未核销', prop: 'unwrittenAmount', width: 110, align: 'right', slot: 'unwrittenAmount' }, { label: '状态', prop: 'status', width: 110, slot: 'status' },
|
||||
{ label: '账单日期', prop: 'billDate', width: 120, formatter: row => date(row.billDate) }, { label: '操作', prop: 'action', width: 260, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
const searchColumns = [
|
||||
{ label: '账单号', prop: 'billNo', component: 'input', placeholder: '账单号', width: '180px' }, { label: '账单名称', prop: 'billName', component: 'input', placeholder: '账单名称', width: '180px' }, { label: '客户编码', prop: 'customerCode', component: 'input', placeholder: '客户编码', width: '140px' },
|
||||
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '全部状态', width: '150px' }, { label: '账期', prop: 'periodRange', component: 'daterange', width: '300px' }
|
||||
{ label: '账单号', prop: 'billNo', component: 'input', placeholder: '账单号', width: '180px' }, { label: '账单名称', prop: 'billName', component: 'input', placeholder: '账单名称', width: '180px' }, { label: '客户名称', prop: 'customerId', component: 'input', placeholder: '请选择客户', width: '220px' }, { label: '客户编码', prop: 'customerCode', component: 'input', placeholder: '客户编码', width: '140px' },
|
||||
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '全部状态', width: '150px' }, { label: '账单日期', prop: 'billDateRange', component: 'daterange', width: '300px' }, { label: '账单截止日', prop: 'billPeriodEnd', component: 'date', width: '160px' }
|
||||
]
|
||||
|
||||
function queryList(params) { const query = { ...params }; if (query.periodRange?.length === 2) { query.billPeriodStart = query.periodRange[0]; query.billPeriodEnd = query.periodRange[1] }; delete query.periodRange; return listArBill(query) }
|
||||
function queryList(params) { const query = { ...params }; if (query.billDateRange?.length === 2) { query.billDateStart = query.billDateRange[0]; query.billDateEnd = query.billDateRange[1] }; delete query.billDateRange; return listArBill(query) }
|
||||
function handleGenerateSuccess(bill) { tableRef.value.refresh(); viewBill(bill) }
|
||||
function viewBill(row) { if (!row?.arBillId) return; detailOpen.value = true; detailLoading.value = true; detail.value = null; getArBill(row.arBillId).then(res => { detail.value = res.data || null }).finally(() => { detailLoading.value = false }) }
|
||||
function confirmBill(row) { proxy.$modal.confirm(`确认账单「${row.billNo}」?确认后将进入正式账单状态,请确认订单及费用金额无误后继续。`).then(() => confirmArBill(row.arBillId)).then(() => { proxy.$modal.msgSuccess('账单已确认'); tableRef.value.refresh(); viewBill(row) }).catch(() => {}) }
|
||||
@@ -61,7 +64,61 @@ function statusType(value) { return AR_BILL_STATUS_TYPES[value] || 'info' }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ar-bill-page :deep(.basic-table > .basic-form) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px 20px;
|
||||
align-items: center;
|
||||
padding: 16px 18px 4px;
|
||||
margin-bottom: 18px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item) {
|
||||
min-width: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:not(:last-child) .el-form-item__content) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:not(:last-child) .el-form-item__content > *) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:last-child) {
|
||||
grid-column: 1 / -1;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:last-child .el-form-item__content) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:last-child .el-button) {
|
||||
min-width: 88px;
|
||||
margin-left: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ar-bill-page .amount { color: #f56c6c; font-weight: 600; }
|
||||
.ar-bill-page .written { color: #67c23a; }
|
||||
.ar-bill-page .unwritten { color: #e6a23c; }
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.ar-bill-page :deep(.basic-table > .basic-form) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ar-bill-page :deep(.basic-table > .basic-form) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
padding: 14px 12px 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 查询条件 -->
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
|
||||
<el-form-item label="账单号" prop="billNo">
|
||||
<el-input v-model="queryParams.billNo" placeholder="请输入账单号" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="账单名称" prop="billName">
|
||||
<el-input v-model="queryParams.billName" placeholder="请输入账单名称" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="客户名称" prop="customerName">
|
||||
<el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width: 140px">
|
||||
<el-option v-for="opt in statusOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="币种" prop="currency">
|
||||
<el-select v-model="queryParams.currency" placeholder="全部币种" clearable style="width: 120px">
|
||||
<el-option v-for="c in currencyOptions" :key="c" :label="c" :value="c" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="账期">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Search" @click="toggleSearch">{{ showSearch ? '收起' : '展开' }}</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" />
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="billList" border>
|
||||
<el-table-column label="账单号" align="center" prop="billNo" min-width="170" show-overflow-tooltip />
|
||||
<el-table-column label="账单名称" align="center" prop="billName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="客户" align="center" prop="customerName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="账期开始" align="center" prop="billPeriodStart" width="110">
|
||||
<template #default="scope">{{ fmtDate(scope.row.billPeriodStart) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账期结束" align="center" prop="billPeriodEnd" width="110">
|
||||
<template #default="scope">{{ fmtDate(scope.row.billPeriodEnd) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账单日期" align="center" prop="billDate" width="110">
|
||||
<template #default="scope">{{ fmtDate(scope.row.billDate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单数" align="center" prop="orderCount" width="80" />
|
||||
<el-table-column label="应收金额" align="right" width="130">
|
||||
<template #default="scope">
|
||||
<span class="amount">{{ scope.row.currency }} {{ fmtMoney(scope.row.totalAmount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已核销" align="right" width="120">
|
||||
<template #default="scope"><span class="written">{{ fmtMoney(scope.row.writtenOffAmount) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未核销" align="right" width="120">
|
||||
<template #default="scope"><span class="unwritten">{{ fmtMoney(scope.row.unwrittenAmount) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="statusTag(scope.row.status)">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="80" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)"
|
||||
v-hasPermi="['customer:bill:query']">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 账单详情抽屉(只读) -->
|
||||
<el-drawer v-model="detailOpen" size="72%" :title="drawerTitle" destroy-on-close>
|
||||
<div v-loading="detailLoading" class="bill-detail">
|
||||
<template v-if="detail">
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="账单号">{{ detail.billNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账单名称" :span="2">{{ detail.billName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户">{{ detail.customerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户编码">{{ detail.customerCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusTag(detail.status)">{{ statusLabel(detail.status) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="账期开始">{{ fmtDate(detail.billPeriodStart) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账期结束">{{ fmtDate(detail.billPeriodEnd) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账单日期">{{ fmtDate(detail.billDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="币种">{{ detail.currency }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单数">{{ detail.orderCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应收金额">
|
||||
<span class="amount">{{ fmtMoney(detail.totalAmount) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="已核销金额">
|
||||
<span class="written">{{ fmtMoney(detail.writtenOffAmount) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="未核销金额">
|
||||
<span class="unwritten">{{ fmtMoney(detail.unwrittenAmount) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="确认人">{{ detail.confirmedBy || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="确认时间">{{ fmtDateTime(detail.confirmedTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收款银行">
|
||||
{{ [detail.bankName, detail.bankAccountName].filter(Boolean).join(' / ') || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="3">{{ detail.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="detail-title">订单明细({{ detail.details?.length || 0 }} 单)</div>
|
||||
<el-table :data="detail.details || []" border size="small" row-key="detailId">
|
||||
<el-table-column type="expand">
|
||||
<template #default="props">
|
||||
<div class="fee-wrap">
|
||||
<div class="fee-title">费用明细</div>
|
||||
<el-table :data="props.row.fees || []" border size="small">
|
||||
<el-table-column label="序号" type="index" width="55" align="center" />
|
||||
<el-table-column label="费用名称" prop="feeName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="数量" prop="quantity" width="90" align="right" />
|
||||
<el-table-column label="单位" prop="unit" width="70" align="center" />
|
||||
<el-table-column label="单价" prop="unitPrice" width="100" align="right" />
|
||||
<el-table-column label="金额" prop="amount" width="110" align="right">
|
||||
<template #default="f">{{ f.row.currency }} {{ fmtMoney(f.row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
|
||||
<template #empty><span class="empty-tip">无费用明细</span></template>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="序号" prop="detailNo" width="60" align="center" />
|
||||
<el-table-column label="订单号" prop="orderNo" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="跟踪号" prop="trackingNo" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="产品" prop="productName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="目的国" prop="countryName" width="100" align="center" />
|
||||
<el-table-column label="实重" prop="actualWeight" width="80" align="right" />
|
||||
<el-table-column label="体积重" prop="volumeWeight" width="80" align="right" />
|
||||
<el-table-column label="计费重" prop="chargeWeight" width="80" align="right" />
|
||||
<el-table-column label="金额" prop="totalAmount" width="120" align="right">
|
||||
<template #default="scope">{{ scope.row.currency }} {{ fmtMoney(scope.row.totalAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CustomerBill">
|
||||
import { listCustomerBill, getCustomerBill } from '@/api/customersuppliermanagement/customermoney/customerbill'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const route = useRoute()
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 'DRAFT', label: '草稿' },
|
||||
{ value: 'CONFIRMED', label: '已确认' },
|
||||
{ value: 'PARTIAL_WRITTEN_OFF', label: '部分核销' },
|
||||
{ value: 'WRITTEN_OFF', label: '已核销' },
|
||||
{ value: 'VOID', label: '已作废' }
|
||||
]
|
||||
const statusTagMap = { DRAFT: 'info', CONFIRMED: 'warning', PARTIAL_WRITTEN_OFF: 'primary', WRITTEN_OFF: 'success', VOID: 'danger' }
|
||||
const currencyOptions = ['CNY', 'USD', 'EUR', 'GBP', 'HKD', 'JPY']
|
||||
|
||||
const billList = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const showSearch = ref(true)
|
||||
const dateRange = ref([])
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
billNo: '',
|
||||
billName: '',
|
||||
customerName: '',
|
||||
customerId: route.query.customerId || undefined,
|
||||
status: '',
|
||||
currency: ''
|
||||
})
|
||||
|
||||
const detailOpen = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detail = ref(null)
|
||||
const drawerTitle = computed(() => detail.value ? `账单详情 - ${detail.value.billNo}` : '账单详情')
|
||||
|
||||
function statusLabel(v) {
|
||||
return statusOptions.find(o => o.value === v)?.label || v || '-'
|
||||
}
|
||||
function statusTag(v) {
|
||||
return statusTagMap[v] || 'info'
|
||||
}
|
||||
function fmtMoney(v) {
|
||||
const n = Number(v)
|
||||
return isNaN(n) ? '0.00' : n.toFixed(2)
|
||||
}
|
||||
function fmtDate(v) {
|
||||
return v ? String(v).substring(0, 10) : '-'
|
||||
}
|
||||
function fmtDateTime(v) {
|
||||
return v ? String(v).substring(0, 16) : '-'
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
const params = proxy.addDateRange({ ...queryParams }, dateRange.value)
|
||||
listCustomerBill(params).then(res => {
|
||||
billList.value = res.rows || []
|
||||
total.value = res.total || 0
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
dateRange.value = []
|
||||
proxy.resetForm('queryRef')
|
||||
// 从客户余额页跳入时保留客户过滤
|
||||
queryParams.customerId = route.query.customerId || undefined
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
function toggleSearch() {
|
||||
showSearch.value = !showSearch.value
|
||||
}
|
||||
|
||||
function handleView(row) {
|
||||
detail.value = null
|
||||
detailOpen.value = true
|
||||
detailLoading.value = true
|
||||
getCustomerBill(row.arBillId).then(res => {
|
||||
detail.value = res.data || null
|
||||
}).finally(() => {
|
||||
detailLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
getList()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.amount {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 600;
|
||||
}
|
||||
.written {
|
||||
color: var(--el-color-success);
|
||||
font-weight: 600;
|
||||
}
|
||||
.unwritten {
|
||||
color: var(--el-color-warning);
|
||||
font-weight: 600;
|
||||
}
|
||||
.bill-detail {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
.detail-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin: 16px 0 8px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
}
|
||||
.fee-wrap {
|
||||
padding: 8px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
.fee-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.empty-tip {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
+233
-3
@@ -108,7 +108,11 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="临时额度" prop="tempEd">
|
||||
<el-input v-model="form.tempEd" placeholder="请输入临时额度" />
|
||||
<el-input v-model="form.tempEd" :disabled="!isAdd" placeholder="请输入临时额度">
|
||||
<template #append v-if="!isAdd">
|
||||
<span style="color: #e6a23c">请在客户余额-额度管理中调整</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -130,7 +134,11 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="信用额度" prop="creditEd">
|
||||
<el-input v-model="form.creditEd" placeholder="请输入信用额度" />
|
||||
<el-input v-model="form.creditEd" :disabled="!isAdd" placeholder="请输入信用额度">
|
||||
<template #append v-if="!isAdd">
|
||||
<span style="color: #e6a23c">请在客户余额-额度管理中调整</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
@@ -178,6 +186,50 @@
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ============ 绑定套餐 ============ -->
|
||||
<el-tab-pane label="绑定套餐" name="priceSet">
|
||||
<div class="pane-toolbar">
|
||||
<el-button type="primary" plain icon="Plus" size="small" :disabled="isAdd" @click="openPriceSetDialog()">
|
||||
{{ boundPriceSets.length ? '更换套餐' : '绑定套餐' }}
|
||||
</el-button>
|
||||
<span class="pane-tip">绑定后写入客户套餐关系表 prc_customer_price_set,一个客户仅可绑定一个价格套餐,仅「生效中」的套餐可绑定</span>
|
||||
</div>
|
||||
<el-table :data="boundPriceSets" border size="small" class="bound-table">
|
||||
<el-table-column label="序号" type="index" width="55" align="center" />
|
||||
<el-table-column label="套餐编码" prop="priceSetCode" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="套餐名称" prop="priceSetName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="计价类型" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.setType === 'DISCOUNT' ? 'warning' : 'primary'" size="small">
|
||||
{{ setTypeMap[row.setType] || row.setType || '-' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="套餐状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="priceSetStatusTagType[row.priceSetStatus] || 'info'" size="small">
|
||||
{{ priceSetStatusMap[row.priceSetStatus] || '未知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="优先级" prop="bindPriority" width="70" align="center" />
|
||||
<el-table-column label="绑定生效" prop="prBegindate" width="110" align="center" />
|
||||
<el-table-column label="绑定失效" width="100" align="center">
|
||||
<template #default="{ row }">{{ row.prEnddate || '长期' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="priceSetNote" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openPriceSetDialog(row)">更换</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleUnbindPriceSet(row)">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty :description="isAdd ? '请先保存客户后再绑定套餐' : '暂未绑定套餐,点击上方「绑定套餐」'" :image-size="80" />
|
||||
</template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ============ 公司主体 ============ -->
|
||||
<el-tab-pane label="公司主体" name="company">
|
||||
<div class="pane-toolbar">
|
||||
@@ -574,6 +626,75 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ====== 绑定套餐对话框 ====== -->
|
||||
<el-dialog :title="priceSetDialogTitle" v-model="priceSetDialogVisible" width="560px" append-to-body>
|
||||
<el-form ref="priceSetRef" :model="priceSetForm" :rules="priceSetRules" label-width="96px">
|
||||
<el-form-item label="价格套餐" prop="priceSetId">
|
||||
<el-select
|
||||
v-model="priceSetForm.priceSetId"
|
||||
filterable
|
||||
placeholder="请选择生效中的套餐"
|
||||
style="width: 100%;"
|
||||
@change="onPriceSetChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in priceSetOptions"
|
||||
:key="opt.priceSetId"
|
||||
:label="((opt.priceSetCode || '') + ' ' + opt.priceSetName).trim()"
|
||||
:value="opt.priceSetId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="selectedPriceSet" label="套餐信息">
|
||||
<div class="ps-readonly">
|
||||
<el-tag size="small" :type="selectedPriceSet.setType === 'DISCOUNT' ? 'warning' : 'primary'">
|
||||
{{ setTypeMap[selectedPriceSet.setType] || selectedPriceSet.setType }}
|
||||
</el-tag>
|
||||
<span class="ps-readonly__range">
|
||||
套餐有效期:{{ selectedPriceSet.effectiveDate || '-' }} 至 {{ selectedPriceSet.expireDate || '长期' }}
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级" prop="bindPriority">
|
||||
<el-input-number v-model="priceSetForm.bindPriority" :min="1" controls-position="right" style="width: 100%;" />
|
||||
<div class="pane-tip">数字越小优先级越高,1 为最高</div>
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生效日期" prop="prBegindate">
|
||||
<el-date-picker
|
||||
v-model="priceSetForm.prBegindate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择日期"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="失效日期" prop="prEnddate">
|
||||
<el-date-picker
|
||||
v-model="priceSetForm.prEnddate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="长期有效"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="priceSetNote">
|
||||
<el-input v-model="priceSetForm.priceSetNote" type="textarea" :rows="2" maxlength="100" show-word-limit placeholder="绑定备注(选填)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" :loading="priceSetSaving" @click="submitPriceSetForm">确 定</el-button>
|
||||
<el-button @click="priceSetDialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -582,6 +703,10 @@ import {
|
||||
listCustomer, getCustomer, addCustomer, updateCustomer,
|
||||
delCustomerCompany, delCustomerContact
|
||||
} from '@/api/customersuppliermanagement/customerinfo/customer.js'
|
||||
import {
|
||||
listCustomerPriceSet, listPriceSetOption,
|
||||
saveCustomerPriceSet, delCustomerPriceSet
|
||||
} from '@/api/customersuppliermanagement/customerinfo/customerPriceSet.js'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import axios from 'axios'
|
||||
import { saveAs } from 'file-saver'
|
||||
@@ -630,7 +755,7 @@ function buildEmptyForm() {
|
||||
foreignCurrencyBalance: null,
|
||||
weightThreshold: null,
|
||||
throwRatio: null,
|
||||
creditEd: null,
|
||||
creditEd: 1000,
|
||||
productIds: null,
|
||||
attachmentList: [],
|
||||
companyList: [],
|
||||
@@ -656,6 +781,99 @@ function removeProduct(productId) {
|
||||
form.value.productIds = ids.join(',')
|
||||
}
|
||||
|
||||
/* ---------------- 套餐绑定(prc_customer_price_set,一客户一套餐) ---------------- */
|
||||
const setTypeMap = { FIXED: '固定价', DISCOUNT: '折扣' }
|
||||
const priceSetStatusMap = { D: '草稿', A: '审核中', E: '生效', R: '驳回', S: '停用' }
|
||||
const priceSetStatusTagType = { D: 'info', A: 'warning', E: 'success', R: 'danger', S: 'info' }
|
||||
|
||||
const boundPriceSets = ref([])
|
||||
const priceSetOptions = ref([])
|
||||
const priceSetDialogVisible = ref(false)
|
||||
const priceSetDialogTitle = ref('')
|
||||
const priceSetSaving = ref(false)
|
||||
const priceSetRef = ref()
|
||||
const priceSetForm = ref({})
|
||||
|
||||
const priceSetRules = {
|
||||
priceSetId: [{ required: true, message: '请选择价格套餐', trigger: 'change' }],
|
||||
bindPriority: [{ required: true, message: '优先级不能为空', trigger: 'blur' }],
|
||||
prBegindate: [{ required: true, message: '绑定生效日期不能为空', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const selectedPriceSet = computed(() =>
|
||||
priceSetOptions.value.find(o => o.priceSetId === priceSetForm.value.priceSetId)
|
||||
)
|
||||
|
||||
/** 加载当前客户已绑定套餐 */
|
||||
function loadBoundPriceSets() {
|
||||
if (!form.value.customerId) {
|
||||
boundPriceSets.value = []
|
||||
return
|
||||
}
|
||||
listCustomerPriceSet(form.value.customerId).then(res => {
|
||||
boundPriceSets.value = res.data || []
|
||||
})
|
||||
}
|
||||
|
||||
/** 加载可绑定的生效套餐(仅加载一次) */
|
||||
function ensurePriceSetOptions() {
|
||||
if (priceSetOptions.value.length) return
|
||||
listPriceSetOption().then(res => {
|
||||
priceSetOptions.value = res.data || []
|
||||
})
|
||||
}
|
||||
|
||||
/** 打开绑定/更换套餐弹窗:row 为空=新增绑定,有值=更换 */
|
||||
function openPriceSetDialog(row) {
|
||||
if (!form.value.customerId) {
|
||||
proxy.$modal.msgWarning('请先保存客户后再绑定套餐')
|
||||
return
|
||||
}
|
||||
ensurePriceSetOptions()
|
||||
priceSetForm.value = row
|
||||
? { ...row }
|
||||
: { priceSetId: null, bindPriority: 1, prBegindate: null, prEnddate: null, priceSetNote: null }
|
||||
priceSetDialogTitle.value = row || boundPriceSets.value.length ? '更换套餐' : '绑定套餐'
|
||||
priceSetDialogVisible.value = true
|
||||
nextTick(() => proxy.$refs['priceSetRef'] && proxy.$refs['priceSetRef'].clearValidate())
|
||||
}
|
||||
|
||||
/** 选择套餐后,生效日期未填时默认带出套餐生效日期 */
|
||||
function onPriceSetChange(priceSetId) {
|
||||
const opt = priceSetOptions.value.find(o => o.priceSetId === priceSetId)
|
||||
if (opt && !priceSetForm.value.prBegindate) {
|
||||
priceSetForm.value.prBegindate = opt.effectiveDate || null
|
||||
}
|
||||
}
|
||||
|
||||
function submitPriceSetForm() {
|
||||
proxy.$refs['priceSetRef'].validate(valid => {
|
||||
if (!valid) return
|
||||
if (priceSetForm.value.prEnddate && priceSetForm.value.prBegindate > priceSetForm.value.prEnddate) {
|
||||
proxy.$modal.msgWarning('失效日期不能早于生效日期')
|
||||
return
|
||||
}
|
||||
const payload = { ...priceSetForm.value, customerId: form.value.customerId }
|
||||
priceSetSaving.value = true
|
||||
saveCustomerPriceSet(payload).then(() => {
|
||||
proxy.$modal.msgSuccess('保存成功')
|
||||
priceSetDialogVisible.value = false
|
||||
loadBoundPriceSets()
|
||||
}).finally(() => {
|
||||
priceSetSaving.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleUnbindPriceSet(row) {
|
||||
proxy.$modal.confirm(`确认解绑套餐「${row.priceSetName || row.priceSetCode || ''}」?解绑后该客户询价将不再命中此套餐。`)
|
||||
.then(() => delCustomerPriceSet(row.customerPricesetId))
|
||||
.then(() => {
|
||||
proxy.$modal.msgSuccess('解绑成功')
|
||||
loadBoundPriceSets()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
/* ---------------- 用户冗余姓名 ---------------- */
|
||||
function handleUserChange(uid, user, nameField) {
|
||||
form.value[nameField] = user ? (user.nickName || user.userName || null) : null
|
||||
@@ -968,6 +1186,7 @@ function loadDetail(id) {
|
||||
form.value.attachmentList = form.value.attachmentList || []
|
||||
form.value.companyList = form.value.companyList || []
|
||||
form.value.contactList = form.value.contactList || []
|
||||
loadBoundPriceSets()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1023,6 +1242,7 @@ function initByRoute() {
|
||||
loadDetail(customerId.value).finally(() => { pageLoading.value = false })
|
||||
} else {
|
||||
form.value = buildEmptyForm()
|
||||
boundPriceSets.value = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1077,4 +1297,14 @@ onMounted(() => {
|
||||
.bound-table {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.ps-readonly {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
&__range {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+839
-4
@@ -11,6 +11,17 @@
|
||||
<div class="head-code">客户编码:{{ customer.customerCode || route.query.customerCode || '-' }}</div>
|
||||
<div class="head-right">
|
||||
<el-tag type="success" size="small">本位币账户 CNY</el-tag>
|
||||
<el-button
|
||||
icon="Document"
|
||||
@click="goBillView"
|
||||
v-hasPermi="['customer:bill:list']"
|
||||
>查看账单</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="Money"
|
||||
@click="openAdjustDialog()"
|
||||
v-hasPermi="['customer:credit:adjust']"
|
||||
>额度调整</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -31,14 +42,23 @@
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<el-card shadow="hover" class="balance-card">
|
||||
<div class="balance-label">总额度</div>
|
||||
<div class="balance-label">
|
||||
总额度
|
||||
<el-button link type="primary" size="small" @click="openAdjustDialog('CREDIT')"
|
||||
v-hasPermi="['customer:credit:adjust']">调整</el-button>
|
||||
</div>
|
||||
<div class="balance-value">{{ formatMoney(currentAccount.totalCredit) }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
<el-card shadow="hover" class="balance-card">
|
||||
<div class="balance-label">额外额度</div>
|
||||
<div class="balance-label">
|
||||
额外额度
|
||||
<el-button link type="primary" size="small" @click="openAdjustDialog('TEMP')"
|
||||
v-hasPermi="['customer:credit:adjust']">调整</el-button>
|
||||
</div>
|
||||
<div class="balance-value">{{ formatMoney(currentAccount.extraCredit) }}</div>
|
||||
<div class="balance-sub">{{ tempCreditPeriodText }}</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="8" :md="4">
|
||||
@@ -225,6 +245,204 @@
|
||||
@pagination="getFlowList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ====== 额度流水 ====== -->
|
||||
<el-tab-pane label="额度流水" name="creditFlow">
|
||||
<el-form :model="creditFlowQuery" :inline="true" class="search-form">
|
||||
<el-form-item label="额度类型" prop="creditType">
|
||||
<el-select v-model="creditFlowQuery.creditType" placeholder="全部" clearable style="width: 130px;">
|
||||
<el-option v-for="opt in creditTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="变动类型" prop="changeType">
|
||||
<el-select v-model="creditFlowQuery.changeType" placeholder="全部" clearable style="width: 130px;">
|
||||
<el-option v-for="opt in creditChangeTypeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker
|
||||
v-model="creditFlowDateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getCreditFlowList">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetCreditFlowQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="creditFlowList" border v-loading="creditFlowLoading">
|
||||
<el-table-column label="流水号" align="center" prop="flowNo" width="190" />
|
||||
<el-table-column label="时间" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">{{ formatDateTime(scope.row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="额度类型" align="center" prop="creditType" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.creditType === 'TEMP' ? 'warning' : ''">
|
||||
{{ formatOption(creditTypeOptions, scope.row.creditType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动类型" align="center" prop="changeType" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="creditChangeTag(scope.row.changeType)">
|
||||
{{ formatOption(creditChangeTypeOptions, scope.row.changeType) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调整方式" align="center" prop="adjustMode" width="90">
|
||||
<template #default="scope">{{ formatOption(adjustModeOptions, scope.row.adjustMode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="方向" align="center" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.direction === 1" type="success">增加</el-tag>
|
||||
<el-tag v-else-if="scope.row.direction === -1" type="danger">减少</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动前额度" align="center" prop="beforeAmount" width="110">
|
||||
<template #default="scope">{{ formatMoney(scope.row.beforeAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动额度" align="center" width="110">
|
||||
<template #default="scope">
|
||||
<span :class="scope.row.direction === 1 ? 'money-in' : 'money-out'">
|
||||
{{ scope.row.direction === 1 ? '+' : '-' }}{{ formatMoney(scope.row.changeAmount) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动后额度" align="center" prop="afterAmount" width="110">
|
||||
<template #default="scope">{{ formatMoney(scope.row.afterAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已用额度" align="center" prop="usedCredit" width="100">
|
||||
<template #default="scope">{{ formatMoney(scope.row.usedCredit) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="生效日期" align="center" prop="effectiveDate" width="110">
|
||||
<template #default="scope">{{ scope.row.effectiveDate || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="失效日期" align="center" prop="expireDate" width="110">
|
||||
<template #default="scope">{{ scope.row.expireDate || '长期' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调整原因" align="center" prop="reason" show-overflow-tooltip min-width="160" />
|
||||
<el-table-column label="操作人" align="center" prop="createBy" width="100" />
|
||||
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip min-width="160" />
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="creditFlowTotal > 0"
|
||||
:total="creditFlowTotal"
|
||||
v-model:page="creditFlowQuery.pageNum"
|
||||
v-model:limit="creditFlowQuery.pageSize"
|
||||
@pagination="getCreditFlowList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<!-- ====== 账单核销(用账户余额核销应收账单) ====== -->
|
||||
<el-tab-pane label="账单核销" name="billWriteoff">
|
||||
<el-alert type="info" :closable="false" class="writeoff-summary">
|
||||
<div class="wo-summary">
|
||||
<span>待核销账单:<b>{{ unsettledList.length }}</b> 张</span>
|
||||
<span>未核销总额:<b class="money-out">CNY {{ unsettledTotal }}</b></span>
|
||||
<span>账户余额:<b class="money-in">CNY {{ formatMoney(currentAccount.balance) }}</b></span>
|
||||
<el-button type="primary" link size="small" icon="Refresh" @click="loadBillWriteoffData">刷新</el-button>
|
||||
<el-button type="primary" plain size="small" @click="goBillView"
|
||||
v-hasPermi="['customer:bill:list']">查看全部账单</el-button>
|
||||
</div>
|
||||
</el-alert>
|
||||
|
||||
<div class="wo-section-title">待核销账单(已确认 / 部分核销)</div>
|
||||
<el-table :data="unsettledList" border size="small" v-loading="unsettledLoading">
|
||||
<el-table-column label="账单号" align="center" prop="billNo" width="170" show-overflow-tooltip />
|
||||
<el-table-column label="账单名称" align="center" prop="billName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="账期开始" align="center" prop="billPeriodStart" width="110">
|
||||
<template #default="scope">{{ formatDate(scope.row.billPeriodStart) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账期结束" align="center" prop="billPeriodEnd" width="110">
|
||||
<template #default="scope">{{ formatDate(scope.row.billPeriodEnd) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账单日期" align="center" prop="billDate" width="110">
|
||||
<template #default="scope">{{ formatDate(scope.row.billDate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收金额" align="center" width="120">
|
||||
<template #default="scope">{{ scope.row.currency }} {{ formatMoney(scope.row.totalAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已核销" align="center" width="110">
|
||||
<template #default="scope"><span class="money-in">{{ formatMoney(scope.row.writtenOffAmount) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未核销" align="center" width="110">
|
||||
<template #default="scope"><span class="money-out">{{ formatMoney(scope.row.unwrittenAmount) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="billStatusTag(scope.row.status)" size="small">{{ billStatusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="90" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="openWriteoffDialog(scope.row)"
|
||||
v-hasPermi="['customer:bill:writeoff']">核销</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无待核销账单" :image-size="70" />
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<div class="wo-section-title">余额核销记录</div>
|
||||
<el-form :inline="true" class="search-form">
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="woQuery.status" placeholder="全部" clearable style="width: 130px;">
|
||||
<el-option label="有效" value="EFFECTIVE" />
|
||||
<el-option label="已反核销" value="CANCELLED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getWriteoffList">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetWriteoffQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="writeoffList" border size="small" v-loading="writeoffLoading">
|
||||
<el-table-column label="核销单号" align="center" prop="writeoffNo" width="180" />
|
||||
<el-table-column label="账单号" align="center" prop="billNo" width="170" show-overflow-tooltip />
|
||||
<el-table-column label="账单名称" align="center" prop="billName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="核销金额" align="center" width="120">
|
||||
<template #default="scope"><span class="money-out">-{{ formatMoney(scope.row.writeoffAmount) }} {{ scope.row.currency }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="核销日期" align="center" prop="writeoffDate" width="110">
|
||||
<template #default="scope">{{ formatDate(scope.row.writeoffDate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" width="90">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 'EFFECTIVE' ? 'success' : 'info'" size="small">
|
||||
{{ scope.row.status === 'EFFECTIVE' ? '有效' : '已反核销' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="登记人" align="center" prop="createBy" width="90" />
|
||||
<el-table-column label="登记时间" align="center" prop="createTime" width="140">
|
||||
<template #default="scope">{{ formatDateTime(scope.row.createTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="反核销原因" align="center" prop="cancelRemark" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="操作" align="center" width="90" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.status === 'EFFECTIVE'" link type="warning" size="small"
|
||||
@click="openCancelWriteoffDialog(scope.row)"
|
||||
v-hasPermi="['customer:bill:writeoff:cancel']">反核销</el-button>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
v-show="writeoffTotal > 0"
|
||||
:total="writeoffTotal"
|
||||
v-model:page="woQuery.pageNum"
|
||||
v-model:limit="woQuery.pageSize"
|
||||
@pagination="getWriteoffList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
@@ -402,6 +620,183 @@
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ====== 额度调整弹窗 ====== -->
|
||||
<el-dialog
|
||||
:title="creditDialog.form.creditType === 'TEMP' ? '临时额度调整' : '信用额度调整'"
|
||||
v-model="creditDialog.visible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
@closed="resetCreditForm"
|
||||
>
|
||||
<el-alert type="info" :closable="false" class="credit-tip">
|
||||
<div>当前{{ creditDialog.form.creditType === 'TEMP' ? '临时额度' : '信用额度' }}:
|
||||
<b>{{ formatMoney(creditBefore) }}</b>,已用额度:<b>{{ formatMoney(currentAccount.usedCredit) }}</b></div>
|
||||
</el-alert>
|
||||
<el-form ref="creditFormRef" :model="creditDialog.form" :rules="creditRules" label-width="100px">
|
||||
<el-row>
|
||||
<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="TEMP">临时额度</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="调整方式" prop="adjustMode">
|
||||
<el-radio-group v-model="creditDialog.form.adjustMode">
|
||||
<el-radio value="SET">设置为</el-radio>
|
||||
<el-radio value="ADD">增加</el-radio>
|
||||
<el-radio value="SUB">减少</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="调整金额" prop="changeAmount">
|
||||
<el-input-number
|
||||
v-model="creditDialog.form.changeAmount"
|
||||
:precision="2"
|
||||
:step="1000"
|
||||
:min="0.01"
|
||||
controls-position="right"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</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="24">
|
||||
<el-form-item label="调整原因" prop="reason">
|
||||
<el-input
|
||||
v-model="creditDialog.form.reason"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
placeholder="请必填,例如:客户旺季临时授信 / 年审调整长期额度"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="credit-preview">
|
||||
<div>调整后{{ creditDialog.form.creditType === 'TEMP' ? '临时额度' : '信用额度' }}:
|
||||
<b :class="creditAfter < 0 ? 'money-out' : 'money-in'">{{ formatMoney(creditAfter) }}</b>
|
||||
</div>
|
||||
<div>调整后授信总额:<b>{{ formatMoney(totalCreditAfter) }}</b>
|
||||
(可用 = 授信总额 - 已用 {{ formatMoney(currentAccount.usedCredit) }})</div>
|
||||
<el-alert
|
||||
v-if="creditAfter < 0"
|
||||
type="error" :closable="false"
|
||||
title="调整后额度不能为负数" class="credit-alert"
|
||||
/>
|
||||
<el-alert
|
||||
v-else-if="totalCreditAfter < (Number(currentAccount.usedCredit) || 0)"
|
||||
type="error" :closable="false"
|
||||
title="授信总额不能小于已用额度" class="credit-alert"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="creditSubmitLoading" @click="submitAdjustCredit">确 定</el-button>
|
||||
<el-button @click="creditDialog.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">
|
||||
<el-descriptions-item label="账单号">{{ woDialog.bill.billNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账单名称">{{ woDialog.bill.billName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账单未核销金额">
|
||||
<span class="money-out">CNY {{ formatMoney(woDialog.bill.unwrittenAmount) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="账户可用余额">
|
||||
<span class="money-in">CNY {{ formatMoney(currentAccount.balance) }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-form :model="woDialog.form" label-width="110px" class="wo-form">
|
||||
<el-form-item label="本次核销金额" required>
|
||||
<el-input-number
|
||||
v-model="woDialog.form.writeoffAmount"
|
||||
:precision="2"
|
||||
:step="100"
|
||||
:min="0.01"
|
||||
controls-position="right"
|
||||
style="width: 220px;"
|
||||
/>
|
||||
<span class="wo-form-unit">CNY</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="核销日期">
|
||||
<el-date-picker
|
||||
v-model="woDialog.form.writeoffDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="默认今天"
|
||||
style="width: 220px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="woDialog.form.remark" type="textarea" :rows="2" maxlength="255" show-word-limit
|
||||
placeholder="选填,最多255字" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-alert type="warning" :closable="false">
|
||||
<template title>
|
||||
核销后账户余额变为 CNY {{ woAfterBalance }},同步释放等额已用额度;余额不足时无法提交。
|
||||
</template>
|
||||
</el-alert>
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="woSubmitLoading" @click="submitWriteoff">确认核销</el-button>
|
||||
<el-button @click="woDialog.visible = false">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ====== 反核销弹窗 ====== -->
|
||||
<el-dialog title="反核销(取消余额核销)" v-model="cancelWoDialog.visible" width="480px" append-to-body>
|
||||
<el-alert type="warning" :closable="false" class="credit-tip">
|
||||
<span v-if="cancelWoDialog.row">
|
||||
反核销后,核销金额 CNY {{ formatMoney(cancelWoDialog.row.writeoffAmount) }} 将退回客户账户余额,
|
||||
账单「{{ cancelWoDialog.row.billNo }}」恢复为未核销,已用额度同步加回。
|
||||
</span>
|
||||
</el-alert>
|
||||
<el-form label-width="100px" class="wo-form">
|
||||
<el-form-item label="反核销原因" required>
|
||||
<el-input v-model="cancelWoDialog.cancelRemark" type="textarea" :rows="3" maxlength="255" show-word-limit
|
||||
placeholder="请填写反核销原因(必填)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="warning" :loading="cancelWoLoading" @click="submitCancelWriteoff">确认反核销</el-button>
|
||||
<el-button @click="cancelWoDialog.visible = false">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -409,11 +804,13 @@
|
||||
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 } from "@/api/customersuppliermanagement/customermoney/customermoney.js"
|
||||
import { listAccount, listReceipt, getReceipt, addReceipt, confirmReceipt, cancelReceipt, listFlow, listCreditFlow, adjustCredit } 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"
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -472,9 +869,28 @@ function receiptTypeTag(v) {
|
||||
function receiptStatusTag(v) {
|
||||
return { PENDING: 'warning', CONFIRMED: 'success', CANCELLED: 'danger' }[v] || 'info'
|
||||
}
|
||||
function bizTypeTag(v) {
|
||||
const bizTypeTag = function(v) {
|
||||
return { RECEIPT: 'success', BILL_PAY: 'danger', REFUND: 'warning', ADJUST: 'info', FREEZE: 'info', UNFREEZE: '' }[v] || 'info'
|
||||
}
|
||||
|
||||
/* ================= 额度相关枚举 ================= */
|
||||
const creditTypeOptions = [
|
||||
{ value: 'CREDIT', label: '信用额度' },
|
||||
{ value: 'TEMP', label: '临时额度' }
|
||||
]
|
||||
const creditChangeTypeOptions = [
|
||||
{ value: 'INIT', label: '初始化' },
|
||||
{ value: 'ADJUST', label: '手工调整' },
|
||||
{ value: 'EXPIRE', label: '到期失效' }
|
||||
]
|
||||
const adjustModeOptions = [
|
||||
{ value: 'SET', label: '设置为' },
|
||||
{ value: 'ADD', label: '增加' },
|
||||
{ value: 'SUB', label: '减少' }
|
||||
]
|
||||
function creditChangeTag(v) {
|
||||
return { INIT: '', ADJUST: 'primary', EXPIRE: 'danger' }[v] || 'info'
|
||||
}
|
||||
function formatMoney(val) {
|
||||
const num = Number(val)
|
||||
return isNaN(num) ? '0.00' : num.toFixed(2)
|
||||
@@ -506,6 +922,152 @@ const availableCredit = computed(() => {
|
||||
return formatMoney(total + extra - used)
|
||||
})
|
||||
|
||||
/** 临时额度有效期文案 */
|
||||
const tempCreditPeriodText = computed(() => {
|
||||
const a = currentAccount.value
|
||||
const extra = Number(a.extraCredit) || 0
|
||||
if (extra <= 0) return ''
|
||||
if (!a.extraExpireDate) return '长期有效'
|
||||
const today = proxy.parseTime(new Date(), '{y}-{m}-{d}')
|
||||
if (a.extraExpireDate < today) return '已过期'
|
||||
return '有效期至 ' + a.extraExpireDate
|
||||
})
|
||||
|
||||
/* ============ 额度调整弹窗 ============ */
|
||||
const creditFormRef = ref(null)
|
||||
const creditSubmitLoading = ref(false)
|
||||
const creditDialog = reactive({
|
||||
visible: false,
|
||||
form: {}
|
||||
})
|
||||
const creditRules = {
|
||||
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' }]
|
||||
}
|
||||
|
||||
/** 禁止选择晚于今天的日期(不允许预约未来生效) */
|
||||
function afterToday(date) {
|
||||
const today = new Date()
|
||||
today.setHours(23, 59, 59, 999)
|
||||
return date.getTime() > today.getTime()
|
||||
}
|
||||
|
||||
function defaultCreditForm(creditType) {
|
||||
return {
|
||||
customerId: route.query.customerId || null,
|
||||
creditType: creditType || 'CREDIT',
|
||||
adjustMode: 'ADD',
|
||||
changeAmount: null,
|
||||
reason: '',
|
||||
effectiveDate: proxy.parseTime(new Date(), '{y}-{m}-{d}'),
|
||||
expireDate: ''
|
||||
}
|
||||
}
|
||||
|
||||
function openAdjustDialog(creditType) {
|
||||
const customerId = Number(route.query.customerId)
|
||||
if (!customerId) {
|
||||
proxy.$modal.msgError('缺少客户ID,请从客户列表进入客户余额页')
|
||||
return
|
||||
}
|
||||
resetCreditForm()
|
||||
creditDialog.form = defaultCreditForm(creditType)
|
||||
creditDialog.visible = true
|
||||
}
|
||||
|
||||
function resetCreditForm() {
|
||||
creditDialog.form = defaultCreditForm()
|
||||
proxy.resetForm('creditFormRef')
|
||||
}
|
||||
|
||||
/** 切换额度类型:清掉校验红框,临时额度默认生效日为今天 */
|
||||
function onCreditTypeChange() {
|
||||
creditDialog.form.changeAmount = null
|
||||
if (creditDialog.form.creditType === 'TEMP' && !creditDialog.form.effectiveDate) {
|
||||
creditDialog.form.effectiveDate = proxy.parseTime(new Date(), '{y}-{m}-{d}')
|
||||
}
|
||||
nextTick(() => proxy.clearValidate('creditFormRef'))
|
||||
}
|
||||
|
||||
/** 当前被调整额度(调整前) */
|
||||
const creditBefore = computed(() => {
|
||||
const a = currentAccount.value
|
||||
return Number(creditDialog.form.creditType === 'TEMP' ? a.extraCredit : a.totalCredit) || 0
|
||||
})
|
||||
|
||||
/** 调整后被调额度 */
|
||||
const creditAfter = computed(() => {
|
||||
const input = Number(creditDialog.form.changeAmount) || 0
|
||||
const mode = creditDialog.form.adjustMode
|
||||
if (mode === 'SET') return input
|
||||
return mode === 'SUB' ? creditBefore.value - input : creditBefore.value + input
|
||||
})
|
||||
|
||||
/** 调整后授信总额(信用+临时) */
|
||||
const totalCreditAfter = computed(() => {
|
||||
const a = currentAccount.value
|
||||
const other = Number(creditDialog.form.creditType === 'TEMP' ? a.totalCredit : a.extraCredit) || 0
|
||||
return other + creditAfter.value
|
||||
})
|
||||
|
||||
function submitAdjustCredit() {
|
||||
creditFormRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
const f = creditDialog.form
|
||||
const amount = Number(f.changeAmount)
|
||||
if (!(amount > 0)) {
|
||||
proxy.$modal.msgError('调整金额必须大于0')
|
||||
return
|
||||
}
|
||||
if (f.creditType === 'TEMP') {
|
||||
if (f.expireDate && f.effectiveDate && f.expireDate <= f.effectiveDate) {
|
||||
proxy.$modal.msgError('失效日期必须晚于生效日期')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (creditAfter.value < 0) {
|
||||
proxy.$modal.msgError('调整后额度不能为负数')
|
||||
return
|
||||
}
|
||||
const used = Number(currentAccount.value.usedCredit) || 0
|
||||
if (totalCreditAfter.value < used) {
|
||||
proxy.$modal.msgError('调整后授信总额不能小于已用额度 ' + formatMoney(used))
|
||||
return
|
||||
}
|
||||
if (creditAfter.value === creditBefore.value) {
|
||||
proxy.$modal.msgError('额度未发生变化,无需调整')
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
customerId: Number(f.customerId) || Number(route.query.customerId) || null,
|
||||
creditType: f.creditType,
|
||||
adjustMode: f.adjustMode,
|
||||
changeAmount: amount,
|
||||
reason: f.reason
|
||||
}
|
||||
if (!payload.customerId) {
|
||||
proxy.$modal.msgError('缺少客户ID,请从客户列表进入客户余额页')
|
||||
return
|
||||
}
|
||||
if (f.creditType === 'TEMP') {
|
||||
payload.effectiveDate = f.effectiveDate || null
|
||||
payload.expireDate = f.expireDate || null
|
||||
}
|
||||
creditSubmitLoading.value = true
|
||||
adjustCredit(payload).then(() => {
|
||||
proxy.$modal.msgSuccess('额度调整成功')
|
||||
creditDialog.visible = false
|
||||
loadCustomer()
|
||||
loadAccounts()
|
||||
getCreditFlowList()
|
||||
}).finally(() => {
|
||||
creditSubmitLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function loadCustomer() {
|
||||
const customerId = route.query.customerId
|
||||
if (!customerId) return
|
||||
@@ -752,6 +1314,208 @@ function resetFlowQuery() {
|
||||
getFlowList()
|
||||
}
|
||||
|
||||
/* ================= 额度流水 ================= */
|
||||
const creditFlowList = ref([])
|
||||
const creditFlowTotal = ref(0)
|
||||
const creditFlowLoading = ref(false)
|
||||
const creditFlowDateRange = ref([])
|
||||
const creditFlowQuery = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
customerId: route.query.customerId,
|
||||
creditType: '',
|
||||
changeType: ''
|
||||
})
|
||||
|
||||
function getCreditFlowList() {
|
||||
creditFlowLoading.value = true
|
||||
const params = proxy.addDateRange({ ...creditFlowQuery }, creditFlowDateRange.value)
|
||||
listCreditFlow(params).then(res => {
|
||||
creditFlowList.value = res.rows || []
|
||||
creditFlowTotal.value = res.total || 0
|
||||
}).finally(() => {
|
||||
creditFlowLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function resetCreditFlowQuery() {
|
||||
creditFlowQuery.creditType = ''
|
||||
creditFlowQuery.changeType = ''
|
||||
creditFlowDateRange.value = []
|
||||
creditFlowQuery.pageNum = 1
|
||||
getCreditFlowList()
|
||||
}
|
||||
|
||||
/* ================= 账单核销 ================= */
|
||||
const billStatusMap = {
|
||||
DRAFT: { label: '草稿', type: 'info' },
|
||||
CONFIRMED: { label: '已确认', type: 'warning' },
|
||||
PARTIAL_WRITTEN_OFF: { label: '部分核销', type: 'primary' },
|
||||
WRITTEN_OFF: { label: '已核销', type: 'success' },
|
||||
VOID: { label: '已作废', type: 'danger' }
|
||||
}
|
||||
function billStatusLabel(v) {
|
||||
return billStatusMap[v]?.label || v || '-'
|
||||
}
|
||||
function billStatusTag(v) {
|
||||
return billStatusMap[v]?.type || 'info'
|
||||
}
|
||||
function formatDate(val) {
|
||||
if (!val) return '-'
|
||||
return String(val).substring(0, 10)
|
||||
}
|
||||
function goBillView() {
|
||||
router.push({
|
||||
path: '/customersuppliermanagement/customerbill',
|
||||
query: { customerId: route.query.customerId || '' }
|
||||
})
|
||||
}
|
||||
|
||||
const unsettledList = ref([])
|
||||
const unsettledLoading = ref(false)
|
||||
/** 未核销账单合计 */
|
||||
const unsettledTotal = computed(() => {
|
||||
const sum = unsettledList.value.reduce((s, r) => s + (Number(r.unwrittenAmount) || 0), 0)
|
||||
return formatMoney(sum)
|
||||
})
|
||||
|
||||
const writeoffList = ref([])
|
||||
const writeoffTotal = ref(0)
|
||||
const writeoffLoading = ref(false)
|
||||
const woQuery = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
customerId: route.query.customerId,
|
||||
status: ''
|
||||
})
|
||||
let billWriteoffLoaded = false
|
||||
|
||||
function loadUnsettledBills() {
|
||||
const customerId = route.query.customerId
|
||||
if (!customerId) return
|
||||
unsettledLoading.value = true
|
||||
listUnsettledBill(customerId).then(res => {
|
||||
unsettledList.value = res.data || []
|
||||
}).finally(() => {
|
||||
unsettledLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getWriteoffList() {
|
||||
const customerId = route.query.customerId
|
||||
if (!customerId) return
|
||||
writeoffLoading.value = true
|
||||
woQuery.customerId = customerId
|
||||
listBalanceWriteoff({ ...woQuery }).then(res => {
|
||||
writeoffList.value = res.rows || []
|
||||
writeoffTotal.value = res.total || 0
|
||||
}).finally(() => {
|
||||
writeoffLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function resetWriteoffQuery() {
|
||||
woQuery.status = ''
|
||||
woQuery.pageNum = 1
|
||||
getWriteoffList()
|
||||
}
|
||||
|
||||
function loadBillWriteoffData() {
|
||||
loadAccounts()
|
||||
loadUnsettledBills()
|
||||
getWriteoffList()
|
||||
}
|
||||
|
||||
/* ---- 核销弹窗 ---- */
|
||||
const woSubmitLoading = ref(false)
|
||||
const woDialog = reactive({ visible: false, bill: null, form: {} })
|
||||
/** 核销后余额预览(不为负则可提交) */
|
||||
const woAfterBalance = computed(() => {
|
||||
const balance = Number(currentAccount.value.balance) || 0
|
||||
const amount = Number(woDialog.form.writeoffAmount) || 0
|
||||
return formatMoney(balance - amount)
|
||||
})
|
||||
|
||||
function openWriteoffDialog(row) {
|
||||
// 先刷新账户余额,避免用过期余额核销
|
||||
listAccount(route.query.customerId).then(res => {
|
||||
accountList.value = res.data || []
|
||||
})
|
||||
woDialog.bill = row
|
||||
const balance = Number(currentAccount.value.balance) || 0
|
||||
const unwritten = Number(row.unwrittenAmount) || 0
|
||||
woDialog.form = {
|
||||
arBillId: row.arBillId,
|
||||
customerId: Number(route.query.customerId),
|
||||
writeoffAmount: Math.min(unwritten, balance > 0 ? balance : unwritten),
|
||||
writeoffDate: proxy.parseTime(new Date(), '{y}-{m}-{d}'),
|
||||
remark: ''
|
||||
}
|
||||
woDialog.visible = true
|
||||
}
|
||||
|
||||
function submitWriteoff() {
|
||||
const bill = woDialog.bill
|
||||
const amount = Number(woDialog.form.writeoffAmount)
|
||||
if (!(amount > 0)) {
|
||||
proxy.$modal.msgError('核销金额必须大于0')
|
||||
return
|
||||
}
|
||||
const unwritten = Number(bill.unwrittenAmount) || 0
|
||||
if (amount > unwritten) {
|
||||
proxy.$modal.msgError('核销金额不能超过账单未核销金额 ' + formatMoney(unwritten))
|
||||
return
|
||||
}
|
||||
const balance = Number(currentAccount.value.balance) || 0
|
||||
if (amount > balance) {
|
||||
proxy.$modal.msgError('账户余额不足,当前余额 CNY ' + formatMoney(balance) + ',请先登记收款并审核')
|
||||
return
|
||||
}
|
||||
woSubmitLoading.value = true
|
||||
balanceWriteoff(woDialog.form).then(() => {
|
||||
proxy.$modal.msgSuccess('核销成功:账单已核销 CNY ' + formatMoney(amount))
|
||||
woDialog.visible = false
|
||||
loadBillWriteoffData()
|
||||
getFlowList()
|
||||
}).finally(() => {
|
||||
woSubmitLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/* ---- 反核销弹窗 ---- */
|
||||
const cancelWoLoading = ref(false)
|
||||
const cancelWoDialog = reactive({ visible: false, row: null, cancelRemark: '' })
|
||||
|
||||
function openCancelWriteoffDialog(row) {
|
||||
cancelWoDialog.row = row
|
||||
cancelWoDialog.cancelRemark = ''
|
||||
cancelWoDialog.visible = true
|
||||
}
|
||||
|
||||
function submitCancelWriteoff() {
|
||||
const remark = cancelWoDialog.cancelRemark?.trim()
|
||||
if (!remark) {
|
||||
proxy.$modal.msgError('请填写反核销原因')
|
||||
return
|
||||
}
|
||||
cancelWoLoading.value = true
|
||||
cancelBalanceWriteoff(cancelWoDialog.row.writeoffId, remark).then(() => {
|
||||
proxy.$modal.msgSuccess('反核销成功,余额已退回')
|
||||
cancelWoDialog.visible = false
|
||||
loadBillWriteoffData()
|
||||
getFlowList()
|
||||
}).finally(() => {
|
||||
cancelWoLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
watch(activeTab, (tab) => {
|
||||
if (tab === 'billWriteoff' && !billWriteoffLoaded) {
|
||||
billWriteoffLoaded = true
|
||||
loadBillWriteoffData()
|
||||
}
|
||||
})
|
||||
|
||||
/* ================= 附件上传 ================= */
|
||||
const uploadFileUrl = ref(baseUrl + "/common/upload")
|
||||
const uploadHeaders = ref({ Authorization: "Bearer " + getToken() })
|
||||
@@ -848,6 +1612,8 @@ function initPage() {
|
||||
viewDialog.attachments = []
|
||||
receiptDateRange.value = []
|
||||
flowDateRange.value = []
|
||||
creditFlowDateRange.value = []
|
||||
creditDialog.visible = false
|
||||
receiptQuery.customerId = customerId
|
||||
receiptQuery.receiptType = ''
|
||||
receiptQuery.status = ''
|
||||
@@ -856,15 +1622,35 @@ function initPage() {
|
||||
flowQuery.bizType = ''
|
||||
flowQuery.direction = ''
|
||||
flowQuery.pageNum = 1
|
||||
creditFlowQuery.customerId = customerId
|
||||
creditFlowQuery.creditType = ''
|
||||
creditFlowQuery.changeType = ''
|
||||
creditFlowQuery.pageNum = 1
|
||||
receiptList.value = []
|
||||
receiptTotal.value = 0
|
||||
flowList.value = []
|
||||
flowTotal.value = 0
|
||||
creditFlowList.value = []
|
||||
creditFlowTotal.value = 0
|
||||
unsettledList.value = []
|
||||
writeoffList.value = []
|
||||
writeoffTotal.value = 0
|
||||
woQuery.customerId = customerId
|
||||
woQuery.status = ''
|
||||
woQuery.pageNum = 1
|
||||
woDialog.visible = false
|
||||
cancelWoDialog.visible = false
|
||||
billWriteoffLoaded = false
|
||||
if (!customerId) return
|
||||
loadCustomer()
|
||||
loadAccounts()
|
||||
getReceiptList()
|
||||
getFlowList()
|
||||
getCreditFlowList()
|
||||
if (activeTab.value === 'billWriteoff') {
|
||||
billWriteoffLoaded = true
|
||||
loadBillWriteoffData()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(initPage)
|
||||
@@ -916,6 +1702,31 @@ watch(() => route.query.customerId, (newId, oldId) => {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.balance-sub {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
min-height: 16px;
|
||||
}
|
||||
.credit-tip {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.credit-preview {
|
||||
margin-top: 4px;
|
||||
padding: 10px 14px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.credit-alert {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.balance-value {
|
||||
font-size: 22px;
|
||||
@@ -952,4 +1763,28 @@ watch(() => route.query.customerId, (newId, oldId) => {
|
||||
font-weight: 600;
|
||||
margin: 12px 0 8px;
|
||||
}
|
||||
.writeoff-summary {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.wo-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
.wo-section-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin: 14px 0 8px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
}
|
||||
.wo-form {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.wo-form-unit {
|
||||
margin-left: 8px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user