客户余额加账单核销
This commit is contained in:
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -11,6 +11,11 @@
|
||||
<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"
|
||||
@@ -334,6 +339,110 @@
|
||||
@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>
|
||||
|
||||
@@ -619,6 +728,75 @@
|
||||
<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>
|
||||
|
||||
@@ -627,6 +805,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, 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"
|
||||
|
||||
@@ -1167,6 +1346,176 @@ function resetCreditFlowQuery() {
|
||||
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() })
|
||||
@@ -1283,12 +1632,25 @@ function initPage() {
|
||||
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)
|
||||
@@ -1401,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