客户余额加账单核销

This commit is contained in:
luojiaquan
2026-09-16 15:19:05 +08:00
parent f3be74b7a4
commit 2e39440540
15 changed files with 1639 additions and 5 deletions
@@ -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>