From 1ffe9b9bf12e1670fbfae0b9acbec95d4aaebe16 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期三, 20 五月 2026 16:23:23 +0800
Subject: [PATCH] fix: 完成财务模块接口对接
---
src/api/financialManagement/accountPurchasePayment.js | 32
src/api/financialManagement/accountPurchaseInvoice.js | 50
src/views/financialManagement/payable/payment.vue | 410 +--
src/views/financialManagement/receivable/receipt.vue | 678 +++++-
src/views/financialManagement/receivable/invoiceApply.vue | 239 ++
src/api/financialManagement/accountPaymentApplication.js | 59
src/api/financialManagement/accountSalesInvoice.js | 41
src/views/financialManagement/payable/input-invoice.vue | 918 ++++++++--
src/views/financialManagement/payable/paymentApply.vue | 970 +++++++++-
src/views/financialManagement/payable/reconciliation.vue | 613 +++++-
src/api/financialManagement/accountStatement.js | 41
src/views/financialManagement/payable/purchaseIn.vue | 50
src/api/financialManagement/accountSalesCollection.js | 50
src/views/financialManagement/receivable/reconciliation.vue | 563 ++++-
src/views/financialManagement/receivable/outputInvoice.vue | 489 ++++-
src/views/financialManagement/payable/purchaseReturn.vue | 113
16 files changed, 3,997 insertions(+), 1,319 deletions(-)
diff --git a/src/api/financialManagement/accountPaymentApplication.js b/src/api/financialManagement/accountPaymentApplication.js
new file mode 100644
index 0000000..0d5e438
--- /dev/null
+++ b/src/api/financialManagement/accountPaymentApplication.js
@@ -0,0 +1,59 @@
+import request from "@/utils/request";
+
+/** 鏍规嵁渚涘簲鍟嗘煡璇㈠彲鍏宠仈鍏ュ簱鍗� */
+export function getInboundBatchesBySupplier(params) {
+ return request({
+ url: "/accountPaymentApplication/getInboundBatchesBySupplier",
+ method: "get",
+ params,
+ });
+}
+
+/** 鏂板浠樻鐢宠 */
+export function addAccountPaymentApplication(data) {
+ return request({
+ url: "/accountPaymentApplication/addAccountPaymentApplication",
+ method: "post",
+ data,
+ });
+}
+
+/** 浠樻鐢宠鍒嗛〉鍒楄〃 */
+export function listPageAccountPaymentApplication(params) {
+ return request({
+ url: "/accountPaymentApplication/listPageAccountPaymentApplication",
+ method: "get",
+ params,
+ });
+}
+
+/** 淇敼浠樻鐢宠 */
+export function updateAccountPaymentApplication(data) {
+ return request({
+ url: "/accountPaymentApplication/updateAccountPaymentApplication",
+ method: "put",
+ data,
+ });
+}
+
+/** 瀹℃牳浠樻鐢宠 */
+export function auditAccountPaymentApplication(data) {
+ return request({
+ url: "/accountPaymentApplication/auditAccountPaymentApplication",
+ method: "put",
+ data,
+ });
+}
+
+/** 鍒犻櫎浠樻鐢宠锛圫pring 瑕佹眰 ids=1&ids=2 鏌ヨ鍙傛暟锛� */
+export function deleteAccountPaymentApplication(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter((id) => id !== undefined && id !== null && id !== "")
+ .map((id) => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/accountPaymentApplication/deleteAccountPaymentApplication?${query}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/financialManagement/accountPurchaseInvoice.js b/src/api/financialManagement/accountPurchaseInvoice.js
new file mode 100644
index 0000000..af391da
--- /dev/null
+++ b/src/api/financialManagement/accountPurchaseInvoice.js
@@ -0,0 +1,50 @@
+import request from "@/utils/request";
+
+/** 鏍规嵁渚涘簲鍟嗘煡璇㈠彲鍏宠仈鍏ュ簱鍗� */
+export function getInboundBatchesBySupplier(params) {
+ return request({
+ url: "/accountPurchaseInvoice/getInboundBatchesBySupplier",
+ method: "get",
+ params,
+ });
+}
+
+/** 鏂板杩涢」鍙戠エ */
+export function addAccountPurchaseInvoice(data) {
+ return request({
+ url: "/accountPurchaseInvoice/addAccountPurchaseInvoice",
+ method: "post",
+ data,
+ });
+}
+
+/** 杩涢」鍙戠エ鍒嗛〉鍒楄〃 */
+export function listPageAccountPurchaseInvoice(params) {
+ return request({
+ url: "/accountPurchaseInvoice/listPageAccountPurchaseInvoice",
+ method: "get",
+ params,
+ });
+}
+
+/** 浣滃簾杩涢」鍙戠エ */
+export function cancelAccountPurchaseInvoice(data) {
+ return request({
+ url: "/accountPurchaseInvoice/cancelAccountPurchaseInvoice",
+ method: "put",
+ data,
+ });
+}
+
+/** 鍒犻櫎杩涢」鍙戠エ锛圫pring 瑕佹眰 ids=1&ids=2 鏌ヨ鍙傛暟锛� */
+export function deleteAccountPurchaseInvoice(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter((id) => id !== undefined && id !== null && id !== "")
+ .map((id) => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/accountPurchaseInvoice/deleteAccountPurchaseInvoice?${query}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/financialManagement/accountPurchasePayment.js b/src/api/financialManagement/accountPurchasePayment.js
new file mode 100644
index 0000000..a10f05a
--- /dev/null
+++ b/src/api/financialManagement/accountPurchasePayment.js
@@ -0,0 +1,32 @@
+import request from "@/utils/request";
+
+/** 鏂板浠樻鍗曪紙鍏宠仈浠樻鐢宠锛� */
+export function addAccountPurchasePayment(data) {
+ return request({
+ url: "/accountPurchasePayment/addAccountPurchasePayment",
+ method: "post",
+ data,
+ });
+}
+
+/** 浠樻鍗曞垎椤靛垪琛� */
+export function listPageAccountPurchasePayment(params) {
+ return request({
+ url: "/accountPurchasePayment/listPageAccountPurchasePayment",
+ method: "get",
+ params,
+ });
+}
+
+/** 鍒犻櫎浠樻鍗曪紙Spring 瑕佹眰 ids=1&ids=2 鏌ヨ鍙傛暟锛� */
+export function deleteAccountPurchasePayment(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter((id) => id !== undefined && id !== null && id !== "")
+ .map((id) => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/accountPurchasePayment/deleteAccountPurchasePayment?${query}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/financialManagement/accountSalesCollection.js b/src/api/financialManagement/accountSalesCollection.js
new file mode 100644
index 0000000..abeb977
--- /dev/null
+++ b/src/api/financialManagement/accountSalesCollection.js
@@ -0,0 +1,50 @@
+import request from "@/utils/request";
+
+/** 鏍规嵁瀹㈡埛鏌ヨ鍙叧鑱斿嚭搴撳崟 */
+export function getOutboundBatchesByCustomer(params) {
+ return request({
+ url: "/accountSalesCollection/getOutboundBatchesByCustomer",
+ method: "get",
+ params,
+ });
+}
+
+/** 鏂板鏀舵鍗� */
+export function addAccountSalesCollection(data) {
+ return request({
+ url: "/accountSalesCollection/addAccountSalesCollection",
+ method: "post",
+ data,
+ });
+}
+
+/** 鏀舵鍗曞垎椤靛垪琛� */
+export function listPageAccountSalesCollection(params) {
+ return request({
+ url: "/accountSalesCollection/listPageAccountSalesCollection",
+ method: "get",
+ params,
+ });
+}
+
+/** 淇敼鏀舵鍗� */
+export function updateAccountSalesCollection(data) {
+ return request({
+ url: "/accountSalesCollection/updateAccountSalesCollection",
+ method: "put",
+ data,
+ });
+}
+
+/** 鍒犻櫎鏀舵鍗曪紙Spring 瑕佹眰 ids=1&ids=2 鏌ヨ鍙傛暟锛� */
+export function deleteAccountSalesCollection(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter((id) => id !== undefined && id !== null && id !== "")
+ .map((id) => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/accountSalesCollection/deleteAccountSalesCollection?${query}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/financialManagement/accountSalesInvoice.js b/src/api/financialManagement/accountSalesInvoice.js
new file mode 100644
index 0000000..6e74c53
--- /dev/null
+++ b/src/api/financialManagement/accountSalesInvoice.js
@@ -0,0 +1,41 @@
+import request from "@/utils/request";
+
+/** 鏂板閿�椤瑰彂绁� */
+export function addAccountSalesInvoice(data) {
+ return request({
+ url: "/accountSalesInvoice/addAccountSalesInvoice",
+ method: "post",
+ data,
+ });
+}
+
+/** 閿�椤瑰彂绁ㄥ垎椤靛垪琛� */
+export function listPageAccountSalesInvoice(params) {
+ return request({
+ url: "/accountSalesInvoice/listPageAccountSalesInvoice",
+ method: "get",
+ params,
+ });
+}
+
+/** 浣滃簾閿�椤瑰彂绁� */
+export function cancelAccountSalesInvoice(data) {
+ return request({
+ url: "/accountSalesInvoice/cancelAccountSalesInvoice",
+ method: "put",
+ data,
+ });
+}
+
+/** 鍒犻櫎閿�椤瑰彂绁紙Spring 瑕佹眰 ids=1&ids=2 鏌ヨ鍙傛暟锛� */
+export function deleteAccountSalesInvoice(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter((id) => id !== undefined && id !== null && id !== "")
+ .map((id) => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/accountSalesInvoice/deleteAccountSalesInvoice?${query}`,
+ method: "delete",
+ });
+}
diff --git a/src/api/financialManagement/accountStatement.js b/src/api/financialManagement/accountStatement.js
new file mode 100644
index 0000000..bf80101
--- /dev/null
+++ b/src/api/financialManagement/accountStatement.js
@@ -0,0 +1,41 @@
+import request from "@/utils/request";
+
+/** 鎸夋湀浠芥煡璇㈠璐﹀崟鏄庣粏锛堢敓鎴愬墠棰勮锛� */
+export function getAccountStatementDetailsByMonth(params) {
+ return request({
+ url: "/accountStatement/getAccountStatementDetailsByMonth",
+ method: "get",
+ params,
+ });
+}
+
+/** 鏂板瀵硅处鍗� */
+export function addAccountStatement(data) {
+ return request({
+ url: "/accountStatement/addAccountStatement",
+ method: "post",
+ data,
+ });
+}
+
+/** 瀵硅处鍗曞垎椤靛垪琛� */
+export function listPageAccountStatement(params) {
+ return request({
+ url: "/accountStatement/listPageAccountStatement",
+ method: "get",
+ params,
+ });
+}
+
+/** 鍒犻櫎瀵硅处鍗曪紙Spring 瑕佹眰 ids=1&ids=2 鏌ヨ鍙傛暟锛� */
+export function deleteAccountStatement(ids) {
+ const idList = Array.isArray(ids) ? ids : [ids];
+ const query = idList
+ .filter((id) => id !== undefined && id !== null && id !== "")
+ .map((id) => `ids=${encodeURIComponent(id)}`)
+ .join("&");
+ return request({
+ url: `/accountStatement/deleteAccountStatement?${query}`,
+ method: "delete",
+ });
+}
diff --git a/src/views/financialManagement/payable/input-invoice.vue b/src/views/financialManagement/payable/input-invoice.vue
index 660d0dd..86ebd09 100644
--- a/src/views/financialManagement/payable/input-invoice.vue
+++ b/src/views/financialManagement/payable/input-invoice.vue
@@ -1,50 +1,61 @@
<template>
<div class="app-container">
<el-form :model="filters" :inline="true">
- <el-form-item label="鍙戠エ浠g爜:">
- <el-input v-model="filters.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" clearable style="width: 200px;" />
- </el-form-item>
<el-form-item label="鍙戠エ鍙风爜:">
- <el-input v-model="filters.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
+ <el-input v-model="filters.invoiceNumber" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="渚涘簲鍟�:">
- <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable filterable style="width: 200px;">
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
</el-form-item>
- <el-form-item label="璁よ瘉鐘舵��:">
- <el-select v-model="filters.certifyStatus" placeholder="璇烽�夋嫨璁よ瘉鐘舵��" clearable style="width: 150px;">
- <el-option label="鏈璇�" value="uncertified" />
- <el-option label="宸茶璇�" value="certified" />
- <el-option label="璁よ瘉澶辫触" value="failed" />
+ <el-form-item label="寮�绁ㄦ棩鏈�:">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ clearable
+ style="width: 240px;"
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="姝e父" :value="0" />
+ <el-option label="浣滃簾" :value="1" />
</el-select>
</el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
<div class="table_list">
<div class="actions">
- <div>
- <el-button type="success" @click="handleBatchCertify" icon="Check" :disabled="selectedRows.length === 0">鎵归噺璁よ瘉</el-button>
- </div>
+ <div></div>
<div>
<el-button type="primary" @click="add" icon="Plus">褰曞叆鍙戠エ</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ <el-button @click="handleExport" icon="Download">瀵煎嚭</el-button>
</div>
</div>
<PIMTable
rowKey="id"
- isSelection
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
size: pagination.pageSize,
total: pagination.total,
}"
- @selection-change="handleSelectionChange"
@pagination="changePage"
>
<template #amount="{ row }">
@@ -56,54 +67,123 @@
<template #totalAmount="{ row }">
<span class="text-success">楼{{ formatMoney(row.totalAmount) }}</span>
</template>
- <template #certifyStatus="{ row }">
- <el-tag :type="getCertifyStatusType(row.certifyStatus)">{{ getCertifyStatusLabel(row.certifyStatus) }}</el-tag>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)" effect="light" round>
+ {{ getStatusLabel(row.status) }}
+ </el-tag>
</template>
<template #operation="{ row }">
<el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
- <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
- <el-button type="success" link @click="handleCertify(row)" v-if="row.certifyStatus === 'uncertified'">璁よ瘉</el-button>
+ <el-button type="warning" link @click="handleCancel(row)" v-if="isNormalStatus(row.status)">浣滃簾</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
- <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <FormDialog
+ :title="dialogTitle"
+ v-model="dialogVisible"
+ width="800px"
+ :operation-type="isView ? 'detail' : ''"
+ @confirm="submitForm"
+ @cancel="closeDialog"
+ >
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
- <el-row :gutter="20">
+ <el-row v-if="isView" :gutter="20">
<el-col :span="12">
- <el-form-item label="鍙戠エ浠g爜" prop="invoiceCode">
- <el-input v-model="form.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
- <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" />
+ <el-form-item label="鐘舵��">
+ <el-tag :type="getStatusType(form.status)" effect="light" round>
+ {{ getStatusLabel(form.status) }}
+ </el-tag>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
+ <el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
+ <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" :disabled="isView" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
<el-form-item label="渚涘簲鍟�" prop="supplierId">
- <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select
+ v-model="form.supplierId"
+ placeholder="璇烽�夋嫨渚涘簲鍟�"
+ style="width: 100%;"
+ filterable
+ :disabled="isView"
+ @change="handleSupplierChange"
+ >
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鍏ュ簱鍗�" prop="stockInRecordIds">
+ <el-input
+ :model-value="inboundBatchDisplayText"
+ placeholder="璇峰厛閫夋嫨渚涘簲鍟�"
+ readonly
+ :disabled="!form.supplierId || isView"
+ class="inbound-batch-input"
+ @click="handleInboundInputClick"
+ >
+ <template v-if="!isView" #append>
+ <el-button
+ :disabled="!form.supplierId"
+ :loading="inboundBatchLoading"
+ @click.stop="openInboundSelectDialog"
+ >
+ 閫夋嫨
+ </el-button>
+ </template>
+ </el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
- <el-date-picker v-model="form.invoiceDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ <el-date-picker
+ v-model="form.invoiceDate"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%;"
+ :disabled="isView"
+ />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
- <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" />
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
+ <el-select
+ v-model="form.invoiceType"
+ placeholder="璇烽�夋嫨鍙戠エ绫诲瀷"
+ style="width: 100%;"
+ :disabled="isView"
+ >
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="澧炲�肩◣涓撶敤鍙戠エ" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="澧炲�肩◣鏅�氬彂绁�" />
+ <el-option label="鐢靛瓙鍙戠エ" value="鐢靛瓙鍙戠エ" />
+ </el-select>
</el-form-item>
</el-col>
- <el-col :span="8">
+ <el-col :span="12">
<el-form-item label="绋庣巼" prop="taxRate">
- <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;" @change="calculateTax">
+ <el-select
+ v-model="form.taxRate"
+ placeholder="璇烽�夋嫨绋庣巼"
+ style="width: 100%;"
+ :disabled="isView"
+ @change="handleTaxRateChange"
+ >
<el-option
v-for="dict in tax_rate"
:key="dict.value"
@@ -113,47 +193,109 @@
</el-select>
</el-form-item>
</el-col>
- <el-col :span="8">
- <el-form-item label="绋庨">
- <el-input v-model="form.taxAmount" disabled />
- </el-form-item>
- </el-col>
</el-row>
<el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="璁よ瘉鐘舵��" prop="certifyStatus">
- <el-select v-model="form.certifyStatus" placeholder="璇烽�夋嫨璁よ瘉鐘舵��" style="width: 100%;" disabled>
- <el-option label="鏈璇�" value="uncertified" />
- <el-option label="宸茶璇�" value="certified" />
- <el-option label="璁よ瘉澶辫触" value="failed" />
- </el-select>
+ <el-col :span="8">
+ <el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
+ <el-input-number
+ v-model="form.amount"
+ :min="0"
+ :precision="2"
+ style="width: 100%;"
+ :disabled="isView"
+ placeholder="鏍规嵁鍏ュ簱鍗曞惈绋庨噾棰濊嚜鍔ㄦ崲绠楋紝鍙慨鏀�"
+ @change="calculateTaxFromExclusive"
+ />
</el-form-item>
</el-col>
- <el-col :span="12">
- <el-form-item label="璁よ瘉鏃ユ湡" prop="certifyDate">
- <el-date-picker v-model="form.certifyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" disabled />
+ <el-col :span="8">
+ <el-form-item label="绋庨">
+ <el-input-number
+ v-model="form.taxAmount"
+ :min="0"
+ :precision="2"
+ :controls="false"
+ style="width: 100%;"
+ disabled
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="8">
+ <el-form-item label="浠风◣鍚堣">
+ <el-input-number
+ v-model="form.totalAmount"
+ :min="0"
+ :precision="2"
+ :controls="false"
+ style="width: 100%;"
+ disabled
+ />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="鍙戠エ鍐呭" prop="content">
- <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" :disabled="isView" />
</el-form-item>
<el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" :disabled="isView" />
</el-form-item>
</el-form>
- <template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <template v-if="!isView" #footer>
+ <el-button type="primary" :loading="submitLoading" @click="submitForm">纭畾</el-button>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
</template>
</FormDialog>
+
+ <el-dialog
+ v-model="inboundSelectVisible"
+ title="閫夋嫨鍏ュ簱鍗曞彿"
+ width="1100px"
+ append-to-body
+ destroy-on-close
+ :close-on-click-modal="false"
+ @closed="handleInboundDialogClosed"
+ >
+ <el-table
+ ref="inboundTableRef"
+ v-loading="inboundBatchLoading"
+ :data="inboundBatchList"
+ row-key="id"
+ border
+ stripe
+ max-height="480"
+ @selection-change="handleInboundDialogSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column prop="inboundBatches" label="鍏ュ簱鍗曞彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="supplierName" label="渚涘簲鍟�" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="specificationModel" label="瑙勬牸鍨嬪彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="purchaseContractNumber" label="閲囪喘璁㈠崟鍙�" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="inboundDate" label="鍏ュ簱鏃ユ湡" width="110" align="center" />
+ <el-table-column prop="inboundAmount" label="鍏ュ簱閲戦(鍚◣)" width="120" align="right">
+ <template #default="{ row }">楼{{ formatMoney(getInboundRowTaxInclusiveAmount(row)) }}</template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <el-button type="primary" @click="confirmInboundSelection">纭畾</el-button>
+ <el-button @click="inboundSelectVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
+import {
+ getInboundBatchesBySupplier,
+ addAccountPurchaseInvoice,
+ listPageAccountPurchaseInvoice,
+ cancelAccountPurchaseInvoice,
+ deleteAccountPurchaseInvoice,
+} from "@/api/financialManagement/accountPurchaseInvoice.js";
defineOptions({
name: "杩涢」鍙戠エ",
@@ -163,10 +305,10 @@
const { tax_rate } = proxy.useDict("tax_rate");
const filters = reactive({
- invoiceCode: "",
- invoiceNo: "",
+ invoiceNumber: "",
supplierId: "",
- certifyStatus: "",
+ dateRange: [],
+ status: "",
});
const pagination = reactive({
@@ -176,211 +318,601 @@
});
const columns = [
- { label: "鍙戠エ浠g爜", prop: "invoiceCode", width: "130" },
- { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "120" },
+ { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "140" },
{ label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
{ label: "寮�绁ㄦ棩鏈�", prop: "invoiceDate", width: "120" },
- { label: "閲戦", prop: "amount", slot: "amount" },
- { label: "绋庨", prop: "taxAmount", slot: "taxAmount" },
- { label: "浠风◣鍚堣", prop: "totalAmount", slot: "totalAmount" },
- { label: "璁よ瘉鐘舵��", prop: "certifyStatus", slot: "certifyStatus" },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+ { label: "閲戦", prop: "amount", dataType: "slot", slot: "amount" },
+ { label: "绋庨", prop: "taxAmount", dataType: "slot", slot: "taxAmount" },
+ { label: "浠风◣鍚堣", prop: "totalAmount", dataType: "slot", slot: "totalAmount" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceType", width: "130" },
+ { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status", width: "90", align: "center" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" },
];
const dataList = ref([]);
-const selectedRows = ref([]);
+const tableLoading = ref(false);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
-const isEdit = ref(false);
-const currentId = ref(null);
+const isView = ref(false);
+const submitLoading = ref(false);
+const supplierList = ref([]);
-const supplierList = [
- { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
- { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
- { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
- { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
-];
+const inboundBatchList = ref([]);
+const inboundBatchOptions = ref([]);
+const inboundBatchLoading = ref(false);
+const inboundSelectVisible = ref(false);
+const inboundTableRef = ref(null);
+const dialogInboundSelection = ref([]);
+
+const STATUS_LABEL_MAP = { 0: "姝e父", 1: "浣滃簾" };
+const STATUS_TYPE_MAP = { 0: "success", 1: "info" };
const form = reactive({
- invoiceCode: "",
invoiceNo: "",
supplierId: "",
invoiceDate: "",
- amount: 0,
+ invoiceType: "澧炲�肩◣涓撶敤鍙戠エ",
taxRate: 13,
+ amount: 0,
taxAmount: 0,
totalAmount: 0,
- certifyStatus: "uncertified",
- certifyDate: "",
content: "",
remark: "",
+ stockInRecordIds: [],
+ inboundBatches: "",
+ storageAttachmentId: undefined,
+ status: 0,
});
const rules = {
- invoiceCode: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄤ唬鐮�", trigger: "blur" }],
invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ stockInRecordIds: [{ required: true, type: "array", min: 1, message: "璇烽�夋嫨鍏宠仈鍏ュ簱鍗�", trigger: "change" }],
invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
- amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨鍙戠エ绫诲瀷", trigger: "change" }],
taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
+ amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
};
-
-const mockData = [
- { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", invoiceDate: "2024-01-08", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, certifyStatus: "certified", certifyDate: "2024-01-15", content: "鍘熸潗鏂欓噰璐�", remark: "" },
- { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", invoiceDate: "2024-01-10", amount: 12000, taxRate: 13, taxAmount: 1560, totalAmount: 13560, certifyStatus: "uncertified", certifyDate: "", content: "鐢靛瓙鍏冨櫒浠�", remark: "" },
- { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", invoiceDate: "2024-01-12", amount: 3500, taxRate: 13, taxAmount: 455, totalAmount: 3955, certifyStatus: "certified", certifyDate: "2024-01-18", content: "鍖呰鏉愭枡", remark: "" },
-];
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
-const calculateTax = () => {
+const normalizeStatus = (status) => {
+ if (status === undefined || status === null || status === "") return 0;
+ const num = Number(status);
+ return Number.isNaN(num) ? 0 : num;
+};
+
+const isNormalStatus = (status) => normalizeStatus(status) === 0;
+
+const getStatusLabel = (status) => STATUS_LABEL_MAP[normalizeStatus(status)] ?? "姝e父";
+
+const getStatusType = (status) => STATUS_TYPE_MAP[normalizeStatus(status)] ?? "success";
+
+const parseStockInRecordIds = (value) => {
+ if (!value) return [];
+ if (Array.isArray(value)) return value;
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .map((s) => (/^\d+$/.test(s) ? Number(s) : s));
+};
+
+const formatInboundBatches = (value) => {
+ if (value === undefined || value === null || value === "") return "";
+ if (Array.isArray(value)) return value.filter(Boolean).join("銆�");
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .join("銆�");
+};
+
+const isSameInboundId = (a, b) => String(a) === String(b);
+
+const getInboundRowId = (row) => row?.id ?? row?.stockInRecordId;
+
+/** 鍏ュ簱鍗曢噾棰濅负鍚◣浠� */
+const getInboundRowTaxInclusiveAmount = (row) =>
+ Number(row?.inboundAmount ?? row?.taxInclusivePrice ?? row?.totalAmount ?? 0);
+
+const normalizeInboundBatchOptions = (data) => {
+ const list = Array.isArray(data) ? data : [];
+ return list.map((item, index) => {
+ if (typeof item === "string" || typeof item === "number") {
+ const text = String(item);
+ return { label: text, value: text, inboundAmount: 0 };
+ }
+ const label =
+ item.inboundBatches ?? item.batchNo ?? item.inboundNo ?? item.label ?? `鍏ュ簱鍗�${index + 1}`;
+ const value = item.id ?? item.stockInRecordId ?? label;
+ return {
+ label: String(label),
+ value,
+ inboundAmount: getInboundRowTaxInclusiveAmount(item),
+ };
+ });
+};
+
+/** 涓嶅惈绋庨噾棰濆彉鏇达細绋庨銆佷环绋庡悎璁℃鍚戣绠� */
+const calculateTaxFromExclusive = () => {
form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2));
form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2));
};
-const getCertifyStatusLabel = (status) => {
- const map = { uncertified: "鏈璇�", certified: "宸茶璇�", failed: "璁よ瘉澶辫触" };
- return map[status] || status;
+/** 浠风◣鍚堣鍙樻洿锛氭寜绋庣巼鍙嶇畻涓嶅惈绋庨噾棰濄�佺◣棰� */
+const calculateTaxFromInclusive = (inclusiveTotal) => {
+ const total = Number(inclusiveTotal ?? form.totalAmount ?? 0);
+ if (total <= 0) {
+ form.amount = 0;
+ form.taxAmount = 0;
+ form.totalAmount = 0;
+ return;
+ }
+ const rate = Number(form.taxRate) / 100;
+ form.totalAmount = Number(total.toFixed(2));
+ form.amount = Number((form.totalAmount / (1 + rate)).toFixed(2));
+ form.taxAmount = Number((form.totalAmount - form.amount).toFixed(2));
};
-const getCertifyStatusType = (status) => {
- const map = { uncertified: "info", certified: "success", failed: "danger" };
- return map[status] || "";
+const handleTaxRateChange = () => {
+ if (form.totalAmount > 0) {
+ calculateTaxFromInclusive(form.totalAmount);
+ } else {
+ calculateTaxFromExclusive();
+ }
+};
+
+/** 鏍规嵁宸查�夊叆搴撳崟姹囨�诲惈绋庨噾棰濓紝鍙嶇畻涓嶅惈绋庨噾棰濅笌绋庨 */
+const syncInvoiceAmount = () => {
+ const selected = form.stockInRecordIds || [];
+ const sumFromOptions = inboundBatchOptions.value
+ .filter((opt) => selected.some((id) => isSameInboundId(id, opt.value)))
+ .reduce((acc, opt) => acc + (Number(opt.inboundAmount) || 0), 0);
+
+ let taxInclusiveSum = sumFromOptions;
+ if (taxInclusiveSum <= 0 && selected.length) {
+ taxInclusiveSum = inboundBatchList.value
+ .filter((row) => selected.some((id) => isSameInboundId(id, getInboundRowId(row))))
+ .reduce((acc, row) => acc + getInboundRowTaxInclusiveAmount(row), 0);
+ }
+
+ calculateTaxFromInclusive(taxInclusiveSum > 0 ? Number(taxInclusiveSum.toFixed(2)) : 0);
+};
+
+const inboundBatchDisplayText = computed(() => {
+ if (form.inboundBatches) return form.inboundBatches;
+ const ids = form.stockInRecordIds || [];
+ if (!ids.length) return "";
+ const labels = inboundBatchOptions.value
+ .filter((opt) => ids.some((id) => isSameInboundId(id, opt.value)))
+ .map((opt) => opt.label);
+ if (labels.length) return labels.join("銆�");
+ return ids.join("銆�");
+});
+
+const normalizeTableRow = (row) => ({
+ ...row,
+ invoiceNo: row.invoiceNumber ?? row.invoiceNo,
+ invoiceDate: row.issueDate ?? row.invoiceDate,
+ amount: row.taxExclusivelPrice ?? row.amount,
+ taxAmount: row.taxPrice ?? row.taxAmount,
+ totalAmount: row.taxInclusivePrice ?? row.totalAmount,
+ content: row.invoiceContent ?? row.content,
+ status: normalizeStatus(row.status),
+ stockInRecordIds: row.stockInRecordIds ?? "",
+ inboundBatches: formatInboundBatches(row.inboundBatches),
+});
+
+const toFormNumber = (val) => {
+ const n = Number(val);
+ return Number.isFinite(n) ? n : 0;
+};
+
+const resolveFormAmounts = (row) => {
+ let amount = toFormNumber(row.taxExclusivelPrice ?? row.amount);
+ let taxAmount = toFormNumber(row.taxPrice ?? row.taxAmount);
+ let totalAmount = toFormNumber(row.taxInclusivePrice ?? row.totalAmount);
+ const taxRate = toFormNumber(row.taxRate) || 13;
+
+ if (totalAmount > 0 && amount === 0 && taxAmount === 0) {
+ amount = Number((totalAmount / (1 + taxRate / 100)).toFixed(2));
+ taxAmount = Number((totalAmount - amount).toFixed(2));
+ } else if (totalAmount > 0 && amount > 0 && taxAmount === 0) {
+ taxAmount = Number((totalAmount - amount).toFixed(2));
+ } else if (amount > 0 && taxAmount === 0 && totalAmount === 0) {
+ taxAmount = Number((amount * taxRate / 100).toFixed(2));
+ totalAmount = Number((amount + taxAmount).toFixed(2));
+ } else if (amount > 0 && taxAmount > 0 && totalAmount === 0) {
+ totalAmount = Number((amount + taxAmount).toFixed(2));
+ }
+
+ return { amount, taxAmount, totalAmount };
+};
+
+const fillFormFromRow = (row) => {
+ const stockInRecordIds = parseStockInRecordIds(row.stockInRecordIds);
+ const { amount, taxAmount, totalAmount } = resolveFormAmounts(row);
+ Object.assign(form, {
+ invoiceNo: row.invoiceNo ?? row.invoiceNumber ?? "",
+ supplierId: row.supplierId,
+ invoiceDate: row.invoiceDate ?? row.issueDate ?? "",
+ invoiceType: row.invoiceType ?? "澧炲�肩◣涓撶敤鍙戠エ",
+ taxRate: row.taxRate ?? 13,
+ amount,
+ taxAmount,
+ totalAmount,
+ content: row.content ?? row.invoiceContent ?? "",
+ remark: row.remark ?? "",
+ stockInRecordIds,
+ inboundBatches: formatInboundBatches(row.inboundBatches),
+ storageAttachmentId: row.storageAttachmentId,
+ status: normalizeStatus(row.status),
+ });
+};
+
+const buildCancelPayload = (row) => ({
+ id: row.id,
+ invoiceNumber: row.invoiceNumber ?? row.invoiceNo,
+ taxRate: row.taxRate,
+ invoiceType: row.invoiceType,
+ issueDate: row.issueDate ?? row.invoiceDate,
+ taxExclusivelPrice: row.taxExclusivelPrice ?? row.amount,
+ taxPrice: row.taxPrice ?? row.taxAmount,
+ taxInclusivePrice: row.taxInclusivePrice ?? row.totalAmount,
+ remark: row.remark ?? "",
+ invoiceContent: row.invoiceContent ?? row.content,
+ supplierId: row.supplierId,
+ storageAttachmentId: row.storageAttachmentId,
+ stockInRecordIds: row.stockInRecordIds ?? "",
+ status: 1,
+});
+
+const buildSubmitPayload = () => ({
+ invoiceNumber: form.invoiceNo,
+ supplierId: form.supplierId,
+ issueDate: form.invoiceDate,
+ invoiceType: form.invoiceType,
+ taxRate: form.taxRate,
+ taxExclusivelPrice: form.amount,
+ taxPrice: form.taxAmount,
+ taxInclusivePrice: form.totalAmount,
+ invoiceContent: form.content,
+ remark: form.remark || "",
+ stockInRecordIds: (form.stockInRecordIds || []).join(","),
+ status: 0,
+ storageAttachmentId: form.storageAttachmentId,
+});
+
+const getSupplierList = () => {
+ getOptions().then((res) => {
+ if (res.code === 200) {
+ supplierList.value = res.data ?? [];
+ }
+ });
+};
+
+const appendFilterParams = (params) => {
+ if (filters.invoiceNumber) {
+ params.invoiceNumber = filters.invoiceNumber;
+ }
+ if (filters.supplierId) {
+ params.supplierId = filters.supplierId;
+ }
+ if (filters.dateRange?.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ if (filters.status !== "" && filters.status != null) {
+ params.status = filters.status;
+ }
+ return params;
+};
+
+const buildListParams = () =>
+ appendFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => appendFilterParams({});
+
+const handleExport = () => {
+ proxy.download(
+ "/accountPurchaseInvoice/exportAccountPurchaseInvoice",
+ buildExportParams(),
+ `杩涢」鍙戠エ_${Date.now()}.xlsx`
+ );
};
const getTableData = () => {
- let result = [...mockData];
- if (filters.invoiceCode) {
- result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode));
- }
- if (filters.invoiceNo) {
- result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo));
- }
- if (filters.supplierId) {
- result = result.filter(item => item.supplierId === filters.supplierId);
- }
- if (filters.certifyStatus) {
- result = result.filter(item => item.certifyStatus === filters.certifyStatus);
- }
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ tableLoading.value = true;
+ listPageAccountPurchaseInvoice(buildListParams())
+ .then((res) => {
+ if (res.code === 200) {
+ const records = res.data?.records ?? [];
+ dataList.value = records.map(normalizeTableRow);
+ pagination.total = res.data?.total ?? 0;
+ } else {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
-const resetFilters = () => {
- filters.invoiceCode = "";
- filters.invoiceNo = "";
- filters.supplierId = "";
- filters.certifyStatus = "";
+const onSearch = () => {
pagination.currentPage = 1;
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const resetFilters = () => {
+ filters.invoiceNumber = "";
+ filters.supplierId = "";
+ filters.dateRange = [];
+ filters.status = "";
+ pagination.currentPage = 1;
getTableData();
};
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
+};
+
+const closeDialog = () => {
+ dialogVisible.value = false;
+ isView.value = false;
+ inboundSelectVisible.value = false;
+};
+
+const resetForm = () => {
+ Object.assign(form, {
+ invoiceNo: "",
+ supplierId: "",
+ invoiceDate: new Date().toISOString().split("T")[0],
+ invoiceType: "澧炲�肩◣涓撶敤鍙戠エ",
+ taxRate: 13,
+ amount: 0,
+ taxAmount: 0,
+ totalAmount: 0,
+ content: "",
+ remark: "",
+ stockInRecordIds: [],
+ inboundBatches: "",
+ storageAttachmentId: undefined,
+ status: 0,
+ });
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
};
const add = () => {
- isEdit.value = false;
+ isView.value = false;
dialogTitle.value = "褰曞叆鍙戠エ";
- Object.assign(form, {
- invoiceCode: "",
- invoiceNo: "",
- supplierId: "",
- invoiceDate: new Date().toISOString().split('T')[0],
- amount: 0,
- taxRate: 13,
- taxAmount: 0,
- totalAmount: 0,
- certifyStatus: "uncertified",
- certifyDate: "",
- content: "",
- remark: "",
- });
- dialogVisible.value = true;
-};
-
-const edit = (row) => {
- isEdit.value = true;
- currentId.value = row.id;
- dialogTitle.value = "缂栬緫鍙戠エ";
- Object.assign(form, row);
+ resetForm();
dialogVisible.value = true;
};
const view = (row) => {
- ElMessage.info(`鏌ョ湅鍙戠エ: ${row.invoiceCode}-${row.invoiceNo}`);
+ isView.value = true;
+ dialogTitle.value = "鏌ョ湅鍙戠エ";
+ fillFormFromRow(row);
+ if (row.supplierId) {
+ loadInboundBatches(row.supplierId, true, false);
+ }
+ dialogVisible.value = true;
};
-const handleCertify = (row) => {
- ElMessageBox.confirm("纭璁よ瘉璇ュ彂绁ㄥ悧锛�", "鎻愮ず", {
- confirmButtonText: "纭",
+const handleCancel = (row) => {
+ ElMessageBox.confirm(`纭浣滃簾鍙戠エ銆�${row.invoiceNo ?? row.invoiceNumber}銆嶅悧锛焋, "浣滃簾纭", {
+ confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
- type: "info",
+ type: "warning",
}).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].certifyStatus = "certified";
- mockData[index].certifyDate = new Date().toISOString().split('T')[0];
- }
- ElMessage.success("璁よ瘉鎴愬姛");
- getTableData();
+ cancelAccountPurchaseInvoice(buildCancelPayload(row))
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("浣滃簾鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "浣滃簾澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("浣滃簾澶辫触");
+ });
});
};
-const handleBatchCertify = () => {
- ElMessageBox.confirm(`纭鎵归噺璁よ瘉閫変腑鐨� ${selectedRows.value.length} 寮犲彂绁ㄥ悧锛焋, "鎻愮ず", {
- confirmButtonText: "纭",
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎鍙戠エ銆�${row.invoiceNo ?? row.invoiceNumber}銆嶅悧锛焋, "鍒犻櫎纭", {
+ confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
- type: "info",
+ type: "warning",
}).then(() => {
- selectedRows.value.forEach(row => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1 && mockData[index].certifyStatus === "uncertified") {
- mockData[index].certifyStatus = "certified";
- mockData[index].certifyDate = new Date().toISOString().split('T')[0];
- }
- });
- ElMessage.success("鎵归噺璁よ瘉鎴愬姛");
- getTableData();
+ deleteAccountPurchaseInvoice([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
});
-};
-
-const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
};
const submitForm = () => {
- formRef.value.validate((valid) => {
- if (valid) {
- const supplier = supplierList.find(item => item.id === form.supplierId);
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ formRef.value?.validate((valid) => {
+ if (!valid) return;
+ submitLoading.value = true;
+ addAccountPurchaseInvoice(buildSubmitPayload())
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("褰曞叆鎴愬姛");
+ closeDialog();
+ pagination.currentPage = 1;
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "褰曞叆澶辫触");
}
- ElMessage.success("缂栬緫鎴愬姛");
- } else {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- mockData.push({ id: newId, ...form, supplierName: supplier?.name });
- ElMessage.success("褰曞叆鎴愬姛");
- }
- dialogVisible.value = false;
- getTableData();
- }
+ })
+ .catch(() => {
+ ElMessage.error("褰曞叆澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
});
};
+const ensureInboundOptionsForSelected = () => {
+ const ids = form.stockInRecordIds || [];
+ ids.forEach((id) => {
+ const exists = inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, id));
+ if (exists) return;
+ const fromList = inboundBatchList.value.find((row) => isSameInboundId(getInboundRowId(row), id));
+ if (fromList) {
+ const [option] = normalizeInboundBatchOptions([fromList]);
+ if (option) inboundBatchOptions.value.push(option);
+ return;
+ }
+ inboundBatchOptions.value.push({
+ label: String(id),
+ value: id,
+ inboundAmount: 0,
+ });
+ });
+};
+
+const restoreInboundTableSelection = () => {
+ nextTick(() => {
+ const table = inboundTableRef.value;
+ if (!table) return;
+ table.clearSelection();
+ const selectedIds = new Set((form.stockInRecordIds || []).map((id) => String(id)));
+ inboundBatchList.value.forEach((row) => {
+ const rowId = getInboundRowId(row);
+ if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) {
+ table.toggleRowSelection(row, true);
+ }
+ });
+ });
+};
+
+const loadInboundBatches = (supplierId, keepSelected = false, syncAmount = true) => {
+ if (!supplierId) {
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
+ if (!keepSelected) {
+ form.stockInRecordIds = [];
+ form.inboundBatches = "";
+ form.amount = 0;
+ form.taxAmount = 0;
+ form.totalAmount = 0;
+ }
+ return Promise.resolve();
+ }
+ inboundBatchLoading.value = true;
+ return getInboundBatchesBySupplier({ supplierId })
+ .then((res) => {
+ if (res.code === 200) {
+ const list = res.data?.records ?? res.data ?? [];
+ inboundBatchList.value = Array.isArray(list) ? list : [];
+ inboundBatchOptions.value = normalizeInboundBatchOptions(list);
+ } else {
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
+ }
+ })
+ .catch(() => {
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
+ })
+ .finally(() => {
+ inboundBatchLoading.value = false;
+ if (keepSelected) {
+ ensureInboundOptionsForSelected();
+ restoreInboundTableSelection();
+ if (syncAmount && !isView.value) {
+ syncInvoiceAmount();
+ }
+ }
+ });
+};
+
+const handleSupplierChange = (supplierId) => {
+ form.stockInRecordIds = [];
+ form.inboundBatches = "";
+ form.amount = 0;
+ form.taxAmount = 0;
+ form.totalAmount = 0;
+ loadInboundBatches(supplierId);
+};
+
+const handleInboundInputClick = () => {
+ if (isView.value) return;
+ openInboundSelectDialog();
+};
+
+const openInboundSelectDialog = () => {
+ if (!form.supplierId || isView.value) return;
+ inboundSelectVisible.value = true;
+ loadInboundBatches(form.supplierId, true).then(() => {
+ restoreInboundTableSelection();
+ });
+};
+
+const handleInboundDialogSelectionChange = (selection) => {
+ dialogInboundSelection.value = selection;
+};
+
+const confirmInboundSelection = () => {
+ if (dialogInboundSelection.value.length === 0) {
+ ElMessage.warning("璇疯嚦灏戦�夋嫨涓�鏉″叆搴撳崟");
+ return;
+ }
+ form.stockInRecordIds = dialogInboundSelection.value
+ .map((row) => getInboundRowId(row))
+ .filter((id) => id !== undefined && id !== null);
+ form.inboundBatches = dialogInboundSelection.value
+ .map((row) => row.inboundBatches ?? row.batchNo ?? "")
+ .filter(Boolean)
+ .join("銆�");
+ dialogInboundSelection.value.forEach((row) => {
+ const [option] = normalizeInboundBatchOptions([row]);
+ if (option && !inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, option.value))) {
+ inboundBatchOptions.value.push(option);
+ }
+ });
+ inboundSelectVisible.value = false;
+ syncInvoiceAmount();
+ formRef.value?.validateField("stockInRecordIds");
+};
+
+const handleInboundDialogClosed = () => {
+ dialogInboundSelection.value = [];
+};
+
onMounted(() => {
+ getSupplierList();
getTableData();
});
</script>
@@ -406,4 +938,8 @@
color: #67c23a;
font-weight: bold;
}
+
+.inbound-batch-input :deep(.el-input__wrapper) {
+ cursor: pointer;
+}
</style>
diff --git a/src/views/financialManagement/payable/payment.vue b/src/views/financialManagement/payable/payment.vue
index d4774fe..d762ae9 100644
--- a/src/views/financialManagement/payable/payment.vue
+++ b/src/views/financialManagement/payable/payment.vue
@@ -2,47 +2,60 @@
<div class="app-container">
<el-form :model="filters" :inline="true">
<el-form-item label="浠樻鍗曞彿:">
- <el-input v-model="filters.paymentCode" placeholder="璇疯緭鍏ヤ粯娆惧崟鍙�" clearable style="width: 200px;" />
+ <el-input v-model="filters.paymentNumber" placeholder="璇疯緭鍏ヤ粯娆惧崟鍙�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="渚涘簲鍟�:">
- <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable filterable style="width: 200px;">
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
</el-form-item>
<el-form-item label="浠樻鏂瑰紡:">
<el-select v-model="filters.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" clearable style="width: 150px;">
- <el-option label="閾惰杞处" value="bank_transfer" />
- <el-option label="鐜伴噾" value="cash" />
- <el-option label="鏀エ" value="check" />
- <el-option label="姹囩エ" value="draft" />
+ <el-option
+ v-for="item in checkout_payment"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
</el-select>
</el-form-item>
- <el-form-item label="鐘舵��:">
- <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
- <el-option label="寰呬粯娆�" value="pending" />
- <el-option label="宸插畬鎴�" value="completed" />
- <el-option label="宸插彇娑�" value="cancelled" />
- </el-select>
+ <el-form-item label="浠樻鏃ユ湡:">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ clearable
+ style="width: 240px;"
+ />
</el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
<div class="table_list">
<div class="actions">
<div>
- <el-statistic title="鏈湡浠樻鍚堣" :value="totalPaymentAmount" precision="2" prefix="楼" />
+ <el-statistic title="鏈〉浠樻鍚堣" :value="totalPaymentAmount" :precision="2" prefix="楼" />
</div>
<div>
- <el-button type="primary" @click="add" icon="Plus">鏂板浠樻</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ <el-button @click="handleExport" icon="Download">瀵煎嚭</el-button>
</div>
</div>
<PIMTable
rowKey="id"
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
size: pagination.pageSize,
@@ -56,105 +69,35 @@
<template #paymentMethod="{ row }">
<el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag>
</template>
- <template #status="{ row }">
- <el-tag :type="row.status === 'completed' ? 'success' : row.status === 'pending' ? 'warning' : 'info'">
- {{ row.status === 'completed' ? '宸插畬鎴�' : row.status === 'pending' ? '寰呬粯娆�' : '宸插彇娑�' }}
- </el-tag>
- </template>
<template #operation="{ row }">
- <el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
- <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
- <el-button type="success" link @click="handleComplete(row)" v-if="row.status === 'pending'">瀹屾垚</el-button>
- <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'pending'">鍙栨秷</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
-
- <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
- <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="浠樻鍗曞彿" prop="paymentCode">
- <el-input v-model="form.paymentCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鍏宠仈鐢宠鍗�" prop="applyCode">
- <el-select v-model="form.applyCode" placeholder="璇烽�夋嫨鍏宠仈鐢宠鍗�" style="width: 100%;" :disabled="isEdit">
- <el-option v-for="item in applyList" :key="item.applyCode" :label="item.applyCode" :value="item.applyCode" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="渚涘簲鍟�" prop="supplierId">
- <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="浠樻鏃ユ湡" prop="paymentDate">
- <el-date-picker v-model="form.paymentDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="浠樻閲戦" prop="amount">
- <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
- <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%;">
- <el-option label="閾惰杞处" value="bank_transfer" />
- <el-option label="鐜伴噾" value="cash" />
- <el-option label="鏀エ" value="check" />
- <el-option label="姹囩エ" value="draft" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="閾惰璐﹀彿" prop="bankAccount" v-if="form.paymentMethod === 'bank_transfer'">
- <el-input v-model="form.bankAccount" placeholder="璇疯緭鍏ラ摱琛岃处鍙�" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="寮�鎴疯" prop="bankName" v-if="form.paymentMethod === 'bank_transfer'">
- <el-input v-model="form.bankName" placeholder="璇疯緭鍏ュ紑鎴疯" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
- </template>
- </FormDialog>
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, computed } from "vue";
+import { ref, reactive, computed, onMounted, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
-import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
+import {
+ listPageAccountPurchasePayment,
+ deleteAccountPurchasePayment,
+} from "@/api/financialManagement/accountPurchasePayment.js";
defineOptions({
name: "浠樻鍗�",
});
+const { proxy } = getCurrentInstance();
+const { checkout_payment } = proxy.useDict("checkout_payment");
+
const filters = reactive({
- paymentCode: "",
+ paymentNumber: "",
supplierId: "",
paymentMethod: "",
- status: "",
+ dateRange: [],
});
const pagination = reactive({
@@ -164,200 +107,151 @@
});
const columns = [
- { label: "浠樻鍗曞彿", prop: "paymentCode", width: "150" },
- { label: "鍏宠仈鐢宠鍗�", prop: "applyCode", width: "150" },
+ { label: "浠樻鍗曞彿", prop: "paymentNumber", width: "150" },
+ { label: "鍏宠仈鐢宠鍗�", prop: "invoiceApplicationNo", width: "150" },
{ label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
{ label: "浠樻鏃ユ湡", prop: "paymentDate", width: "120" },
- { label: "浠樻閲戦", prop: "amount", slot: "amount" },
- { label: "浠樻鏂瑰紡", prop: "paymentMethod", slot: "paymentMethod" },
- { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "浠樻閲戦", prop: "amount", dataType: "slot", slot: "amount" },
+ { label: "浠樻鏂瑰紡", prop: "paymentMethod", dataType: "slot", slot: "paymentMethod", width: "120" },
{ label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "80", fixed: "right" },
];
const dataList = ref([]);
-const dialogVisible = ref(false);
-const dialogTitle = ref("");
-const formRef = ref(null);
-const isEdit = ref(false);
-const currentId = ref(null);
+const tableLoading = ref(false);
+const supplierList = ref([]);
-const supplierList = [
- { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
- { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
- { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
- { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
-];
-
-const applyList = [
- { applyCode: "FK2024001", supplierId: 1, amount: 5000 },
- { applyCode: "FK2024002", supplierId: 2, amount: 8000 },
- { applyCode: "FK2024003", supplierId: 3, amount: 3000 },
-];
-
-const form = reactive({
- paymentCode: "",
- applyCode: "",
- supplierId: "",
- paymentDate: "",
- amount: 0,
- paymentMethod: "bank_transfer",
- bankAccount: "",
- bankName: "",
- remark: "",
-});
-
-const rules = {
- applyCode: [{ required: true, message: "璇烽�夋嫨鍏宠仈鐢宠鍗�", trigger: "change" }],
- supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
- paymentDate: [{ required: true, message: "璇烽�夋嫨浠樻鏃ユ湡", trigger: "change" }],
- amount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
- paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
-};
-
-const mockData = [
- { id: 1, paymentCode: "FKD2024001", applyCode: "FK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", paymentDate: "2024-01-15", amount: 5000, paymentMethod: "bank_transfer", status: "completed", bankAccount: "6222021234567890123", bankName: "宸ュ晢閾惰", remark: "" },
- { id: 2, paymentCode: "FKD2024002", applyCode: "FK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", paymentDate: "2024-01-18", amount: 8000, paymentMethod: "bank_transfer", status: "pending", bankAccount: "6222029876543210987", bankName: "寤鸿閾惰", remark: "" },
- { id: 3, paymentCode: "FKD2024003", applyCode: "FK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", paymentDate: "2024-01-20", amount: 3000, paymentMethod: "cash", status: "completed", remark: "" },
-];
-
-const totalPaymentAmount = computed(() => {
- return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0);
-});
+const totalPaymentAmount = computed(() =>
+ dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0)
+);
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
-const getPaymentMethodLabel = (method) => {
- const map = {
- bank_transfer: "閾惰杞处",
- cash: "鐜伴噾",
- check: "鏀エ",
- draft: "姹囩エ",
- };
- return map[method] || method;
+const getPaymentMethodLabel = (value) => {
+ if (value === undefined || value === null || value === "") return "-";
+ const item = checkout_payment.value?.find((m) => String(m.value) === String(value));
+ return item?.label ?? value;
+};
+
+const normalizeTableRow = (row) => ({
+ ...row,
+ paymentNumber: row.paymentNumber ?? row.paymentCode,
+ invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
+ amount: row.paymentAmount ?? row.amount,
+ bankAccountNum: row.bankAccountNum ?? row.bankAccount ?? "",
+ bankAccountName: row.bankAccountName ?? row.bankName ?? "",
+});
+
+const getSupplierList = () => {
+ getOptions().then((res) => {
+ if (res.code === 200) {
+ supplierList.value = res.data ?? [];
+ }
+ });
+};
+
+const appendFilterParams = (params) => {
+ if (filters.paymentNumber) {
+ params.paymentNumber = filters.paymentNumber;
+ }
+ if (filters.supplierId) {
+ params.supplierId = filters.supplierId;
+ }
+ if (filters.paymentMethod) {
+ params.paymentMethod = filters.paymentMethod;
+ }
+ if (filters.dateRange?.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ return params;
+};
+
+const buildListParams = () =>
+ appendFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => appendFilterParams({});
+
+const handleExport = () => {
+ proxy.download(
+ "/accountPurchasePayment/exportAccountPurchasePayment",
+ buildExportParams(),
+ `浠樻鍗昣${Date.now()}.xlsx`
+ );
};
const getTableData = () => {
- let result = [...mockData];
- if (filters.paymentCode) {
- result = result.filter(item => item.paymentCode.includes(filters.paymentCode));
- }
- if (filters.supplierId) {
- result = result.filter(item => item.supplierId === filters.supplierId);
- }
- if (filters.paymentMethod) {
- result = result.filter(item => item.paymentMethod === filters.paymentMethod);
- }
- if (filters.status) {
- result = result.filter(item => item.status === filters.status);
- }
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ tableLoading.value = true;
+ listPageAccountPurchasePayment(buildListParams())
+ .then((res) => {
+ if (res.code === 200) {
+ dataList.value = (res.data?.records ?? []).map(normalizeTableRow);
+ pagination.total = res.data?.total ?? 0;
+ } else {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
-const resetFilters = () => {
- filters.paymentCode = "";
- filters.supplierId = "";
- filters.paymentMethod = "";
- filters.status = "";
+const onSearch = () => {
pagination.currentPage = 1;
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const resetFilters = () => {
+ filters.paymentNumber = "";
+ filters.supplierId = "";
+ filters.paymentMethod = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
getTableData();
};
-const add = () => {
- isEdit.value = false;
- dialogTitle.value = "鏂板浠樻";
- Object.assign(form, {
- paymentCode: "FKD" + Date.now().toString().slice(-8),
- applyCode: "",
- supplierId: "",
- paymentDate: new Date().toISOString().split('T')[0],
- amount: 0,
- paymentMethod: "bank_transfer",
- bankAccount: "",
- bankName: "",
- remark: "",
- });
- dialogVisible.value = true;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
};
-const edit = (row) => {
- isEdit.value = true;
- currentId.value = row.id;
- dialogTitle.value = "缂栬緫浠樻";
- Object.assign(form, row);
- dialogVisible.value = true;
-};
-
-const view = (row) => {
- ElMessage.info(`鏌ョ湅浠樻鍗�: ${row.paymentCode}`);
-};
-
-const handleComplete = (row) => {
- ElMessageBox.confirm("纭璇ヤ粯娆惧崟宸插畬鎴愬悧锛�", "鎻愮ず", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "info",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "completed";
- }
- ElMessage.success("浠樻瀹屾垚");
- getTableData();
- });
-};
-
-const handleCancel = (row) => {
- ElMessageBox.confirm("纭鍙栨秷璇ヤ粯娆惧崟鍚楋紵", "鎻愮ず", {
- confirmButtonText: "纭",
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎浠樻鍗曘��${row.paymentNumber}銆嶅悧锛焋, "鎻愮ず", {
+ confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
}).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "cancelled";
- }
- ElMessage.success("宸插彇娑�");
- getTableData();
- });
-};
-
-const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
-};
-
-const submitForm = () => {
- formRef.value.validate((valid) => {
- if (valid) {
- const supplier = supplierList.find(item => item.id === form.supplierId);
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ deleteAccountPurchasePayment([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
}
- ElMessage.success("缂栬緫鎴愬姛");
- } else {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
- ElMessage.success("鏂板鎴愬姛");
- }
- dialogVisible.value = false;
- getTableData();
- }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
});
};
onMounted(() => {
+ getSupplierList();
getTableData();
});
</script>
diff --git a/src/views/financialManagement/payable/paymentApply.vue b/src/views/financialManagement/payable/paymentApply.vue
index fb23db3..3937e96 100644
--- a/src/views/financialManagement/payable/paymentApply.vue
+++ b/src/views/financialManagement/payable/paymentApply.vue
@@ -2,23 +2,40 @@
<div class="app-container">
<el-form :model="filters" :inline="true">
<el-form-item label="鐢宠鍗曞彿:">
- <el-input v-model="filters.applyCode" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable style="width: 200px;" />
+ <el-input v-model="filters.invoiceApplicationNo" placeholder="璇疯緭鍏ョ敵璇峰崟鍙�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="渚涘簲鍟�:">
- <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable filterable style="width: 200px;">
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
</el-form-item>
- <el-form-item label="鐘舵��:">
+ <el-form-item label="瀹℃牳鐘舵��:">
<el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
- <el-option label="寰呭鎵�" value="pending" />
- <el-option label="宸插鎵�" value="approved" />
- <el-option label="宸查┏鍥�" value="rejected" />
- <el-option label="宸蹭粯娆�" value="paid" />
+ <el-option label="寰呭鏍�" :value="0" />
+ <el-option label="瀹℃牳閫氳繃" :value="1" />
+ <el-option label="瀹℃牳涓嶉�氳繃" :value="2" />
</el-select>
+ </el-form-item>
+ <el-form-item label="鐢宠鏃ユ湡:">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ clearable
+ style="width: 240px;"
+ />
</el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -27,20 +44,19 @@
<div></div>
<div>
<el-button type="primary" @click="add" icon="Plus">鏂板鐢宠</el-button>
- <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">鎵归噺鐢宠</el-button>
+ <el-button @click="handleExport" icon="Download">瀵煎嚭</el-button>
</div>
</div>
<PIMTable
rowKey="id"
- isSelection
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
size: pagination.pageSize,
total: pagination.total,
}"
- @selection-change="handleSelectionChange"
@pagination="changePage"
>
<template #amount="{ row }">
@@ -54,90 +70,291 @@
</template>
<template #operation="{ row }">
<el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
- <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
- <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">瀹℃壒</el-button>
+ <el-button type="primary" link @click="edit(row)" v-if="isPendingStatus(row.status)">缂栬緫</el-button>
+ <el-button type="success" link @click="handleAudit(row)" v-if="isPendingStatus(row.status)">瀹℃牳</el-button>
+ <el-button type="warning" link @click="openPaymentDialog(row)" v-if="isApprovedStatus(row.status)">浠樻</el-button>
+ <el-button type="danger" link @click="handleDelete(row)" v-if="isPendingStatus(row.status)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
- <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <FormDialog
+ :title="dialogTitle"
+ v-model="dialogVisible"
+ width="800px"
+ :operation-type="isView ? 'detail' : ''"
+ @confirm="submitForm"
+ @cancel="closeDialog"
+ >
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+ <el-row v-if="isView" :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀹℃牳鐘舵��">
+ <el-tag :type="getStatusType(form.status)">{{ getStatusLabel(form.status) }}</el-tag>
+ </el-form-item>
+ </el-col>
+ </el-row>
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鐢宠鍗曞彿" prop="applyCode">
- <el-input v-model="form.applyCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ <el-form-item label="鐢宠鍗曞彿" prop="invoiceApplicationNo">
+ <el-input v-model="form.invoiceApplicationNo" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="渚涘簲鍟�" prop="supplierId">
- <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" :disabled="isEdit">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select
+ v-model="form.supplierId"
+ placeholder="璇烽�夋嫨渚涘簲鍟�"
+ style="width: 100%;"
+ filterable
+ :disabled="isEdit || isView"
+ @change="handleSupplierChange"
+ >
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="浠樻閲戦" prop="amount">
- <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ <el-form-item label="鍏宠仈鍏ュ簱鍗�" prop="stockInRecordIds">
+ <el-input
+ :model-value="inboundBatchDisplayText"
+ placeholder="璇峰厛閫夋嫨渚涘簲鍟�"
+ readonly
+ :disabled="!form.supplierId || isEdit || isView"
+ class="inbound-batch-input"
+ @click="handleInboundInputClick"
+ >
+ <template v-if="!isEdit && !isView" #append>
+ <el-button
+ :disabled="!form.supplierId"
+ :loading="inboundBatchLoading"
+ @click.stop="openInboundSelectDialog"
+ >
+ 閫夋嫨
+ </el-button>
+ </template>
+ </el-input>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐢宠鏃ユ湡" prop="applyDate">
+ <el-date-picker
+ v-model="form.applyDate"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%;"
+ :disabled="isView"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻閲戦" prop="paymentAmount">
+ <el-input-number
+ v-model="form.paymentAmount"
+ :min="0"
+ :precision="2"
+ style="width: 100%;"
+ :disabled="isView"
+ placeholder="鏍规嵁鍏ュ簱鍗曡嚜鍔ㄦ眹鎬伙紝鍙慨鏀�"
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
- <el-select v-model="form.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%;">
- <el-option label="閾惰杞处" value="bank_transfer" />
- <el-option label="鐜伴噾" value="cash" />
- <el-option label="鏀エ" value="check" />
- <el-option label="姹囩エ" value="draft" />
+ <el-select
+ v-model="form.paymentMethod"
+ placeholder="璇烽�夋嫨浠樻鏂瑰紡"
+ style="width: 100%;"
+ :disabled="isView"
+ >
+ <el-option
+ v-for="item in checkout_payment"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
</el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="浠樻浜嬬敱" prop="paymentContent">
+ <el-input
+ v-model="form.paymentContent"
+ type="textarea"
+ :rows="3"
+ placeholder="璇疯緭鍏ヤ粯娆句簨鐢�"
+ :disabled="isView"
+ />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" :disabled="isView" />
+ </el-form-item>
+ </el-form>
+ <template v-if="!isView" #footer>
+ <el-button type="primary" :loading="submitLoading" @click="submitForm">纭畾</el-button>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
+ </template>
+ </FormDialog>
+
+ <FormDialog
+ title="浠樻"
+ v-model="paymentDialogVisible"
+ width="800px"
+ @confirm="submitPayment"
+ @cancel="paymentDialogVisible = false"
+ >
+ <el-form :model="paymentForm" :rules="paymentRules" ref="paymentFormRef" label-width="120px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻鍗曞彿" prop="paymentNumber">
+ <el-input v-model="paymentForm.paymentNumber" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鐢宠鍗�" prop="invoiceApplicationNo">
+ <el-input v-model="paymentForm.invoiceApplicationNo" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鐢宠鏃ユ湡" prop="applyDate">
- <el-date-picker v-model="form.applyDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ <el-form-item label="渚涘簲鍟�">
+ <el-input v-model="paymentForm.supplierName" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="鏈熸湜浠樻鏃ユ湡" prop="expectedDate">
- <el-date-picker v-model="form.expectedDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ <el-form-item label="浠樻鏃ユ湡" prop="paymentDate">
+ <el-date-picker
+ v-model="paymentForm.paymentDate"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%;"
+ />
</el-form-item>
</el-col>
</el-row>
- <el-form-item label="鍏宠仈鍏ュ簱鍗�" prop="relatedDocs">
- <el-select v-model="form.relatedDocs" multiple placeholder="璇烽�夋嫨鍏宠仈鍏ュ簱鍗�" style="width: 100%;">
- <el-option v-for="item in inList" :key="item.inCode" :label="item.inCode" :value="item.inCode" />
- </el-select>
- </el-form-item>
- <el-form-item label="浠樻浜嬬敱" prop="reason">
- <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="璇疯緭鍏ヤ粯娆句簨鐢�" />
- </el-form-item>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="浠樻閲戦" prop="paymentAmount">
+ <el-input-number
+ v-model="paymentForm.paymentAmount"
+ :min="0"
+ :precision="2"
+ style="width: 100%;"
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-select v-model="paymentForm.paymentMethod" placeholder="璇烽�夋嫨浠樻鏂瑰紡" style="width: 100%;">
+ <el-option
+ v-for="item in checkout_payment"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row v-if="isBankTransferPayment(paymentForm.paymentMethod)" :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閾惰璐﹀彿" prop="bankAccount">
+ <el-input v-model="paymentForm.bankAccount" placeholder="閾惰璐﹀彿" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="寮�鎴疯" prop="bankName">
+ <el-input v-model="paymentForm.bankName" placeholder="寮�鎴疯" />
+ </el-form-item>
+ </el-col>
+ </el-row>
<el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ <el-input v-model="paymentForm.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
</el-form-item>
</el-form>
<template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="paymentSubmitLoading" @click="submitPayment">纭畾</el-button>
+ <el-button @click="paymentDialogVisible = false">鍙栨秷</el-button>
</template>
</FormDialog>
+
+ <el-dialog
+ v-model="inboundSelectVisible"
+ title="閫夋嫨鍏ュ簱鍗曞彿"
+ width="1100px"
+ append-to-body
+ destroy-on-close
+ :close-on-click-modal="false"
+ @closed="handleInboundDialogClosed"
+ >
+ <el-table
+ ref="inboundTableRef"
+ v-loading="inboundBatchLoading"
+ :data="inboundBatchList"
+ row-key="id"
+ border
+ stripe
+ max-height="480"
+ @selection-change="handleInboundDialogSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column prop="inboundBatches" label="鍏ュ簱鍗曞彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="supplierName" label="渚涘簲鍟�" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="specificationModel" label="瑙勬牸鍨嬪彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="purchaseContractNumber" label="閲囪喘璁㈠崟鍙�" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="inboundDate" label="鍏ュ簱鏃ユ湡" width="110" align="center" />
+ <el-table-column prop="inboundAmount" label="鍏ュ簱閲戦(鍚◣)" width="120" align="right">
+ <template #default="{ row }">楼{{ formatMoney(getInboundRowTaxInclusiveAmount(row)) }}</template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <el-button type="primary" @click="confirmInboundSelection">纭畾</el-button>
+ <el-button @click="inboundSelectVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
-import { ref, reactive, onMounted } from "vue";
+import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
+import {
+ getInboundBatchesBySupplier,
+ addAccountPaymentApplication,
+ listPageAccountPaymentApplication,
+ updateAccountPaymentApplication,
+ auditAccountPaymentApplication,
+ deleteAccountPaymentApplication,
+} from "@/api/financialManagement/accountPaymentApplication.js";
+import { addAccountPurchasePayment } from "@/api/financialManagement/accountPurchasePayment.js";
defineOptions({
name: "浠樻鐢宠",
});
+const { proxy } = getCurrentInstance();
+const { checkout_payment } = proxy.useDict("checkout_payment");
+
const filters = reactive({
- applyCode: "",
+ invoiceApplicationNo: "",
supplierId: "",
status: "",
+ dateRange: [],
});
const pagination = reactive({
@@ -149,199 +366,634 @@
const columns = [
{ label: "鐢宠鍗曞彿", prop: "applyCode", width: "150" },
{ label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
- { label: "浠樻閲戦", prop: "amount", slot: "amount" },
- { label: "浠樻鏂瑰紡", prop: "paymentMethod", slot: "paymentMethod" },
+ { label: "浠樻閲戦", prop: "amount", dataType: "slot", slot: "amount" },
+ { label: "浠樻鏂瑰紡", prop: "paymentMethod", dataType: "slot", slot: "paymentMethod", width: "120" },
{ label: "鐢宠鏃ユ湡", prop: "applyDate", width: "120" },
- { label: "鏈熸湜浠樻鏃�", prop: "expectedDate", width: "120" },
- { label: "鐘舵��", prop: "status", slot: "status" },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "200", fixed: "right" },
+ { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status", width: "100" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "260", fixed: "right" },
];
const dataList = ref([]);
-const selectedRows = ref([]);
+const tableLoading = ref(false);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
+const isView = ref(false);
+const submitLoading = ref(false);
const currentId = ref(null);
+const supplierList = ref([]);
-const supplierList = [
- { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
- { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
- { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
- { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
-];
+const inboundBatchList = ref([]);
+const inboundBatchOptions = ref([]);
+const inboundBatchLoading = ref(false);
+const inboundSelectVisible = ref(false);
+const inboundTableRef = ref(null);
+const dialogInboundSelection = ref([]);
-const inList = [
- { inCode: "RK2024001", supplierId: 1 },
- { inCode: "RK2024002", supplierId: 2 },
- { inCode: "RK2024003", supplierId: 3 },
-];
+const paymentDialogVisible = ref(false);
+const paymentFormRef = ref(null);
+const paymentSubmitLoading = ref(false);
+
+const paymentForm = reactive({
+ paymentNumber: "",
+ invoiceApplicationNo: "",
+ supplierName: "",
+ supplierId: "",
+ accountPaymentApplicationId: null,
+ paymentDate: "",
+ paymentAmount: 0,
+ paymentMethod: "",
+ bankAccount: "",
+ bankName: "",
+ remark: "",
+});
+
+const paymentRules = {
+ paymentDate: [{ required: true, message: "璇烽�夋嫨浠樻鏃ユ湡", trigger: "change" }],
+ paymentAmount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
+ paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
+};
+
+const STATUS_LABEL_MAP = { 0: "寰呭鏍�", 1: "瀹℃牳閫氳繃", 2: "瀹℃牳涓嶉�氳繃" };
+const STATUS_TYPE_MAP = { 0: "warning", 1: "success", 2: "danger" };
const form = reactive({
- applyCode: "",
+ invoiceApplicationNo: "",
supplierId: "",
- amount: 0,
- paymentMethod: "bank_transfer",
+ paymentAmount: 0,
+ paymentMethod: "",
applyDate: "",
- expectedDate: "",
- relatedDocs: [],
- reason: "",
+ paymentContent: "",
remark: "",
+ stockInRecordIds: [],
+ inboundBatches: "",
+ status: 0,
});
const rules = {
supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
- amount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
+ stockInRecordIds: [{ required: true, type: "array", min: 1, message: "璇烽�夋嫨鍏宠仈鍏ュ簱鍗�", trigger: "change" }],
+ paymentAmount: [{ required: true, message: "璇疯緭鍏ヤ粯娆鹃噾棰�", trigger: "blur" }],
paymentMethod: [{ required: true, message: "璇烽�夋嫨浠樻鏂瑰紡", trigger: "change" }],
applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
- expectedDate: [{ required: true, message: "璇烽�夋嫨鏈熸湜浠樻鏃ユ湡", trigger: "change" }],
};
-
-const mockData = [
- { id: 1, applyCode: "FK2024001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", amount: 5000, paymentMethod: "bank_transfer", applyDate: "2024-01-12", expectedDate: "2024-01-15", status: "pending", relatedDocs: ["RK2024001"], reason: "鏀粯鍘熸潗鏂欒揣娆�", remark: "" },
- { id: 2, applyCode: "FK2024002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", amount: 8000, paymentMethod: "bank_transfer", applyDate: "2024-01-14", expectedDate: "2024-01-18", status: "approved", relatedDocs: ["RK2024002"], reason: "鏀粯鐢靛瓙鍏冨櫒浠惰揣娆�", remark: "" },
- { id: 3, applyCode: "FK2024003", supplierId: 3, supplierName: "骞垮窞鍖呰鏉愭枡鍘�", amount: 3000, paymentMethod: "cash", applyDate: "2024-01-16", expectedDate: "2024-01-20", status: "paid", relatedDocs: ["RK2024003"], reason: "鏀粯鍖呰鏉愭枡璐ф", remark: "" },
-];
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
-const getPaymentMethodLabel = (method) => {
- const map = {
- bank_transfer: "閾惰杞处",
- cash: "鐜伴噾",
- check: "鏀エ",
- draft: "姹囩エ",
+const normalizeStatus = (status) => {
+ if (status === undefined || status === null || status === "") return 0;
+ const num = Number(status);
+ return Number.isNaN(num) ? 0 : num;
+};
+
+const isPendingStatus = (status) => normalizeStatus(status) === 0;
+
+const isApprovedStatus = (status) => normalizeStatus(status) === 1;
+
+const isBankTransferPayment = (method) => {
+ if (method === undefined || method === null || method === "") return false;
+ const item = checkout_payment.value?.find((m) => String(m.value) === String(method));
+ if (item?.label?.includes("閾惰")) return true;
+ return String(method) === "bank_transfer" || String(method).toLowerCase().includes("bank");
+};
+
+const getStatusLabel = (status) => STATUS_LABEL_MAP[normalizeStatus(status)] ?? "寰呭鏍�";
+
+const getStatusType = (status) => STATUS_TYPE_MAP[normalizeStatus(status)] ?? "warning";
+
+const getPaymentMethodLabel = (value) => {
+ if (value === undefined || value === null || value === "") return "-";
+ const item = checkout_payment.value?.find((m) => String(m.value) === String(value));
+ return item?.label ?? value;
+};
+
+const getDefaultPaymentMethod = () => checkout_payment.value?.[0]?.value ?? "";
+
+const parseStockInRecordIds = (value) => {
+ if (!value) return [];
+ if (Array.isArray(value)) return value;
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .map((s) => (/^\d+$/.test(s) ? Number(s) : s));
+};
+
+const formatInboundBatches = (value) => {
+ if (value === undefined || value === null || value === "") return "";
+ if (Array.isArray(value)) return value.filter(Boolean).join("銆�");
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .join("銆�");
+};
+
+const isSameInboundId = (a, b) => String(a) === String(b);
+
+const getInboundRowId = (row) => row?.id ?? row?.stockInRecordId;
+
+const getInboundRowTaxInclusiveAmount = (row) =>
+ Number(row?.inboundAmount ?? row?.taxInclusivePrice ?? row?.totalAmount ?? row?.amount ?? 0);
+
+const normalizeInboundBatchOptions = (data) => {
+ const list = Array.isArray(data) ? data : [];
+ return list.map((item, index) => {
+ const label =
+ item.inboundBatches ?? item.batchNo ?? item.inboundNo ?? `鍏ュ簱鍗�${index + 1}`;
+ const value = item.id ?? item.stockInRecordId ?? label;
+ return {
+ label: String(label),
+ value,
+ inboundAmount: getInboundRowTaxInclusiveAmount(item),
+ };
+ });
+};
+
+const syncPaymentAmount = () => {
+ const selected = form.stockInRecordIds || [];
+ let sum = inboundBatchOptions.value
+ .filter((opt) => selected.some((id) => isSameInboundId(id, opt.value)))
+ .reduce((acc, opt) => acc + (Number(opt.inboundAmount) || 0), 0);
+
+ if (sum <= 0 && selected.length) {
+ sum = inboundBatchList.value
+ .filter((row) => selected.some((id) => isSameInboundId(id, getInboundRowId(row))))
+ .reduce((acc, row) => acc + getInboundRowTaxInclusiveAmount(row), 0);
+ }
+
+ form.paymentAmount = sum > 0 ? Number(sum.toFixed(2)) : 0;
+};
+
+const inboundBatchDisplayText = computed(() => {
+ if (form.inboundBatches) return form.inboundBatches;
+ const ids = form.stockInRecordIds || [];
+ if (!ids.length) return "";
+ const labels = inboundBatchOptions.value
+ .filter((opt) => ids.some((id) => isSameInboundId(id, opt.value)))
+ .map((opt) => opt.label);
+ if (labels.length) return labels.join("銆�");
+ return ids.join("銆�");
+});
+
+const normalizeTableRow = (row) => ({
+ ...row,
+ applyCode: row.invoiceApplicationNo ?? row.applyCode,
+ amount: row.paymentAmount ?? row.amount,
+ reason: row.paymentContent ?? row.reason,
+ status: normalizeStatus(row.status),
+ stockInRecordIds: row.stockInRecordIds ?? "",
+ inboundBatches: formatInboundBatches(row.inboundBatches),
+});
+
+const fillFormFromRow = (row) => {
+ const stockInRecordIds = parseStockInRecordIds(row.stockInRecordIds);
+ Object.assign(form, {
+ invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
+ supplierId: row.supplierId,
+ paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0),
+ paymentMethod: row.paymentMethod ?? getDefaultPaymentMethod(),
+ applyDate: row.applyDate ?? "",
+ paymentContent: row.paymentContent ?? row.reason ?? "",
+ remark: row.remark ?? "",
+ stockInRecordIds,
+ inboundBatches: formatInboundBatches(row.inboundBatches),
+ status: normalizeStatus(row.status),
+ });
+};
+
+const buildPayloadFromRow = (row, statusOverride) => ({
+ id: row.id,
+ supplierId: row.supplierId,
+ stockInRecordIds:
+ typeof row.stockInRecordIds === "string"
+ ? row.stockInRecordIds
+ : (row.stockInRecordIds || []).join(","),
+ invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
+ paymentMethod: row.paymentMethod,
+ paymentContent: row.paymentContent ?? row.reason ?? "",
+ applyDate: row.applyDate,
+ remark: row.remark ?? "",
+ status: statusOverride !== undefined ? statusOverride : normalizeStatus(row.status),
+ paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0),
+});
+
+const buildSubmitPayload = (forUpdate = false) => {
+ const payload = {
+ supplierId: form.supplierId,
+ stockInRecordIds: (form.stockInRecordIds || []).join(","),
+ invoiceApplicationNo: form.invoiceApplicationNo || "",
+ paymentMethod: form.paymentMethod,
+ paymentContent: form.paymentContent || "",
+ applyDate: form.applyDate,
+ remark: form.remark || "",
+ status: 0,
+ paymentAmount: form.paymentAmount,
};
- return map[method] || method;
+ if (forUpdate) {
+ payload.id = currentId.value;
+ }
+ return payload;
};
-const getStatusLabel = (status) => {
- const map = { pending: "寰呭鎵�", approved: "宸插鎵�", rejected: "宸查┏鍥�", paid: "宸蹭粯娆�" };
- return map[status] || status;
+const getSupplierList = () => {
+ getOptions().then((res) => {
+ if (res.code === 200) {
+ supplierList.value = res.data ?? [];
+ }
+ });
};
-const getStatusType = (status) => {
- const map = { pending: "warning", approved: "success", rejected: "danger", paid: "primary" };
- return map[status] || "";
+const appendFilterParams = (params) => {
+ if (filters.invoiceApplicationNo) {
+ params.invoiceApplicationNo = filters.invoiceApplicationNo;
+ }
+ if (filters.supplierId) {
+ params.supplierId = filters.supplierId;
+ }
+ if (filters.status !== "" && filters.status != null) {
+ params.status = filters.status;
+ }
+ if (filters.dateRange?.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ return params;
+};
+
+const buildListParams = () =>
+ appendFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => appendFilterParams({});
+
+const handleExport = () => {
+ proxy.download(
+ "/accountPaymentApplication/exportAccountPaymentApplication",
+ buildExportParams(),
+ `浠樻鐢宠_${Date.now()}.xlsx`
+ );
};
const getTableData = () => {
- let result = [...mockData];
- if (filters.applyCode) {
- result = result.filter(item => item.applyCode.includes(filters.applyCode));
- }
- if (filters.supplierId) {
- result = result.filter(item => item.supplierId === filters.supplierId);
- }
- if (filters.status) {
- result = result.filter(item => item.status === filters.status);
- }
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ tableLoading.value = true;
+ listPageAccountPaymentApplication(buildListParams())
+ .then((res) => {
+ if (res.code === 200) {
+ dataList.value = (res.data?.records ?? []).map(normalizeTableRow);
+ pagination.total = res.data?.total ?? 0;
+ } else {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
-const resetFilters = () => {
- filters.applyCode = "";
- filters.supplierId = "";
- filters.status = "";
+const onSearch = () => {
pagination.currentPage = 1;
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const resetFilters = () => {
+ filters.invoiceApplicationNo = "";
+ filters.supplierId = "";
+ filters.status = "";
+ filters.dateRange = [];
+ pagination.currentPage = 1;
getTableData();
};
-const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
+ getTableData();
+};
+
+const closeDialog = () => {
+ dialogVisible.value = false;
+ isView.value = false;
+ isEdit.value = false;
+ inboundSelectVisible.value = false;
+};
+
+const resetForm = () => {
+ Object.assign(form, {
+ invoiceApplicationNo: "",
+ supplierId: "",
+ paymentAmount: 0,
+ paymentMethod: getDefaultPaymentMethod(),
+ applyDate: new Date().toISOString().split("T")[0],
+ paymentContent: "",
+ remark: "",
+ stockInRecordIds: [],
+ inboundBatches: "",
+ status: 0,
+ });
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
};
const add = () => {
isEdit.value = false;
+ isView.value = false;
dialogTitle.value = "鏂板浠樻鐢宠";
- Object.assign(form, {
- applyCode: "FK" + Date.now().toString().slice(-8),
- supplierId: "",
- amount: 0,
- paymentMethod: "bank_transfer",
- applyDate: new Date().toISOString().split('T')[0],
- expectedDate: "",
- relatedDocs: [],
- reason: "",
- remark: "",
- });
+ resetForm();
dialogVisible.value = true;
};
const edit = (row) => {
isEdit.value = true;
+ isView.value = false;
currentId.value = row.id;
dialogTitle.value = "缂栬緫浠樻鐢宠";
- Object.assign(form, row);
+ fillFormFromRow(row);
dialogVisible.value = true;
};
const view = (row) => {
- ElMessage.info(`鏌ョ湅鐢宠鍗�: ${row.applyCode}`);
+ isView.value = true;
+ isEdit.value = false;
+ dialogTitle.value = "鏌ョ湅浠樻鐢宠";
+ fillFormFromRow(row);
+ if (row.supplierId) {
+ loadInboundBatches(row.supplierId, true, false);
+ }
+ dialogVisible.value = true;
+};
+
+const submitAudit = (row, status) => {
+ auditAccountPaymentApplication(buildPayloadFromRow(row, status))
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success(status === 1 ? "瀹℃牳閫氳繃" : "瀹℃牳涓嶉�氳繃");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "瀹℃牳澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("瀹℃牳澶辫触");
+ });
};
const handleAudit = (row) => {
- ElMessageBox.confirm("纭瀹℃壒閫氳繃璇ヤ粯娆剧敵璇峰悧锛�", "鎻愮ず", {
- confirmButtonText: "閫氳繃",
- cancelButtonText: "椹冲洖",
+ ElMessageBox.confirm("璇烽�夋嫨瀹℃牳缁撴灉", "浠樻鐢宠瀹℃牳", {
+ confirmButtonText: "瀹℃牳閫氳繃",
+ cancelButtonText: "瀹℃牳涓嶉�氳繃",
distinguishCancelAndClose: true,
type: "warning",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "approved";
- }
- ElMessage.success("瀹℃壒閫氳繃");
- getTableData();
- }).catch((action) => {
- if (action === "cancel") {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "rejected";
+ })
+ .then(() => {
+ submitAudit(row, 1);
+ })
+ .catch((action) => {
+ if (action === "cancel") {
+ submitAudit(row, 2);
}
- ElMessage.warning("宸查┏鍥�");
- getTableData();
- }
+ });
+};
+
+const openPaymentDialog = (row) => {
+ Object.assign(paymentForm, {
+ paymentNumber: "",
+ invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
+ supplierName: row.supplierName ?? "",
+ supplierId: row.supplierId,
+ accountPaymentApplicationId: row.id,
+ paymentDate: new Date().toISOString().split("T")[0],
+ paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0),
+ paymentMethod: row.paymentMethod ?? getDefaultPaymentMethod(),
+ bankAccount: row.bankAccountNum ?? row.bankAccount ?? "",
+ bankName: row.bankAccountName ?? row.bankName ?? "",
+ remark: "",
+ });
+ paymentDialogVisible.value = true;
+ nextTick(() => {
+ paymentFormRef.value?.clearValidate();
});
};
-const handleBatchApply = () => {
- ElMessage.success(`鎵归噺鐢宠 ${selectedRows.value.length} 鏉¤褰昤);
+const submitPayment = () => {
+ paymentFormRef.value?.validate((valid) => {
+ if (!valid) return;
+ paymentSubmitLoading.value = true;
+ addAccountPurchasePayment({
+ accountPaymentApplicationId: paymentForm.accountPaymentApplicationId,
+ supplierId: paymentForm.supplierId,
+ paymentDate: paymentForm.paymentDate,
+ paymentMethod: paymentForm.paymentMethod,
+ paymentAmount: paymentForm.paymentAmount,
+ paymentNumber: paymentForm.paymentNumber || "",
+ remark: paymentForm.remark || "",
+ })
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("浠樻鎴愬姛");
+ paymentDialogVisible.value = false;
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "浠樻澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("浠樻澶辫触");
+ })
+ .finally(() => {
+ paymentSubmitLoading.value = false;
+ });
+ });
+};
+
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎鐢宠鍗曘��${row.applyCode ?? row.invoiceApplicationNo}銆嶅悧锛焋, "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ deleteAccountPaymentApplication([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ });
};
const submitForm = () => {
- formRef.value.validate((valid) => {
- if (valid) {
- const supplier = supplierList.find(item => item.id === form.supplierId);
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name };
+ formRef.value?.validate((valid) => {
+ if (!valid) return;
+ submitLoading.value = true;
+ const request = isEdit.value
+ ? updateAccountPaymentApplication(buildSubmitPayload(true))
+ : addAccountPaymentApplication(buildSubmitPayload(false));
+
+ request
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success(isEdit.value ? "缂栬緫鎴愬姛" : "鏂板鎴愬姛");
+ closeDialog();
+ pagination.currentPage = 1;
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "淇濆瓨澶辫触");
}
- ElMessage.success("缂栬緫鎴愬姛");
- } else {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" });
- ElMessage.success("鏂板鎴愬姛");
- }
- dialogVisible.value = false;
- getTableData();
- }
+ })
+ .catch(() => {
+ ElMessage.error("淇濆瓨澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
});
};
+const ensureInboundOptionsForSelected = () => {
+ const ids = form.stockInRecordIds || [];
+ ids.forEach((id) => {
+ const exists = inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, id));
+ if (exists) return;
+ const fromList = inboundBatchList.value.find((row) => isSameInboundId(getInboundRowId(row), id));
+ if (fromList) {
+ const [option] = normalizeInboundBatchOptions([fromList]);
+ if (option) inboundBatchOptions.value.push(option);
+ return;
+ }
+ inboundBatchOptions.value.push({
+ label: String(id),
+ value: id,
+ inboundAmount: 0,
+ });
+ });
+};
+
+const restoreInboundTableSelection = () => {
+ nextTick(() => {
+ const table = inboundTableRef.value;
+ if (!table) return;
+ table.clearSelection();
+ const selectedIds = new Set((form.stockInRecordIds || []).map((id) => String(id)));
+ inboundBatchList.value.forEach((row) => {
+ const rowId = getInboundRowId(row);
+ if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) {
+ table.toggleRowSelection(row, true);
+ }
+ });
+ });
+};
+
+const loadInboundBatches = (supplierId, keepSelected = false, syncAmount = true) => {
+ if (!supplierId) {
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
+ if (!keepSelected) {
+ form.stockInRecordIds = [];
+ form.inboundBatches = "";
+ form.paymentAmount = 0;
+ }
+ return Promise.resolve();
+ }
+ inboundBatchLoading.value = true;
+ return getInboundBatchesBySupplier({ supplierId })
+ .then((res) => {
+ if (res.code === 200) {
+ const list = res.data?.records ?? res.data ?? [];
+ inboundBatchList.value = Array.isArray(list) ? list : [];
+ inboundBatchOptions.value = normalizeInboundBatchOptions(list);
+ } else {
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
+ }
+ })
+ .catch(() => {
+ inboundBatchList.value = [];
+ inboundBatchOptions.value = [];
+ })
+ .finally(() => {
+ inboundBatchLoading.value = false;
+ if (keepSelected) {
+ ensureInboundOptionsForSelected();
+ restoreInboundTableSelection();
+ if (syncAmount && !isView.value) {
+ syncPaymentAmount();
+ }
+ }
+ });
+};
+
+const handleSupplierChange = (supplierId) => {
+ form.stockInRecordIds = [];
+ form.inboundBatches = "";
+ form.paymentAmount = 0;
+ loadInboundBatches(supplierId);
+};
+
+const handleInboundInputClick = () => {
+ if (isEdit.value || isView.value) return;
+ openInboundSelectDialog();
+};
+
+const openInboundSelectDialog = () => {
+ if (!form.supplierId || isEdit.value || isView.value) return;
+ inboundSelectVisible.value = true;
+ loadInboundBatches(form.supplierId, true, false).then(() => {
+ restoreInboundTableSelection();
+ });
+};
+
+const handleInboundDialogSelectionChange = (selection) => {
+ dialogInboundSelection.value = selection;
+};
+
+const confirmInboundSelection = () => {
+ if (dialogInboundSelection.value.length === 0) {
+ ElMessage.warning("璇疯嚦灏戦�夋嫨涓�鏉″叆搴撳崟");
+ return;
+ }
+ form.stockInRecordIds = dialogInboundSelection.value
+ .map((row) => getInboundRowId(row))
+ .filter((id) => id !== undefined && id !== null);
+ form.inboundBatches = dialogInboundSelection.value
+ .map((row) => row.inboundBatches ?? row.batchNo ?? "")
+ .filter(Boolean)
+ .join("銆�");
+ dialogInboundSelection.value.forEach((row) => {
+ const [option] = normalizeInboundBatchOptions([row]);
+ if (option && !inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, option.value))) {
+ inboundBatchOptions.value.push(option);
+ }
+ });
+ inboundSelectVisible.value = false;
+ syncPaymentAmount();
+ formRef.value?.validateField("stockInRecordIds");
+};
+
+const handleInboundDialogClosed = () => {
+ dialogInboundSelection.value = [];
+};
+
onMounted(() => {
+ getSupplierList();
getTableData();
});
</script>
@@ -357,4 +1009,8 @@
color: #f56c6c;
font-weight: bold;
}
+
+.inbound-batch-input :deep(.el-input__wrapper) {
+ cursor: pointer;
+}
</style>
diff --git a/src/views/financialManagement/payable/purchaseIn.vue b/src/views/financialManagement/payable/purchaseIn.vue
index 4fadcbb..ebc8f0c 100644
--- a/src/views/financialManagement/payable/purchaseIn.vue
+++ b/src/views/financialManagement/payable/purchaseIn.vue
@@ -6,7 +6,14 @@
<el-input v-model="filters.inboundBatches" placeholder="璇疯緭鍏ュ叆搴撳崟鍙�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="渚涘簲鍟�:">
- <el-input v-model="filters.supplierName" placeholder="璇疯緭鍏ヤ緵搴斿晢" clearable style="width: 200px;" />
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable filterable style="width: 200px;">
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
+ </el-select>
</el-form-item>
<el-form-item label="鍏ュ簱鏃ユ湡:">
<el-date-picker
@@ -45,7 +52,7 @@
@pagination="changePage"
>
<template #inboundDate="{ row }">
- {{ row.InboundDate || row.inboundDate || "" }}
+ {{ row.inboundDate ?? row.InboundDate ?? "" }}
</template>
</PIMTable>
</div>
@@ -56,6 +63,7 @@
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
import { ElMessage } from "element-plus";
import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase";
+import { listSupplier } from "@/api/basicData/supplierManageFile.js";
defineOptions({
name: "閲囪喘鍏ュ簱",
@@ -65,7 +73,7 @@
const filters = reactive({
inboundBatches: "",
- supplierName: "",
+ supplierId: "",
dateRange: [],
});
@@ -80,7 +88,7 @@
{ label: "渚涘簲鍟�", prop: "supplierName", minWidth: "180" },
{
label: "鍏ュ簱鏃ユ湡",
- prop: "InboundDate",
+ prop: "inboundDate",
minWidth: "170",
dataType: "slot",
slot: "inboundDate",
@@ -92,18 +100,30 @@
const dataList = ref([]);
const tableLoading = ref(false);
+const supplierList = ref([]);
-function buildFilterParams() {
- const params = {
- inboundBatches: filters.inboundBatches || undefined,
- supplierName: filters.supplierName || undefined,
- };
- if (filters.dateRange && filters.dateRange.length === 2) {
+const buildFilterParams = () => {
+ const params = {};
+ if (filters.inboundBatches) {
+ params.inboundBatches = filters.inboundBatches;
+ }
+ if (filters.supplierId) {
+ params.supplierId = filters.supplierId;
+ }
+ if (filters.dateRange?.length === 2) {
params.startDate = filters.dateRange[0];
params.endDate = filters.dateRange[1];
}
return params;
-}
+};
+
+const getSupplierList = () => {
+ listSupplier({ current: -1, size: -1, isWhite: 0 }).then((res) => {
+ if (res.code === 200) {
+ supplierList.value = res.data?.records ?? [];
+ }
+ });
+};
const onSearch = () => {
pagination.currentPage = 1;
@@ -125,10 +145,13 @@
} else {
ElMessage.error(res.msg || "鏌ヨ澶辫触");
dataList.value = [];
+ pagination.total = 0;
}
})
.catch(() => {
dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
})
.finally(() => {
tableLoading.value = false;
@@ -137,7 +160,7 @@
const resetFilters = () => {
filters.inboundBatches = "";
- filters.supplierName = "";
+ filters.supplierId = "";
filters.dateRange = [];
pagination.currentPage = 1;
getTableData();
@@ -153,11 +176,12 @@
proxy.download(
"/accountPurchase/exportAccountPurchaseInbound",
buildFilterParams(),
- `閲囪喘鍏ュ簱_${new Date().getTime()}.xlsx`
+ `閲囪喘鍏ュ簱_${Date.now()}.xlsx`
);
};
onMounted(() => {
+ getSupplierList();
getTableData();
});
</script>
diff --git a/src/views/financialManagement/payable/purchaseReturn.vue b/src/views/financialManagement/payable/purchaseReturn.vue
index e7ca665..eeec383 100644
--- a/src/views/financialManagement/payable/purchaseReturn.vue
+++ b/src/views/financialManagement/payable/purchaseReturn.vue
@@ -1,26 +1,20 @@
<template>
<!-- 閲囪喘閫�璐� -->
-
<div class="app-container">
<el-form :model="filters" :inline="true">
<el-form-item label="閫�璐у崟鍙�:">
- <el-input
- v-model="filters.returnNo"
- placeholder="璇疯緭鍏ラ��璐у崟鍙�"
- clearable
- style="width: 200px"
- />
+ <el-input v-model="filters.returnNo" placeholder="璇疯緭鍏ラ��璐у崟鍙�" clearable style="width: 200px;" />
</el-form-item>
-
<el-form-item label="渚涘簲鍟�:">
- <el-input
- v-model="filters.supplierName"
- placeholder="璇疯緭鍏ヤ緵搴斿晢"
- clearable
- style="width: 200px"
- />
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable filterable style="width: 200px;">
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
+ </el-select>
</el-form-item>
-
<el-form-item label="閫�璐ф棩鏈�:">
<el-date-picker
v-model="filters.dateRange"
@@ -33,23 +27,18 @@
clearable
/>
</el-form-item>
-
<el-form-item>
<el-button type="primary" @click="onSearch">鎼滅储</el-button>
-
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
-
<div class="table_list">
<div class="actions">
<div></div>
-
<div>
<el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
</div>
</div>
-
<PIMTable
rowKey="id"
:column="columns"
@@ -57,9 +46,7 @@
:tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
-
size: pagination.pageSize,
-
total: pagination.total,
}"
@pagination="changePage"
@@ -68,14 +55,11 @@
</div>
</template>
-
-
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
-
import { ElMessage } from "element-plus";
-
import { listPageAccountPurchaseReturn } from "@/api/financialManagement/accountPurchase";
+import { listSupplier } from "@/api/basicData/supplierManageFile.js";
defineOptions({
name: "閲囪喘閫�璐�",
@@ -85,38 +69,26 @@
const filters = reactive({
returnNo: "",
-
- supplierName: "",
-
+ supplierId: "",
dateRange: [],
});
const pagination = reactive({
currentPage: 1,
-
pageSize: 10,
-
total: 0,
});
const columns = [
{ label: "閫�璐у崟鍙�", prop: "returnNo", minWidth: "150" },
-
{ label: "渚涘簲鍟�", prop: "supplierName", minWidth: "180" },
-
{ label: "鍏宠仈鍏ュ簱鍗曞彿", prop: "inboundBatches", minWidth: "150" },
-
{ label: "閫�璐ф棩鏈�", prop: "preparedAt", minWidth: "170" },
-
{
label: "閫�娆炬�婚",
-
prop: "totalAmount",
-
minWidth: "150",
-
align: "right",
-
formatData: (val) =>
val === null || val === undefined || val === ""
? ""
@@ -125,66 +97,65 @@
maximumFractionDigits: 2,
}),
},
-
{ label: "閫�璐ф柟寮�", prop: "returnType", minWidth: "150" },
-
{ label: "閲囪喘璁㈠崟鍙�", prop: "purchaseContractNumber", minWidth: "150" },
];
const dataList = ref([]);
-
const tableLoading = ref(false);
+const supplierList = ref([]);
-function buildFilterParams() {
- const params = {
- returnNo: filters.returnNo || undefined,
-
- supplierName: filters.supplierName || undefined,
- };
-
- if (filters.dateRange && filters.dateRange.length === 2) {
+const buildFilterParams = () => {
+ const params = {};
+ if (filters.returnNo) {
+ params.returnNo = filters.returnNo;
+ }
+ if (filters.supplierId) {
+ params.supplierId = filters.supplierId;
+ }
+ if (filters.dateRange?.length === 2) {
params.startDate = filters.dateRange[0];
-
params.endDate = filters.dateRange[1];
}
-
return params;
-}
+};
+
+const getSupplierList = () => {
+ listSupplier({ current: -1, size: -1, isWhite: 0 }).then((res) => {
+ if (res.code === 200) {
+ supplierList.value = res.data?.records ?? [];
+ }
+ });
+};
const onSearch = () => {
pagination.currentPage = 1;
-
getTableData();
};
const getTableData = () => {
tableLoading.value = true;
-
listPageAccountPurchaseReturn({
...buildFilterParams(),
-
current: pagination.currentPage,
-
size: pagination.pageSize,
})
.then((res) => {
const ok = res.code === 200 || res.code === 0;
-
if (ok && res.data) {
pagination.total = res.data.total ?? 0;
-
dataList.value = res.data.records ?? [];
} else {
ElMessage.error(res.msg || "鏌ヨ澶辫触");
-
dataList.value = [];
+ pagination.total = 0;
}
})
-
.catch(() => {
dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
})
-
.finally(() => {
tableLoading.value = false;
});
@@ -192,48 +163,36 @@
const resetFilters = () => {
filters.returnNo = "";
-
- filters.supplierName = "";
-
+ filters.supplierId = "";
filters.dateRange = [];
-
pagination.currentPage = 1;
-
getTableData();
};
const changePage = ({ page, limit }) => {
pagination.currentPage = page;
-
pagination.pageSize = limit;
-
getTableData();
};
const handleOut = () => {
proxy.download(
"/accountPurchase/exportAccountPurchaseReturn",
-
buildFilterParams(),
-
- `閲囪喘閫�璐${new Date().getTime()}.xlsx`
+ `閲囪喘閫�璐${Date.now()}.xlsx`
);
};
onMounted(() => {
+ getSupplierList();
getTableData();
});
</script>
-
-
<style lang="scss" scoped>
.actions {
display: flex;
-
justify-content: space-between;
-
margin-bottom: 15px;
}
</style>
-
diff --git a/src/views/financialManagement/payable/reconciliation.vue b/src/views/financialManagement/payable/reconciliation.vue
index 3aa23cd..e749e56 100644
--- a/src/views/financialManagement/payable/reconciliation.vue
+++ b/src/views/financialManagement/payable/reconciliation.vue
@@ -2,8 +2,13 @@
<div class="app-container">
<el-form :model="filters" :inline="true">
<el-form-item label="渚涘簲鍟�:">
- <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable style="width: 200px;">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="filters.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" clearable filterable style="width: 200px;">
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
</el-form-item>
<el-form-item label="瀵硅处鏈熼棿:">
@@ -12,7 +17,7 @@
<el-date-picker v-model="filters.endMonth" type="month" placeholder="缁撴潫鏈堜唤" value-format="YYYY-MM" style="width: 140px;" />
</el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -29,6 +34,7 @@
rowKey="id"
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
size: pagination.pageSize,
@@ -36,21 +42,21 @@
}"
@pagination="changePage"
>
- <template #beginBalance="{ row }">
- <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.beginBalance) }}</span>
+ <template #openingBalance="{ row }">
+ <span :class="row.openingBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.openingBalance) }}</span>
</template>
- <template #currentPayable="{ row }">
- <span class="text-danger">楼{{ formatMoney(row.currentPayable) }}</span>
+ <template #currentPlan="{ row }">
+ <span class="text-danger">楼{{ formatMoney(row.currentPlan) }}</span>
</template>
- <template #currentPayment="{ row }">
- <span class="text-success">楼{{ formatMoney(row.currentPayment) }}</span>
+ <template #currentActually="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.currentActually) }}</span>
</template>
- <template #endBalance="{ row }">
- <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.endBalance) }}</span>
+ <template #closingBalance="{ row }">
+ <span :class="row.closingBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.closingBalance) }}</span>
</template>
<template #operation="{ row }">
<el-button type="primary" link @click="viewDetail(row)">鏌ョ湅鏄庣粏</el-button>
- <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
@@ -60,7 +66,7 @@
<h3>{{ currentSupplier }} 搴斾粯瀵硅处鍗�</h3>
<p>瀵硅处鏈熼棿: {{ currentPeriod }}</p>
</div>
- <el-table :data="detailData" border style="width: 100%">
+ <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading">
<el-table-column prop="date" label="鏃ユ湡" width="120" />
<el-table-column prop="type" label="绫诲瀷" width="100">
<template #default="{ row }">
@@ -77,6 +83,7 @@
<el-table-column prop="credit" label="璐锋柟(搴斾粯)" width="120">
<template #default="{ row }">
<span v-if="row.credit > 0" class="text-danger">楼{{ formatMoney(row.credit) }}</span>
+ <span v-else-if="row.credit < 0" class="text-success">楼{{ formatMoney(Math.abs(row.credit)) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
@@ -98,52 +105,80 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="閫夋嫨渚涘簲鍟�" prop="supplierId">
- <el-select v-model="generateForm.supplierId" placeholder="璇烽�夋嫨渚涘簲鍟�" style="width: 100%;" @change="onSupplierChange">
- <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select
+ v-model="generateForm.supplierId"
+ placeholder="璇烽�夋嫨渚涘簲鍟�"
+ style="width: 100%;"
+ filterable
+ @change="onSupplierChange"
+ >
+ <el-option
+ v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id"
+ />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="瀵硅处鏈堜唤" prop="period">
- <el-date-picker v-model="generateForm.period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" />
+ <el-form-item label="瀵硅处鏈堜唤" prop="statementMonth">
+ <el-date-picker
+ v-model="generateForm.statementMonth"
+ type="month"
+ placeholder="閫夋嫨鏈堜唤"
+ value-format="YYYY-MM"
+ style="width: 100%;"
+ @change="onStatementMonthChange"
+ />
</el-form-item>
</el-col>
</el-row>
</el-form>
- <div v-if="purchaseData.length > 0" class="purchase-section">
- <div class="section-title">鏈湀閲囪喘鏁版嵁</div>
- <el-table :data="purchaseData" border style="width: 100%; margin-bottom: 15px;" v-loading="purchaseLoading" @selection-change="handlePurchaseSelectionChange">
+ <div v-if="statementDetailLoaded" class="purchase-section">
+ <div v-if="purchaseData.length > 0" class="section-title">鏈湀閲囪喘鏁版嵁</div>
+ <el-table
+ v-if="purchaseData.length > 0"
+ ref="purchaseTableRef"
+ :data="purchaseData"
+ border
+ row-key="id"
+ style="width: 100%; margin-bottom: 15px;"
+ v-loading="purchaseLoading"
+ @selection-change="handlePurchaseSelectionChange"
+ >
<el-table-column type="selection" width="55" align="center" />
- <el-table-column prop="date" label="鏃ユ湡" width="120" />
- <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="occurrenceDate" label="鏃ユ湡" width="120" />
+ <el-table-column prop="receiptNumber" label="鍗曟嵁缂栧彿" width="150" />
<el-table-column prop="type" label="绫诲瀷" width="100">
<template #default="{ row }">
- <el-tag :type="row.type === '鍏ュ簱' ? 'success' : 'danger'">{{ row.type }}</el-tag>
+ <el-tag :type="getDetailTypeTagType(row.type)">{{ row.typeLabel }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="amount" label="閲戦" width="120">
<template #default="{ row }">
- <span :class="row.type === '鍏ュ簱' ? 'text-danger' : 'text-success'">楼{{ formatMoney(row.amount) }}</span>
+ <span :class="getDetailAmountClass(row.type)">楼{{ formatMoney(row.amount) }}</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="澶囨敞" />
</el-table>
+ <el-empty v-else description="璇ヤ緵搴斿晢鏈湀鏆傛棤鏄庣粏鏁版嵁" :image-size="80" />
<div class="summary-row">
- <span>鏈熷垵浣欓: <strong class="text-primary">楼{{ formatMoney(generateForm.beginBalance) }}</strong></span>
- <span>鏈湡搴斾粯: <strong class="text-danger">楼{{ formatMoney(generateForm.currentPayable) }}</strong></span>
- <span>鏈湡浠樻: <strong class="text-success">楼{{ formatMoney(generateForm.currentPayment) }}</strong></span>
- <span>鏈熸湯浣欓: <strong class="text-primary">楼{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment)) }}</strong></span>
+ <span>鏈熷垵浣欓: <strong class="text-primary">楼{{ formatMoney(generateForm.openingBalance) }}</strong></span>
+ <span>鏈湡搴斾粯: <strong class="text-danger">楼{{ formatMoney(generateForm.currentPlan) }}</strong></span>
+ <span>鏈湡浠樻: <strong class="text-success">楼{{ formatMoney(generateForm.currentActually) }}</strong></span>
+ <span>鏈熸湯浣欓: <strong :class="displayClosingBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(displayClosingBalance) }}</strong></span>
</div>
</div>
- <div v-else-if="generateForm.supplierId && !purchaseLoading" class="empty-tip">
+ <div v-else-if="generateForm.supplierId && generateForm.statementMonth && !purchaseLoading" class="empty-tip">
<el-empty description="璇ヤ緵搴斿晢鏈湀鏆傛棤閲囪喘鏁版嵁" />
</div>
<template #footer>
- <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">纭鐢熸垚</el-button>
+ <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate" :loading="submitLoading">纭鐢熸垚</el-button>
<el-button @click="generateDialogVisible = false">鍙栨秷</el-button>
</template>
</FormDialog>
@@ -151,9 +186,20 @@
</template>
<script setup>
-import { ref, reactive, onMounted, computed } from "vue";
-import { ElMessage } from "element-plus";
+import { ref, reactive, onMounted, computed, nextTick, getCurrentInstance } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
+import {
+ getAccountStatementDetailsByMonth,
+ addAccountStatement,
+ listPageAccountStatement,
+ deleteAccountStatement,
+} from "@/api/financialManagement/accountStatement.js";
+
+const ACCOUNT_TYPE_PAYABLE = 2;
+
+const { proxy } = getCurrentInstance();
defineOptions({
name: "搴斾粯瀵硅处",
@@ -172,56 +218,192 @@
});
const columns = [
- { label: "瀵硅处鍗曞彿", prop: "statementCode", width: "150" },
+ { label: "瀵硅处鍗曞彿", prop: "statementNumber", width: "150" },
{ label: "渚涘簲鍟�", prop: "supplierName", width: "180" },
- { label: "瀵硅处鏈熼棿", prop: "period", width: "150" },
- { label: "鏈熷垵浣欓", prop: "beginBalance", slot: "beginBalance" },
- { label: "鏈湡搴斾粯", prop: "currentPayable", slot: "currentPayable" },
- { label: "鏈湡浠樻", prop: "currentPayment", slot: "currentPayment" },
- { label: "鏈熸湯浣欓", prop: "endBalance", slot: "endBalance" },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+ { label: "瀵硅处鏈熼棿", prop: "statementMonth", width: "150" },
+ { label: "鏈熷垵浣欓", prop: "openingBalance", dataType: "slot", slot: "openingBalance" },
+ { label: "鏈湡搴斾粯", prop: "currentPlan", dataType: "slot", slot: "currentPlan" },
+ { label: "鏈湡浠樻", prop: "currentActually", dataType: "slot", slot: "currentActually" },
+ { label: "鏈熸湯浣欓", prop: "closingBalance", dataType: "slot", slot: "closingBalance" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" },
];
const dataList = ref([]);
+const tableLoading = ref(false);
+const submitLoading = ref(false);
const detailDialogVisible = ref(false);
const currentSupplier = ref("");
const currentPeriod = ref("");
const detailData = ref([]);
+const detailLoading = ref(false);
const generateDialogVisible = ref(false);
const purchaseLoading = ref(false);
+const statementDetailLoaded = ref(false);
const purchaseData = ref([]);
const selectedPurchases = ref([]);
+const purchaseTableRef = ref(null);
+const supplierList = ref([]);
+
+/** 鏄庣粏 type锛�1鍑哄簱 2鍏ュ簱 3鏀舵 4浠樻 5閫�璐� */
+const STATEMENT_DETAIL_TYPE_MAP = {
+ 1: "鍑哄簱",
+ 2: "鍏ュ簱",
+ 3: "鏀舵",
+ 4: "浠樻",
+ 5: "閫�璐�",
+};
+
+const calculateEndBalance = (openingBalance, currentPlan, currentActually) => {
+ return openingBalance + currentPlan - currentActually;
+};
+
+const getDetailTypeLabel = (type) => STATEMENT_DETAIL_TYPE_MAP[Number(type)] ?? "";
+
+const getDetailTypeTagType = (type) => {
+ const t = Number(type);
+ if (t === 2) return "success";
+ if (t === 4) return "primary";
+ if (t === 5) return "danger";
+ return "info";
+};
+
+const getDetailAmountClass = (type) => {
+ const t = Number(type);
+ if (t === 2) return "text-danger";
+ if (t === 4) return "text-success";
+ return "text-danger";
+};
const generateForm = reactive({
supplierId: "",
supplierName: "",
- period: "",
- beginBalance: 0,
- currentPayable: 0,
- currentPayment: 0,
+ statementMonth: "",
+ openingBalance: 0,
+ currentPlan: 0,
+ currentActually: 0,
+ closingBalance: 0,
});
-const canGenerate = computed(() => {
- return generateForm.supplierId && generateForm.period && selectedPurchases.value.length > 0;
-});
+const displayClosingBalance = computed(() =>
+ calculateEndBalance(
+ generateForm.openingBalance,
+ generateForm.currentPlan,
+ generateForm.currentActually
+ )
+);
-const supplierList = [
- { id: 1, name: "鍖椾含鍘熸潗鏂欎緵搴斿晢" },
- { id: 2, name: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�" },
- { id: 3, name: "骞垮窞鍖呰鏉愭枡鍘�" },
- { id: 4, name: "娣卞湷浜旈噾閰嶄欢鍏徃" },
-];
+const canGenerate = computed(
+ () => generateForm.supplierId && generateForm.statementMonth && selectedPurchases.value.length > 0
+);
-const mockData = [
- { id: 1, statementCode: "DZ202401001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", period: "2024-01", beginBalance: 20000, currentPayable: 15000, currentPayment: 10000, endBalance: 25000 },
- { id: 2, statementCode: "DZ202401002", supplierId: 2, supplierName: "涓婃捣鐢靛瓙鍏冨櫒浠跺叕鍙�", period: "2024-01", beginBalance: 10000, currentPayable: 20000, currentPayment: 15000, endBalance: 15000 },
- { id: 3, statementCode: "DZ202402001", supplierId: 1, supplierName: "鍖椾含鍘熸潗鏂欎緵搴斿晢", period: "2024-02", beginBalance: 25000, currentPayable: 18000, currentPayment: 20000, endBalance: 23000 },
-];
-
-const calculateEndBalance = (beginBalance, currentPayable, currentPayment) => {
- return beginBalance + currentPayable - currentPayment;
+const applyStatementSummary = (data) => {
+ generateForm.openingBalance = Number(data.openingBalance ?? 0);
+ generateForm.currentPlan = Number(data.currentPlan ?? 0);
+ generateForm.currentActually = Number(data.currentActually ?? 0);
+ generateForm.closingBalance = Number(
+ data.closingBalance ??
+ calculateEndBalance(
+ generateForm.openingBalance,
+ generateForm.currentPlan,
+ generateForm.currentActually
+ )
+ );
};
+
+const getSupplierList = () => {
+ getOptions().then((res) => {
+ if (res.code === 200) {
+ supplierList.value = res.data ?? [];
+ }
+ });
+};
+
+const normalizePurchaseRows = (list) => {
+ const rows = Array.isArray(list) ? list : [];
+ return rows.map((item, index) => {
+ const type = Number(item.type);
+ return {
+ id: item.id ?? `detail-${index}`,
+ accountStatementId: item.accountStatementId,
+ occurrenceDate: item.occurrenceDate ?? "",
+ receiptNumber: item.receiptNumber ?? "",
+ type,
+ typeLabel: getDetailTypeLabel(type),
+ amount: Math.abs(Number(item.amount ?? 0)),
+ remark: item.remark ?? "",
+ };
+ });
+};
+
+const selectAllPurchaseRows = (keepApiSummary = false) => {
+ nextTick(() => {
+ const table = purchaseTableRef.value;
+ if (!table) return;
+ table.clearSelection();
+ purchaseData.value.forEach((row) => table.toggleRowSelection(row, true));
+ selectedPurchases.value = [...purchaseData.value];
+ if (!keepApiSummary) {
+ calculateSummary();
+ }
+ });
+};
+
+const isNumericId = (id) => id !== undefined && id !== null && id !== "" && /^\d+$/.test(String(id));
+
+const buildFilterParams = (params = {}) => {
+ const result = { ...params, accountType: ACCOUNT_TYPE_PAYABLE };
+ if (filters.supplierId) {
+ result.customerId = filters.supplierId;
+ }
+ if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) {
+ result.statementMonth = filters.startMonth;
+ } else if (filters.startMonth) {
+ result.startMonth = filters.startMonth;
+ }
+ if (filters.endMonth && filters.startMonth !== filters.endMonth) {
+ result.endMonth = filters.endMonth;
+ }
+ return result;
+};
+
+const buildListParams = () =>
+ buildFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => buildFilterParams({});
+
+const buildDetailSubmitItem = (row) => {
+ const item = {
+ occurrenceDate: row.occurrenceDate,
+ receiptNumber: row.receiptNumber,
+ type: row.type,
+ amount: row.amount,
+ remark: row.remark ?? "",
+ };
+ if (isNumericId(row.id)) {
+ item.id = Number(row.id);
+ }
+ if (row.accountStatementId) {
+ item.accountStatementId = row.accountStatementId;
+ }
+ return item;
+};
+
+const buildAddPayload = () => ({
+ customerId: generateForm.supplierId,
+ customerName: generateForm.supplierName,
+ statementMonth: generateForm.statementMonth,
+ accountType: ACCOUNT_TYPE_PAYABLE,
+ statementNumber: "",
+ openingBalance: generateForm.openingBalance,
+ currentPlan: generateForm.currentPlan,
+ currentActually: generateForm.currentActually,
+ closingBalance: generateForm.closingBalance,
+ accountStatementDetails: selectedPurchases.value.map(buildDetailSubmitItem),
+});
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
@@ -229,15 +411,35 @@
};
const getTableData = () => {
- let result = [...mockData];
- if (filters.supplierId) {
- result = result.filter(item => item.supplierId === filters.supplierId);
- }
- if (filters.startMonth && filters.endMonth) {
- result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
- }
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ tableLoading.value = true;
+ listPageAccountStatement(buildListParams())
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+ dataList.value = (res.data.records ?? []).map((row) => ({
+ ...row,
+ supplierName: row.supplierName ?? row.customerName,
+ }));
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ dataList.value = [];
+ pagination.total = 0;
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+const onSearch = () => {
+ pagination.currentPage = 1;
+ getTableData();
};
const resetFilters = () => {
@@ -248,92 +450,106 @@
getTableData();
};
-const changePage = ({ current, size }) => {
- pagination.currentPage = current;
- pagination.pageSize = size;
+const changePage = ({ page, limit }) => {
+ pagination.currentPage = page;
+ pagination.pageSize = limit;
getTableData();
};
const generateStatement = () => {
generateForm.supplierId = "";
generateForm.supplierName = "";
- generateForm.period = "";
- generateForm.beginBalance = 0;
- generateForm.currentPayable = 0;
- generateForm.currentPayment = 0;
+ generateForm.statementMonth = "";
+ generateForm.openingBalance = 0;
+ generateForm.currentPlan = 0;
+ generateForm.currentActually = 0;
+ generateForm.closingBalance = 0;
+ statementDetailLoaded.value = false;
purchaseData.value = [];
selectedPurchases.value = [];
generateDialogVisible.value = true;
};
const onSupplierChange = (supplierId) => {
- const supplier = supplierList.find(item => item.id === supplierId);
- if (supplier) {
- generateForm.supplierName = supplier.name;
- }
+ const supplier = supplierList.value.find((item) => item.id === supplierId);
+ generateForm.supplierName = supplier?.supplierName ?? "";
loadPurchaseData();
};
-const onPeriodChange = () => {
+const onStatementMonthChange = () => {
loadPurchaseData();
};
const loadPurchaseData = () => {
- if (!generateForm.supplierId || !generateForm.period) {
+ if (!generateForm.supplierId || !generateForm.statementMonth) {
purchaseData.value = [];
+ selectedPurchases.value = [];
+ statementDetailLoaded.value = false;
+ generateForm.openingBalance = 0;
+ generateForm.currentPlan = 0;
+ generateForm.currentActually = 0;
+ generateForm.closingBalance = 0;
return;
}
purchaseLoading.value = true;
+ selectedPurchases.value = [];
+ statementDetailLoaded.value = false;
- setTimeout(() => {
- const mockPurchaseData = [
- { id: 1, date: generateForm.period + "-05", code: "RK2024001", type: "鍏ュ簱", amount: 8000, remark: "鍘熸潗鏂欓噰璐�" },
- { id: 2, date: generateForm.period + "-10", code: "FK2024001", type: "浠樻", amount: 5000, remark: "鏀粯璐ф" },
- { id: 3, date: generateForm.period + "-15", code: "RK2024002", type: "鍏ュ簱", amount: 12000, remark: "鐢靛瓙鍏冨櫒浠�" },
- { id: 4, date: generateForm.period + "-18", code: "TH2024001", type: "閫�璐�", amount: 2000, remark: "璐ㄩ噺闂閫�璐�" },
- { id: 5, date: generateForm.period + "-22", code: "RK2024003", type: "鍏ュ簱", amount: 6000, remark: "鍖呰鏉愭枡" },
- { id: 6, date: generateForm.period + "-25", code: "FK2024002", type: "浠樻", amount: 8000, remark: "鏀粯璐ф" },
- ];
+ getAccountStatementDetailsByMonth({
+ accountType: ACCOUNT_TYPE_PAYABLE,
+ customerId: generateForm.supplierId,
+ statementMonth: generateForm.statementMonth,
+ })
+ .then((res) => {
+ if (res.code === 200) {
+ const data = res.data ?? {};
+ const details = data.accountStatementDetails;
+ const list = Array.isArray(details) ? details : [];
+ purchaseData.value = normalizePurchaseRows(list);
+ applyStatementSummary(data);
+ statementDetailLoaded.value = true;
- purchaseData.value = mockPurchaseData;
-
- const lastPeriod = getLastPeriod(generateForm.period);
- const lastStatement = mockData.find(item =>
- item.supplierId === generateForm.supplierId && item.period === lastPeriod
- );
- generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0;
-
- calculateSummary();
-
- purchaseLoading.value = false;
- }, 500);
-};
-
-const getLastPeriod = (period) => {
- const [year, month] = period.split("-").map(Number);
- if (month === 1) {
- return `${year - 1}-12`;
- }
- return `${year}-${String(month - 1).padStart(2, "0")}`;
+ if (purchaseData.value.length > 0) {
+ selectAllPurchaseRows(true);
+ }
+ } else {
+ purchaseData.value = [];
+ statementDetailLoaded.value = false;
+ ElMessage.error(res.msg || "鏌ヨ瀵硅处鏄庣粏澶辫触");
+ }
+ })
+ .catch(() => {
+ purchaseData.value = [];
+ statementDetailLoaded.value = false;
+ ElMessage.error("鏌ヨ瀵硅处鏄庣粏澶辫触");
+ })
+ .finally(() => {
+ purchaseLoading.value = false;
+ });
};
const calculateSummary = () => {
let payable = 0;
let payment = 0;
- selectedPurchases.value.forEach(item => {
- if (item.type === "鍏ュ簱") {
+ selectedPurchases.value.forEach((item) => {
+ if (item.type === 2) {
payable += item.amount;
- } else if (item.type === "閫�璐�") {
+ } else if (item.type === 5) {
payable -= item.amount;
- } else if (item.type === "浠樻") {
+ } else if (item.type === 4) {
payment += item.amount;
}
});
- generateForm.currentPayable = payable;
- generateForm.currentPayment = payment;
+ generateForm.currentPlan = payable;
+ generateForm.currentActually = payment;
+ generateForm.closingBalance = calculateEndBalance(
+ generateForm.openingBalance,
+ generateForm.currentPlan,
+ generateForm.currentActually
+ );
};
const handlePurchaseSelectionChange = (selection) => {
@@ -342,51 +558,127 @@
};
const confirmGenerate = () => {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment);
+ if (!canGenerate.value) return;
+ submitLoading.value = true;
+ addAccountStatement(buildAddPayload())
+ .then((res) => {
+ if (res.code === 200) {
+ generateDialogVisible.value = false;
+ ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
+ pagination.currentPage = 1;
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鐢熸垚澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鐢熸垚澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+};
- mockData.unshift({
- id: newId,
- statementCode: "DZ" + Date.now(),
- supplierId: generateForm.supplierId,
- supplierName: generateForm.supplierName,
- period: generateForm.period,
- beginBalance: generateForm.beginBalance,
- currentPayable: generateForm.currentPayable,
- currentPayment: generateForm.currentPayment,
- endBalance,
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎瀵硅处鍗曘��${row.statementNumber || row.id}銆嶅悧锛焋, "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ deleteAccountStatement([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ });
+};
+
+const buildDetailTableFromApi = (data, statementMonth) => {
+ const details = Array.isArray(data.accountStatementDetails) ? data.accountStatementDetails : [];
+ let runningBalance = Number(data.openingBalance ?? 0);
+ const rows = [
+ {
+ date: statementMonth ?? "",
+ type: "鏈熷垵",
+ code: "-",
+ debit: 0,
+ credit: 0,
+ balance: runningBalance,
+ remark: "鏈熷垵浣欓",
+ },
+ ];
+
+ details.forEach((item) => {
+ const amount = Math.abs(Number(item.amount ?? 0));
+ const type = Number(item.type);
+ let debit = 0;
+ let credit = 0;
+
+ if (type === 2) {
+ credit = amount;
+ runningBalance += amount;
+ } else if (type === 4) {
+ debit = amount;
+ runningBalance -= amount;
+ } else if (type === 5) {
+ credit = -amount;
+ runningBalance -= amount;
+ }
+
+ rows.push({
+ date: item.occurrenceDate ?? "",
+ type: getDetailTypeLabel(type),
+ code: item.receiptNumber ?? "",
+ debit,
+ credit,
+ balance: runningBalance,
+ remark: item.remark ?? "",
+ });
});
- generateDialogVisible.value = false;
- ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
- getTableData();
+ return rows;
};
const viewDetail = (row) => {
- currentSupplier.value = row.supplierName;
- currentPeriod.value = row.period;
+ const partnerId = row.customerId ?? row.supplierId;
+ if (!partnerId || !row.statementMonth) {
+ ElMessage.warning("缂哄皯渚涘簲鍟嗘垨瀵硅处鏈堜唤锛屾棤娉曟煡璇㈡槑缁�");
+ return;
+ }
- const purchaseInAmount = Math.floor(row.currentPayable * 0.7);
- const returnAmount = Math.floor(row.currentPayable * 0.1);
- const firstPayment = Math.floor(row.currentPayment * 0.5);
- const secondPayment = row.currentPayment - firstPayment;
-
- let runningBalance = row.beginBalance;
-
- detailData.value = [
- { date: row.period + "-01", type: "鏈熷垵", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "鏈熷垵浣欓" },
- { date: row.period + "-05", type: "鍏ュ簱", code: "RK2024001", debit: 0, credit: purchaseInAmount, balance: runningBalance += purchaseInAmount, remark: "閲囪喘鍏ュ簱" },
- { date: row.period + "-10", type: "浠樻", code: "FK2024001", debit: firstPayment, credit: 0, balance: runningBalance -= firstPayment, remark: "鏀粯璐ф" },
- { date: row.period + "-15", type: "鍏ュ簱", code: "RK2024002", debit: 0, credit: row.currentPayable - purchaseInAmount - returnAmount, balance: runningBalance += (row.currentPayable - purchaseInAmount - returnAmount), remark: "閲囪喘鍏ュ簱" },
- { date: row.period + "-20", type: "閫�璐�", code: "TH2024001", debit: 0, credit: -returnAmount, balance: runningBalance -= returnAmount, remark: "閲囪喘閫�璐�" },
- { date: row.period + "-25", type: "浠樻", code: "FK2024002", debit: secondPayment, credit: 0, balance: runningBalance -= secondPayment, remark: "鏀粯璐ф" },
- ];
-
+ currentSupplier.value = row.supplierName ?? row.customerName ?? "";
+ currentPeriod.value = row.statementMonth ?? "";
+ detailData.value = [];
detailDialogVisible.value = true;
-};
+ detailLoading.value = true;
-const printStatement = (row) => {
- ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementCode}`);
+ getAccountStatementDetailsByMonth({
+ accountType: ACCOUNT_TYPE_PAYABLE,
+ customerId: partnerId,
+ statementMonth: row.statementMonth,
+ })
+ .then((res) => {
+ if (res.code === 200) {
+ detailData.value = buildDetailTableFromApi(res.data ?? {}, row.statementMonth);
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ鏄庣粏澶辫触");
+ detailDialogVisible.value = false;
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鏌ヨ鏄庣粏澶辫触");
+ detailDialogVisible.value = false;
+ })
+ .finally(() => {
+ detailLoading.value = false;
+ });
};
const printDetail = () => {
@@ -394,10 +686,15 @@
};
const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
+ proxy.download(
+ "/accountStatement/exportAccountStatement",
+ buildExportParams(),
+ `搴斾粯瀵硅处鍗昣${Date.now()}.xlsx`
+ );
};
onMounted(() => {
+ getSupplierList();
getTableData();
});
</script>
@@ -415,6 +712,10 @@
.text-danger {
color: #f56c6c;
+}
+
+.text-primary {
+ color: #409eff;
}
.statement-header {
@@ -461,9 +762,5 @@
.empty-tip {
margin-top: 30px;
-}
-
-.text-primary {
- color: #409eff;
}
</style>
diff --git a/src/views/financialManagement/receivable/invoiceApply.vue b/src/views/financialManagement/receivable/invoiceApply.vue
index ac691bc..14fdd67 100644
--- a/src/views/financialManagement/receivable/invoiceApply.vue
+++ b/src/views/financialManagement/receivable/invoiceApply.vue
@@ -72,7 +72,7 @@
<el-button type="primary" link @click="edit(row)" v-if="isPendingStatus(row.status)">缂栬緫</el-button>
<el-button type="danger" link @click="handleDelete(row)" v-if="isPendingStatus(row.status)">鍒犻櫎</el-button>
<el-button type="success" link @click="handleAudit(row)" v-if="isPendingStatus(row.status)">瀹℃牳</el-button>
- <!-- <el-button type="warning" link @click="handleInvoice(row)" v-if="isApprovedStatus(row.status)">寮�绁�</el-button> -->
+ <el-button type="primary" link @click="openFileDialog(row)" v-if="isApprovedStatus(row.status)">闄勪欢</el-button>
</template>
</PIMTable>
</div>
@@ -119,25 +119,24 @@
</el-col>
<el-col :span="12">
<el-form-item label="鍑哄簱鍗曞彿" prop="outboundBatchNos">
- <el-select
- v-model="form.outboundBatchNos"
- multiple
- collapse-tags
- collapse-tags-tooltip
- filterable
+ <el-input
+ :model-value="outboundBatchDisplayText"
placeholder="璇峰厛閫夋嫨瀹㈡埛"
- style="width: 100%;"
- :disabled="!form.customerId || isView"
- :loading="outboundBatchLoading"
- @change="handleOutboundBatchChange"
+ readonly
+ :disabled="!form.customerId || isEdit || isView"
+ class="outbound-batch-input"
+ @click="handleOutboundInputClick"
>
- <el-option
- v-for="item in outboundBatchOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
+ <template v-if="!isEdit && !isView" #append>
+ <el-button
+ :disabled="!form.customerId"
+ :loading="outboundBatchLoading"
+ @click.stop="openOutboundSelectDialog"
+ >
+ 閫夋嫨
+ </el-button>
+ </template>
+ </el-input>
</el-form-item>
</el-col>
</el-row>
@@ -171,9 +170,9 @@
<el-col :span="12">
<el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
<el-select v-model="form.invoiceType" placeholder="璇烽�夋嫨鍙戠エ绫诲瀷" style="width: 100%;" :disabled="isView">
- <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="special" />
- <el-option label="澧炲�肩◣鏅�氬彂绁�" value="normal" />
- <el-option label="鐢靛瓙鍙戠エ" value="electronic" />
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="澧炲�肩◣涓撶敤鍙戠エ" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="澧炲�肩◣鏅�氬彂绁�" />
+ <el-option label="鐢靛瓙鍙戠エ" value="鐢靛瓙鍙戠エ" />
</el-select>
</el-form-item>
</el-col>
@@ -202,11 +201,58 @@
<el-button @click="closeDialog">鍙栨秷</el-button>
</template>
</FormDialog>
+
+ <el-dialog
+ v-model="outboundSelectVisible"
+ title="閫夋嫨鍑哄簱鍗�"
+ width="1200px"
+ append-to-body
+ destroy-on-close
+ :close-on-click-modal="false"
+ @closed="handleOutboundDialogClosed"
+ >
+ <el-table
+ ref="outboundTableRef"
+ v-loading="outboundBatchLoading"
+ :data="outboundBatchList"
+ row-key="id"
+ border
+ stripe
+ max-height="480"
+ @selection-change="handleOutboundDialogSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column prop="outboundBatches" label="鍑哄簱鍗曞彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="specificationModel" label="瑙勬牸鍨嬪彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="salesContractNo" label="閿�鍞悎鍚屽彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="shippingNo" label="鍙戣揣鍗曞彿" min-width="130" show-overflow-tooltip />
+ <el-table-column prop="shippingDate" label="鍙戣揣鏃ユ湡" width="110" align="center" />
+ <el-table-column prop="outboundAmount" label="鍑哄簱閲戦" width="110" align="right">
+ <template #default="{ row }">楼{{ formatMoney(row.outboundAmount) }}</template>
+ </el-table-column>
+ <el-table-column prop="taxRate" label="绋庣巼" width="80" align="center">
+ <template #default="{ row }">{{ row.taxRate }}%</template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <el-button type="primary" @click="confirmOutboundSelection">纭畾</el-button>
+ <el-button @click="outboundSelectVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
+
+ <FileList
+ v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ record-type="account_invoice_application"
+ :record-id="currentRecordId"
+ />
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance, defineAsyncComponent } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { listCustomer } from "@/api/basicData/customer.js";
@@ -218,6 +264,8 @@
updateAccountInvoiceApplication,
deleteAccountInvoiceApplication,
} from "@/api/financialManagement/invoiceApply.js";
+
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
defineOptions({
name: "寮�绁ㄧ敵璇�",
@@ -244,10 +292,10 @@
{ label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
{ label: "寮�绁ㄩ噾棰�", prop: "amount", dataType: "slot", slot: "amount" },
{ label: "绋庣巼", prop: "taxRate", dataType: "slot", slot: "taxRate" },
- { label: "鍙戠エ绫诲瀷", prop: "invoiceTypeLabel", width: "130" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceType", width: "130" },
{ label: "鐢宠鏃ユ湡", prop: "applyDate", width: "120" },
{ label: "瀹℃牳鐘舵��", prop: "status", dataType: "slot", slot: "status", width: "110", align: "center" },
- { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "260", fixed: "right" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "300", fixed: "right" },
];
const dataList = ref([]);
@@ -262,13 +310,18 @@
const closeDialog = () => {
dialogVisible.value = false;
+ outboundSelectVisible.value = false;
isView.value = false;
isEdit.value = false;
};
const customerList = ref([]);
+const outboundBatchList = ref([]);
const outboundBatchOptions = ref([]);
const outboundBatchLoading = ref(false);
+const outboundSelectVisible = ref(false);
+const outboundTableRef = ref(null);
+const dialogOutboundSelection = ref([]);
const getCustomerList = () => {
listCustomer({ current: -1, size: -1, type: 0 }).then((res) => {
@@ -302,9 +355,13 @@
});
};
+const isSameOutboundId = (a, b) => String(a) === String(b);
+
const getSelectedOutboundOptions = () => {
const selected = form.outboundBatchNos || [];
- return outboundBatchOptions.value.filter((opt) => selected.includes(opt.value));
+ return outboundBatchOptions.value.filter((opt) =>
+ selected.some((id) => isSameOutboundId(id, opt.value))
+ );
};
/** 鏍¢獙鎵�閫夊嚭搴撳崟绋庣巼鏄惁涓�鑷达紝涓�鑷村垯鍥炲~ form.taxRate */
@@ -334,18 +391,89 @@
const syncInvoiceAmount = () => {
const selected = form.outboundBatchNos || [];
const sum = outboundBatchOptions.value
- .filter((opt) => selected.includes(opt.value))
+ .filter((opt) => selected.some((id) => isSameOutboundId(id, opt.value)))
.reduce((acc, opt) => acc + (Number(opt.outboundAmount) || 0), 0);
form.amount = sum > 0 ? Number(sum.toFixed(2)) : 0;
};
-const handleOutboundBatchChange = () => {
+const getOutboundRowId = (row) => row?.id ?? row?.stockOutRecordId;
+
+const outboundBatchDisplayText = computed(() => {
+ if (isEdit.value || isView.value) {
+ return form.outboundBatches || "";
+ }
+ if (form.outboundBatches) return form.outboundBatches;
+ const ids = form.outboundBatchNos || [];
+ if (!ids.length) return "";
+ return outboundBatchOptions.value
+ .filter((opt) => ids.some((id) => isSameOutboundId(id, opt.value)))
+ .map((opt) => opt.label)
+ .join("銆�");
+});
+
+const handleOutboundInputClick = () => {
+ if (isEdit.value || isView.value) return;
+ openOutboundSelectDialog();
+};
+
+const restoreOutboundTableSelection = () => {
+ nextTick(() => {
+ const table = outboundTableRef.value;
+ if (!table) return;
+ table.clearSelection();
+ const selectedIds = new Set((form.outboundBatchNos || []).map((id) => String(id)));
+ outboundBatchList.value.forEach((row) => {
+ const rowId = getOutboundRowId(row);
+ if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) {
+ table.toggleRowSelection(row, true);
+ }
+ });
+ });
+};
+
+const openOutboundSelectDialog = () => {
+ if (!form.customerId || isEdit.value || isView.value) return;
+ outboundSelectVisible.value = true;
+ loadOutboundBatches(form.customerId, true).then(() => {
+ restoreOutboundTableSelection();
+ });
+};
+
+const handleOutboundDialogSelectionChange = (selection) => {
+ dialogOutboundSelection.value = selection;
+};
+
+const confirmOutboundSelection = () => {
+ if (dialogOutboundSelection.value.length === 0) {
+ ElMessage.warning("璇疯嚦灏戦�夋嫨涓�鏉″嚭搴撳崟");
+ return;
+ }
+ const prevIds = [...(form.outboundBatchNos || [])];
+ const prevBatches = form.outboundBatches;
+ form.outboundBatchNos = dialogOutboundSelection.value
+ .map((row) => getOutboundRowId(row))
+ .filter((id) => id !== undefined && id !== null);
+ form.outboundBatches = dialogOutboundSelection.value
+ .map((row) => row.outboundBatches ?? row.batchNo ?? row.shippingNo ?? "")
+ .filter(Boolean)
+ .join("銆�");
+ if (!checkTaxRateConsistency()) {
+ form.outboundBatchNos = prevIds;
+ form.outboundBatches = prevBatches;
+ return;
+ }
+ outboundSelectVisible.value = false;
syncInvoiceAmount();
- checkTaxRateConsistency();
+ formRef.value?.validateField("outboundBatchNos");
+};
+
+const handleOutboundDialogClosed = () => {
+ dialogOutboundSelection.value = [];
};
const loadOutboundBatches = (customerId, keepSelected = false) => {
if (!customerId) {
+ outboundBatchList.value = [];
outboundBatchOptions.value = [];
if (!keepSelected) {
form.outboundBatchNos = [];
@@ -358,12 +486,15 @@
.then((res) => {
if (res.code === 200) {
const list = res.data?.records ?? res.data ?? [];
+ outboundBatchList.value = Array.isArray(list) ? list : [];
outboundBatchOptions.value = normalizeOutboundBatchOptions(list);
} else {
+ outboundBatchList.value = [];
outboundBatchOptions.value = [];
}
})
.catch(() => {
+ outboundBatchList.value = [];
outboundBatchOptions.value = [];
})
.finally(() => {
@@ -377,6 +508,7 @@
const handleCustomerChange = (customerId) => {
form.outboundBatchNos = [];
+ form.outboundBatches = "";
form.amount = 0;
loadOutboundBatches(customerId);
};
@@ -385,9 +517,10 @@
applyCode: "",
customerId: "",
outboundBatchNos: [],
+ outboundBatches: "",
amount: 0,
taxRate: 13,
- invoiceType: "special",
+ invoiceType: "澧炲�肩◣涓撶敤鍙戠エ",
applyDate: "",
content: "",
remark: "",
@@ -400,12 +533,6 @@
taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
invoiceType: [{ required: true, message: "璇烽�夋嫨鍙戠エ绫诲瀷", trigger: "change" }],
applyDate: [{ required: true, message: "璇烽�夋嫨鐢宠鏃ユ湡", trigger: "change" }],
-};
-
-const INVOICE_TYPE_LABEL_MAP = {
- special: "澧炲�肩◣涓撶敤鍙戠エ",
- normal: "澧炲�肩◣鏅�氬彂绁�",
- electronic: "鐢靛瓙鍙戠エ",
};
/** 瀹℃牳鐘舵�侊細0寰呭鏍� 1瀹℃牳閫氳繃 2瀹℃牳涓嶉�氳繃 */
@@ -421,8 +548,6 @@
2: "danger",
};
-const getInvoiceTypeLabel = (type) => INVOICE_TYPE_LABEL_MAP[type] || type || "";
-
const normalizeStatus = (status) => {
if (status === undefined || status === null || status === "") return status;
const num = Number(status);
@@ -432,13 +557,32 @@
const isPendingStatus = (status) => normalizeStatus(status) === 0;
const isApprovedStatus = (status) => normalizeStatus(status) === 1;
+const fileDialogVisible = ref(false);
+const currentRecordId = ref(0);
+
+const openFileDialog = (row) => {
+ currentRecordId.value = row.id;
+ fileDialogVisible.value = true;
+};
+
+const formatOutboundBatches = (value) => {
+ if (value === undefined || value === null || value === "") return "";
+ if (Array.isArray(value)) return value.filter(Boolean).join("銆�");
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .join("銆�");
+};
+
const normalizeTableRow = (row) => ({
...row,
applyCode: row.invoiceApplicationNo ?? row.applyCode,
amount: row.invoiceAmount ?? row.amount,
content: row.invoiceContent ?? row.content,
status: normalizeStatus(row.status ?? row.auditStatus),
- invoiceTypeLabel: row.invoiceTypeLabel || getInvoiceTypeLabel(row.invoiceType),
+ stockOutRecordIds: row.stockOutRecordIds ?? row.stockOutRecordId ?? "",
+ outboundBatches: formatOutboundBatches(row.outboundBatches),
});
const appendFilterParams = (params) => {
@@ -553,14 +697,15 @@
const fillFormFromRow = (row) => {
const outboundBatchNos = Array.isArray(row.outboundBatchNos)
? row.outboundBatchNos
- : parseStockOutRecordIds(row.stockOutRecordIds ?? row.outboundBatches);
+ : parseStockOutRecordIds(row.stockOutRecordIds ?? row.stockOutRecordId);
Object.assign(form, {
...row,
applyCode: row.applyCode ?? row.invoiceApplicationNo ?? "",
- amount: row.amount ?? row.invoiceAmount,
+ amount: Number(row.amount ?? row.invoiceAmount ?? 0),
content: row.content ?? row.invoiceContent,
status: normalizeStatus(row.status ?? row.auditStatus),
outboundBatchNos,
+ outboundBatches: formatOutboundBatches(row.outboundBatches),
});
};
@@ -572,13 +717,15 @@
applyCode: "KP" + Date.now().toString().slice(-8),
customerId: "",
outboundBatchNos: [],
+ outboundBatches: "",
amount: 0,
taxRate: 13,
- invoiceType: "special",
- applyDate: new Date().toISOString().split('T')[0],
+ invoiceType: "澧炲�肩◣涓撶敤鍙戠エ",
+ applyDate: new Date().toISOString().split("T")[0],
content: "",
remark: "",
});
+ outboundBatchList.value = [];
outboundBatchOptions.value = [];
dialogVisible.value = true;
};
@@ -619,7 +766,6 @@
dialogTitle.value = "缂栬緫寮�绁ㄧ敵璇�";
fillFormFromRow(row);
dialogVisible.value = true;
- loadOutboundBatches(form.customerId, true);
};
const view = (row) => {
@@ -628,7 +774,6 @@
dialogTitle.value = "鏌ョ湅寮�绁ㄧ敵璇�";
fillFormFromRow(row);
dialogVisible.value = true;
- loadOutboundBatches(form.customerId, true);
};
const submitAudit = (row, status) => {
@@ -747,4 +892,10 @@
color: #409eff;
font-weight: bold;
}
+
+.outbound-batch-input:not(.is-disabled) {
+ :deep(.el-input__wrapper) {
+ cursor: pointer;
+ }
+}
</style>
diff --git a/src/views/financialManagement/receivable/outputInvoice.vue b/src/views/financialManagement/receivable/outputInvoice.vue
index 3e597db..d746aea 100644
--- a/src/views/financialManagement/receivable/outputInvoice.vue
+++ b/src/views/financialManagement/receivable/outputInvoice.vue
@@ -1,19 +1,35 @@
<template>
<div class="app-container">
<el-form :model="filters" :inline="true">
- <el-form-item label="鍙戠エ浠g爜:">
- <el-input v-model="filters.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" clearable style="width: 200px;" />
- </el-form-item>
<el-form-item label="鍙戠エ鍙风爜:">
- <el-input v-model="filters.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
+ <el-input v-model="filters.invoiceNumber" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="瀹㈡埛:">
<el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
- <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="寮�绁ㄦ棩鏈�:">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ clearable
+ style="width: 240px;"
+ />
+ </el-form-item>
+ <el-form-item label="鐘舵��:">
+ <el-select v-model="filters.status" placeholder="璇烽�夋嫨鐘舵��" clearable style="width: 150px;">
+ <el-option label="姝e父" :value="0" />
+ <el-option label="浣滃簾" :value="1" />
</el-select>
</el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
@@ -21,13 +37,13 @@
<div class="actions">
<div></div>
<div>
- <el-button type="primary" @click="add" icon="Plus">褰曞叆鍙戠エ</el-button>
- <el-button @click="handleImport" icon="Upload">瀵煎叆</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ <!-- <el-button type="primary" @click="add" icon="Plus">褰曞叆鍙戠エ</el-button> -->
+ <el-button type="success" @click="handleExport" icon="Download">瀵煎嚭</el-button>
</div>
</div>
<PIMTable
rowKey="id"
+ v-loading="tableLoading"
:column="columns"
:tableData="dataList"
:page="{
@@ -46,58 +62,98 @@
<template #totalAmount="{ row }">
<span class="text-success">楼{{ formatMoney(row.totalAmount) }}</span>
</template>
- <template #invoiceType="{ row }">
- <el-tag :type="row.invoiceType === 'special' ? 'danger' : 'primary'">{{ row.invoiceTypeLabel }}</el-tag>
+ <template #status="{ row }">
+ <el-tag :type="getStatusType(row.status)" effect="light" round>
+ {{ getStatusLabel(row.status) }}
+ </el-tag>
</template>
<template #operation="{ row }">
<el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
- <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
- <el-button type="danger" link @click="handleDelete(row)">浣滃簾</el-button>
+ <el-button
+ type="primary"
+ link
+ @click="openFileDialog(row)"
+ v-if="row.accountInvoiceApplicationId"
+ >
+ 闄勪欢
+ </el-button>
+ <el-button type="warning" link @click="handleCancel(row)" v-if="isNormalStatus(row.status)">浣滃簾</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
- <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <FormDialog
+ :title="dialogTitle"
+ v-model="dialogVisible"
+ width="800px"
+ :operation-type="isView ? 'detail' : ''"
+ @confirm="submitForm"
+ @cancel="closeDialog"
+ >
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
- <el-row :gutter="20">
+ <el-row v-if="isView" :gutter="20">
<el-col :span="12">
- <el-form-item label="鍙戠エ浠g爜" prop="invoiceCode">
- <el-input v-model="form.invoiceCode" placeholder="璇疯緭鍏ュ彂绁ㄤ唬鐮�" />
+ <el-form-item label="鐘舵��">
+ <el-tag :type="getStatusType(form.status)" effect="light" round>
+ {{ getStatusLabel(form.status) }}
+ </el-tag>
</el-form-item>
</el-col>
+ </el-row>
+ <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="鍙戠エ鍙风爜" prop="invoiceNo">
- <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" />
+ <el-input v-model="form.invoiceNo" placeholder="璇疯緭鍏ュ彂绁ㄥ彿鐮�" :disabled="isView" />
</el-form-item>
</el-col>
- </el-row>
- <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="瀹㈡埛" prop="customerId">
- <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;">
- <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isView">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
</el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
- <el-date-picker v-model="form.invoiceDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
- <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
- <el-select v-model="form.invoiceType" placeholder="璇烽�夋嫨鍙戠エ绫诲瀷" style="width: 100%;" @change="handleInvoiceTypeChange">
- <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="special" />
- <el-option label="澧炲�肩◣鏅�氬彂绁�" value="normal" />
- <el-option label="鐢靛瓙鍙戠エ" value="electronic" />
- </el-select>
+ <el-form-item label="寮�绁ㄦ棩鏈�" prop="invoiceDate">
+ <el-date-picker
+ v-model="form.invoiceDate"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%;"
+ :disabled="isView"
+ />
</el-form-item>
</el-col>
<el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷" prop="invoiceType">
+ <el-select
+ v-model="form.invoiceType"
+ placeholder="璇烽�夋嫨鍙戠エ绫诲瀷"
+ style="width: 100%;"
+ :disabled="isView"
+ @change="handleInvoiceTypeChange"
+ >
+ <el-option label="澧炲�肩◣涓撶敤鍙戠エ" value="澧炲�肩◣涓撶敤鍙戠エ" />
+ <el-option label="澧炲�肩◣鏅�氬彂绁�" value="澧炲�肩◣鏅�氬彂绁�" />
+ <el-option label="鐢靛瓙鍙戠エ" value="鐢靛瓙鍙戠エ" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
<el-form-item label="绋庣巼" prop="taxRate">
- <el-select v-model="form.taxRate" placeholder="璇烽�夋嫨绋庣巼" style="width: 100%;" @change="calculateTax">
+ <el-select
+ v-model="form.taxRate"
+ placeholder="璇烽�夋嫨绋庣巼"
+ style="width: 100%;"
+ :disabled="isView"
+ @change="calculateTax"
+ >
<el-option
v-for="dict in tax_rate"
:key="dict.value"
@@ -111,7 +167,14 @@
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="閲戦(涓嶅惈绋�)" prop="amount">
- <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" />
+ <el-input-number
+ v-model="form.amount"
+ :min="0"
+ :precision="2"
+ style="width: 100%;"
+ :disabled="isView"
+ @change="calculateTax"
+ />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -126,24 +189,41 @@
</el-col>
</el-row>
<el-form-item label="鍙戠エ鍐呭" prop="content">
- <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" />
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ彂绁ㄥ唴瀹�" :disabled="isView" />
</el-form-item>
<el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" />
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="璇疯緭鍏ュ娉�" :disabled="isView" />
</el-form-item>
</el-form>
- <template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <template v-if="!isView" #footer>
+ <el-button type="primary" :loading="submitLoading" @click="submitForm">纭畾</el-button>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
</template>
</FormDialog>
+
+ <FileList
+ v-if="fileDialogVisible"
+ v-model:visible="fileDialogVisible"
+ record-type="account_invoice_application"
+ :record-id="currentRecordId"
+ :editable="false"
+ />
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, computed, getCurrentInstance } from "vue";
+import { ref, reactive, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { listCustomer } from "@/api/basicData/customer.js";
+import {
+ addAccountSalesInvoice,
+ listPageAccountSalesInvoice,
+ cancelAccountSalesInvoice,
+ deleteAccountSalesInvoice,
+} from "@/api/financialManagement/accountSalesInvoice.js";
+
+const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
defineOptions({
name: "閿�椤瑰彂绁�",
@@ -153,9 +233,10 @@
const { tax_rate } = proxy.useDict("tax_rate");
const filters = reactive({
- invoiceCode: "",
- invoiceNo: "",
+ invoiceNumber: "",
customerId: "",
+ dateRange: [],
+ status: "",
});
const pagination = reactive({
@@ -165,47 +246,76 @@
});
const columns = [
- { label: "鍙戠エ浠g爜", prop: "invoiceCode", width: "130" },
- { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "120" },
+ { label: "鍙戠エ鍙风爜", prop: "invoiceNo", width: "140" },
{ label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
{ label: "寮�绁ㄦ棩鏈�", prop: "invoiceDate", width: "120" },
- { label: "閲戦", prop: "amount", slot: "amount" },
- { label: "绋庨", prop: "taxAmount", slot: "taxAmount" },
- { label: "浠风◣鍚堣", prop: "totalAmount", slot: "totalAmount" },
- { label: "鍙戠エ绫诲瀷", prop: "invoiceType", slot: "invoiceType" },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "180", fixed: "right" },
+ { label: "閲戦", prop: "amount", dataType: "slot", slot: "amount" },
+ { label: "绋庨", prop: "taxAmount", dataType: "slot", slot: "taxAmount" },
+ { label: "浠风◣鍚堣", prop: "totalAmount", dataType: "slot", slot: "totalAmount" },
+ { label: "鍙戠エ绫诲瀷", prop: "invoiceType", width: "130" },
+ { label: "鐘舵��", prop: "status", dataType: "slot", slot: "status", width: "90", align: "center" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "260", fixed: "right" },
];
const dataList = ref([]);
+const tableLoading = ref(false);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
-const isEdit = ref(false);
-const currentId = ref(null);
+const isView = ref(false);
+const submitLoading = ref(false);
-const customerList = [
- { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
- { id: 2, name: "涓婃捣璐告槗鍏徃" },
- { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
- { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
-];
+const customerList = ref([]);
+const fileDialogVisible = ref(false);
+const currentRecordId = ref(0);
+
+const openFileDialog = (row) => {
+ if (!row.accountInvoiceApplicationId) {
+ ElMessage.warning("鏈叧鑱斿紑绁ㄧ敵璇凤紝鏃犳硶鏌ョ湅闄勪欢");
+ return;
+ }
+ currentRecordId.value = row.accountInvoiceApplicationId;
+ fileDialogVisible.value = true;
+};
+
+/** 鐘舵�侊細0姝e父 1浣滃簾 */
+const STATUS_LABEL_MAP = { 0: "姝e父", 1: "浣滃簾" };
+const STATUS_TYPE_MAP = { 0: "success", 1: "info" };
+
+const normalizeStatus = (status) => {
+ if (status === undefined || status === null || status === "") return 0;
+ const num = Number(status);
+ return Number.isNaN(num) ? 0 : num;
+};
+
+const isNormalStatus = (status) => normalizeStatus(status) === 0;
+
+const getStatusLabel = (status) => {
+ const num = normalizeStatus(status);
+ return STATUS_LABEL_MAP[num] ?? "姝e父";
+};
+
+const getStatusType = (status) => {
+ const num = normalizeStatus(status);
+ return STATUS_TYPE_MAP[num] ?? "success";
+};
const form = reactive({
- invoiceCode: "",
invoiceNo: "",
customerId: "",
invoiceDate: "",
- invoiceType: "special",
+ invoiceType: "澧炲�肩◣涓撶敤鍙戠エ",
taxRate: 13,
amount: 0,
taxAmount: 0,
totalAmount: 0,
content: "",
remark: "",
+ accountInvoiceApplicationId: undefined,
+ storageAttachmentId: undefined,
});
const rules = {
- invoiceCode: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄤ唬鐮�", trigger: "blur" }],
invoiceNo: [{ required: true, message: "璇疯緭鍏ュ彂绁ㄥ彿鐮�", trigger: "blur" }],
customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
invoiceDate: [{ required: true, message: "璇烽�夋嫨寮�绁ㄦ棩鏈�", trigger: "change" }],
@@ -213,12 +323,6 @@
taxRate: [{ required: true, message: "璇烽�夋嫨绋庣巼", trigger: "change" }],
amount: [{ required: true, message: "璇疯緭鍏ラ噾棰�", trigger: "blur" }],
};
-
-const mockData = [
- { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", invoiceDate: "2024-01-15", amount: 5000, taxRate: 13, taxAmount: 650, totalAmount: 5650, invoiceType: "special", invoiceTypeLabel: "澧炲�肩◣涓撶敤鍙戠エ", content: "杞欢鏈嶅姟璐�", remark: "" },
- { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", invoiceDate: "2024-01-16", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, invoiceType: "normal", invoiceTypeLabel: "澧炲�肩◣鏅�氬彂绁�", content: "鍟嗗搧閿�鍞�", remark: "" },
- { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", invoiceDate: "2024-01-18", amount: 12000, taxRate: 6, taxAmount: 720, totalAmount: 12720, invoiceType: "electronic", invoiceTypeLabel: "鐢靛瓙鍙戠エ", content: "鎶�鏈湇鍔¤垂", remark: "" },
-];
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
@@ -231,33 +335,142 @@
};
const handleInvoiceTypeChange = () => {
- if (form.invoiceType === "special") {
- form.taxRate = 13;
- } else {
- form.taxRate = 13;
- }
calculateTax();
};
-const getTableData = () => {
- let result = [...mockData];
- if (filters.invoiceCode) {
- result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode));
- }
- if (filters.invoiceNo) {
- result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo));
+const normalizeTableRow = (row) => ({
+ ...row,
+ invoiceNo: row.invoiceNumber ?? row.invoiceNo,
+ invoiceDate: row.issueDate ?? row.invoiceDate,
+ amount: row.taxExclusivelPrice ?? row.amount,
+ taxAmount: row.taxPrice ?? row.taxAmount,
+ totalAmount: row.taxInclusivePrice ?? row.totalAmount,
+ content: row.invoiceContent ?? row.content,
+ status: normalizeStatus(row.status),
+});
+
+const fillFormFromRow = (row) => {
+ Object.assign(form, {
+ invoiceNo: row.invoiceNo ?? row.invoiceNumber ?? "",
+ customerId: row.customerId,
+ invoiceDate: row.invoiceDate ?? row.issueDate ?? "",
+ invoiceType: row.invoiceType ?? "澧炲�肩◣涓撶敤鍙戠エ",
+ taxRate: row.taxRate ?? 13,
+ amount: row.amount ?? row.taxExclusivelPrice ?? 0,
+ taxAmount: row.taxAmount ?? row.taxPrice ?? 0,
+ totalAmount: row.totalAmount ?? row.taxInclusivePrice ?? 0,
+ content: row.content ?? row.invoiceContent ?? "",
+ remark: row.remark ?? "",
+ accountInvoiceApplicationId: row.accountInvoiceApplicationId,
+ storageAttachmentId: row.storageAttachmentId,
+ status: normalizeStatus(row.status),
+ });
+};
+
+const buildCancelPayload = (row) => ({
+ id: row.id,
+ accountInvoiceApplicationId: row.accountInvoiceApplicationId,
+ invoiceNumber: row.invoiceNumber ?? row.invoiceNo,
+ taxRate: row.taxRate,
+ invoiceType: row.invoiceType,
+ issueDate: row.issueDate ?? row.invoiceDate,
+ taxExclusivelPrice: row.taxExclusivelPrice ?? row.amount,
+ taxPrice: row.taxPrice ?? row.taxAmount,
+ taxInclusivePrice: row.taxInclusivePrice ?? row.totalAmount,
+ remark: row.remark ?? "",
+ invoiceContent: row.invoiceContent ?? row.content,
+ customerId: row.customerId,
+ storageAttachmentId: row.storageAttachmentId,
+ status: 1,
+});
+
+const buildSubmitPayload = () => ({
+ invoiceNumber: form.invoiceNo,
+ customerId: form.customerId,
+ issueDate: form.invoiceDate,
+ invoiceType: form.invoiceType,
+ taxRate: form.taxRate,
+ taxExclusivelPrice: form.amount,
+ taxPrice: form.taxAmount,
+ taxInclusivePrice: form.totalAmount,
+ invoiceContent: form.content,
+ remark: form.remark || "",
+ accountInvoiceApplicationId: form.accountInvoiceApplicationId,
+ storageAttachmentId: form.storageAttachmentId,
+});
+
+const getCustomerList = () => {
+ listCustomer({ current: -1, size: -1, type: 0 }).then((res) => {
+ if (res.code === 200) {
+ customerList.value = res.data?.records || [];
+ }
+ });
+};
+
+const appendFilterParams = (params) => {
+ if (filters.invoiceNumber) {
+ params.invoiceNumber = filters.invoiceNumber;
}
if (filters.customerId) {
- result = result.filter(item => item.customerId === filters.customerId);
+ params.customerId = filters.customerId;
}
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ if (filters.dateRange?.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ if (filters.status !== "" && filters.status != null) {
+ params.status = filters.status;
+ }
+ return params;
+};
+
+const buildListParams = () =>
+ appendFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => appendFilterParams({});
+
+const handleExport = () => {
+ const params = buildExportParams();
+ proxy.download("/accountSalesInvoice/exportAccountSalesInvoice", params, `閿�椤瑰彂绁╛${Date.now()}.xlsx`);
+};
+
+const getTableData = () => {
+ tableLoading.value = true;
+ listPageAccountSalesInvoice(buildListParams())
+ .then((res) => {
+ if (res.code === 200) {
+ const records = res.data?.records ?? [];
+ dataList.value = records.map(normalizeTableRow);
+ pagination.total = res.data?.total ?? 0;
+ } else {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+const onSearch = () => {
+ pagination.currentPage = 1;
+ getTableData();
};
const resetFilters = () => {
- filters.invoiceCode = "";
- filters.invoiceNo = "";
+ filters.invoiceNumber = "";
filters.customerId = "";
+ filters.dateRange = [];
+ filters.status = "";
pagination.currentPage = 1;
getTableData();
};
@@ -268,83 +481,105 @@
getTableData();
};
+const closeDialog = () => {
+ dialogVisible.value = false;
+ isView.value = false;
+};
+
const add = () => {
- isEdit.value = false;
+ isView.value = false;
dialogTitle.value = "褰曞叆鍙戠エ";
Object.assign(form, {
- invoiceCode: "",
invoiceNo: "",
customerId: "",
- invoiceDate: new Date().toISOString().split('T')[0],
- invoiceType: "special",
+ invoiceDate: new Date().toISOString().split("T")[0],
+ invoiceType: "澧炲�肩◣涓撶敤鍙戠エ",
taxRate: 13,
amount: 0,
taxAmount: 0,
totalAmount: 0,
content: "",
remark: "",
+ accountInvoiceApplicationId: undefined,
+ storageAttachmentId: undefined,
});
- dialogVisible.value = true;
-};
-
-const edit = (row) => {
- isEdit.value = true;
- currentId.value = row.id;
- dialogTitle.value = "缂栬緫鍙戠エ";
- Object.assign(form, row);
dialogVisible.value = true;
};
const view = (row) => {
- ElMessage.info(`鏌ョ湅鍙戠エ: ${row.invoiceCode}-${row.invoiceNo}`);
+ isView.value = true;
+ dialogTitle.value = "鏌ョ湅鍙戠エ";
+ fillFormFromRow(row);
+ dialogVisible.value = true;
};
-const handleDelete = (row) => {
- ElMessageBox.confirm("纭浣滃簾璇ュ彂绁ㄥ悧锛�", "鎻愮ず", {
+const handleCancel = (row) => {
+ ElMessageBox.confirm(`纭浣滃簾鍙戠エ銆�${row.invoiceNo ?? row.invoiceNumber}銆嶅悧锛焋, "浣滃簾纭", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
}).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData.splice(index, 1);
- }
- ElMessage.success("浣滃簾鎴愬姛");
- getTableData();
+ cancelAccountSalesInvoice(buildCancelPayload(row))
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("浣滃簾鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "浣滃簾澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("浣滃簾澶辫触");
+ });
});
};
-const handleImport = () => {
- ElMessage.info("瀵煎叆鍔熻兘");
-};
-
-const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎鍙戠エ銆�${row.invoiceNo ?? row.invoiceNumber}銆嶅悧锛熷垹闄ゅ悗涓嶅彲鎭㈠銆俙, "鍒犻櫎纭", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ deleteAccountSalesInvoice([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ });
};
const submitForm = () => {
formRef.value.validate((valid) => {
- if (valid) {
- const customer = customerList.find(item => item.id === form.customerId);
- const invoiceTypeMap = { special: "澧炲�肩◣涓撶敤鍙戠エ", normal: "澧炲�肩◣鏅�氬彂绁�", electronic: "鐢靛瓙鍙戠エ" };
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] };
+ if (!valid) return;
+ submitLoading.value = true;
+ addAccountSalesInvoice(buildSubmitPayload())
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("褰曞叆鎴愬姛");
+ closeDialog();
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "褰曞叆澶辫触");
}
- ElMessage.success("缂栬緫鎴愬姛");
- } else {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] });
- ElMessage.success("褰曞叆鎴愬姛");
- }
- dialogVisible.value = false;
- getTableData();
- }
+ })
+ .catch(() => {
+ ElMessage.error("褰曞叆澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
});
};
onMounted(() => {
+ getCustomerList();
getTableData();
});
</script>
diff --git a/src/views/financialManagement/receivable/receipt.vue b/src/views/financialManagement/receivable/receipt.vue
index 2bbbb96..f19da42 100644
--- a/src/views/financialManagement/receivable/receipt.vue
+++ b/src/views/financialManagement/receivable/receipt.vue
@@ -2,40 +2,54 @@
<div class="app-container">
<el-form :model="filters" :inline="true">
<el-form-item label="鏀舵鍗曞彿:">
- <el-input v-model="filters.receiptCode" placeholder="璇疯緭鍏ユ敹娆惧崟鍙�" clearable style="width: 200px;" />
+ <el-input v-model="filters.collectionNumber" placeholder="璇疯緭鍏ユ敹娆惧崟鍙�" clearable style="width: 200px;" />
</el-form-item>
<el-form-item label="瀹㈡埛:">
- <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
- <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable filterable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="鏀舵鏂瑰紡:">
- <el-select v-model="filters.receiptMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" clearable style="width: 150px;">
- <el-option label="閾惰杞处" value="bank_transfer" />
- <el-option label="鐜伴噾" value="cash" />
- <el-option label="鏀エ" value="check" />
- <el-option label="姹囩エ" value="draft" />
- <el-option label="鏀粯瀹�" value="alipay" />
- <el-option label="寰俊" value="wechat" />
+ <el-select v-model="filters.collectionMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" clearable style="width: 150px;">
+ <el-option
+ v-for="item in payment_methods"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
</el-select>
</el-form-item>
+ <el-form-item label="鏀舵鏃ユ湡:">
+ <el-date-picker
+ v-model="filters.dateRange"
+ type="daterange"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ clearable
+ style="width: 240px;"
+ />
+ </el-form-item>
<el-form-item>
- <el-button type="primary" @click="getTableData">鎼滅储</el-button>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
<el-button @click="resetFilters">閲嶇疆</el-button>
</el-form-item>
</el-form>
<div class="table_list">
<div class="actions">
<div>
- <el-statistic title="鏈湡鏀舵鍚堣" :value="totalReceiptAmount" precision="2" prefix="楼" />
+ <el-statistic title="鏈〉鏀舵鍚堣" :value="totalReceiptAmount" :precision="2" prefix="楼" />
</div>
<div>
<el-button type="primary" @click="add" icon="Plus">鏂板鏀舵</el-button>
- <el-button @click="handleOut" icon="Download">瀵煎嚭</el-button>
+ <el-button type="success" @click="handleExport" icon="Download">瀵煎嚭</el-button>
</div>
</div>
<PIMTable
rowKey="id"
+ v-loading="tableLoading"
:column="columns"
:tableData="dataList"
:page="{
@@ -49,97 +63,192 @@
<span class="text-success">楼{{ formatMoney(row.amount) }}</span>
</template>
<template #receiptMethod="{ row }">
- <el-tag>{{ getReceiptMethodLabel(row.receiptMethod) }}</el-tag>
- </template>
- <template #status="{ row }">
- <el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'">{{ row.status === 'confirmed' ? '宸茬‘璁�' : '寰呯‘璁�' }}</el-tag>
+ <span>{{ getReceiptMethodLabel(row.receiptMethod) }}</span>
</template>
<template #operation="{ row }">
<el-button type="primary" link @click="view(row)">鏌ョ湅</el-button>
- <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">缂栬緫</el-button>
- <el-button type="success" link @click="handleConfirm(row)" v-if="row.status === 'pending'">纭</el-button>
- <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">鍒犻櫎</el-button>
+ <el-button type="primary" link @click="edit(row)">缂栬緫</el-button>
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
- <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false">
+ <FormDialog
+ :title="dialogTitle"
+ v-model="dialogVisible"
+ width="800px"
+ :operation-type="isView ? 'detail' : ''"
+ @confirm="submitForm"
+ @cancel="closeDialog"
+ >
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-row :gutter="20">
- <el-col :span="12">
+ <el-col :span="24">
<el-form-item label="鏀舵鍗曞彿" prop="receiptCode">
<el-input v-model="form.receiptCode" placeholder="绯荤粺鑷姩鐢熸垚" disabled />
</el-form-item>
</el-col>
+ </el-row>
+ <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="瀹㈡埛" prop="customerId">
- <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" :disabled="isEdit">
- <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select
+ v-model="form.customerId"
+ placeholder="璇烽�夋嫨瀹㈡埛"
+ style="width: 100%;"
+ :disabled="isEdit || isView"
+ filterable
+ @change="handleCustomerChange"
+ >
+ <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
</el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍏宠仈鍗曟嵁" prop="stockOutRecordIds">
+ <el-input
+ :model-value="outboundBatchDisplayText"
+ placeholder="璇峰厛閫夋嫨瀹㈡埛"
+ readonly
+ :disabled="!form.customerId || isEdit || isView"
+ class="outbound-batch-input"
+ @click="handleOutboundInputClick"
+ >
+ <template v-if="!isEdit && !isView" #append>
+ <el-button
+ :disabled="!form.customerId"
+ :loading="outboundBatchLoading"
+ @click.stop="openOutboundSelectDialog"
+ >
+ 閫夋嫨
+ </el-button>
+ </template>
+ </el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="鏀舵鏃ユ湡" prop="receiptDate">
- <el-date-picker v-model="form.receiptDate" type="date" placeholder="閫夋嫨鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%;" />
+ <el-date-picker
+ v-model="form.receiptDate"
+ type="date"
+ placeholder="閫夋嫨鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ style="width: 100%;"
+ :disabled="isView"
+ />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="鏀舵閲戦" prop="amount">
- <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" />
+ <el-input-number
+ v-model="form.amount"
+ :min="0"
+ :precision="2"
+ style="width: 100%;"
+ :disabled="isView"
+ placeholder="鏍规嵁鍏宠仈鍗曟嵁鑷姩姹囨�伙紝鍙慨鏀�"
+ />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="鏀舵鏂瑰紡" prop="receiptMethod">
- <el-select v-model="form.receiptMethod" placeholder="璇烽�夋嫨鏀舵鏂瑰紡" style="width: 100%;">
- <el-option label="閾惰杞处" value="bank_transfer" />
- <el-option label="鐜伴噾" value="cash" />
- <el-option label="鏀エ" value="check" />
- <el-option label="姹囩エ" value="draft" />
- <el-option label="鏀粯瀹�" value="alipay" />
- <el-option label="寰俊" value="wechat" />
+ <el-select
+ v-model="form.receiptMethod"
+ placeholder="璇烽�夋嫨鏀舵鏂瑰紡"
+ style="width: 100%;"
+ :disabled="isView"
+ >
+ <el-option
+ v-for="item in payment_methods"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
</el-select>
</el-form-item>
</el-col>
- <el-col :span="12">
- <el-form-item label="閾惰璐﹀彿" prop="bankAccount" v-if="form.receiptMethod === 'bank_transfer'">
- <el-input v-model="form.bankAccount" placeholder="璇疯緭鍏ラ摱琛岃处鍙�" />
- </el-form-item>
- </el-col>
</el-row>
- <el-form-item label="鍏宠仈鍗曟嵁" prop="relatedDocs">
- <el-select v-model="form.relatedDocs" multiple placeholder="璇烽�夋嫨鍏宠仈鍗曟嵁" style="width: 100%;">
- <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" />
- </el-select>
- </el-form-item>
<el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" />
+ <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="璇疯緭鍏ュ娉�" :disabled="isView" />
</el-form-item>
</el-form>
- <template #footer>
- <el-button type="primary" @click="submitForm">纭畾</el-button>
- <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <template v-if="!isView" #footer>
+ <el-button type="primary" :loading="submitLoading" @click="submitForm">纭畾</el-button>
+ <el-button @click="closeDialog">鍙栨秷</el-button>
</template>
</FormDialog>
+
+ <el-dialog
+ v-model="outboundSelectVisible"
+ title="閫夋嫨鍏宠仈鍗曟嵁"
+ width="1200px"
+ append-to-body
+ destroy-on-close
+ :close-on-click-modal="false"
+ @closed="handleOutboundDialogClosed"
+ >
+ <el-table
+ ref="outboundTableRef"
+ v-loading="outboundBatchLoading"
+ :data="outboundBatchList"
+ row-key="id"
+ border
+ stripe
+ max-height="480"
+ @selection-change="handleOutboundDialogSelectionChange"
+ >
+ <el-table-column type="selection" width="55" align="center" />
+ <el-table-column prop="outboundBatches" label="鍑哄簱鍗曞彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="120" show-overflow-tooltip />
+ <el-table-column prop="specificationModel" label="瑙勬牸鍨嬪彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="salesContractNo" label="閿�鍞悎鍚屽彿" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="shippingNo" label="鍙戣揣鍗曞彿" min-width="130" show-overflow-tooltip />
+ <el-table-column prop="shippingDate" label="鍙戣揣鏃ユ湡" width="110" align="center" />
+ <el-table-column prop="outboundAmount" label="鍑哄簱閲戦" width="110" align="right">
+ <template #default="{ row }">楼{{ formatMoney(row.outboundAmount) }}</template>
+ </el-table-column>
+ <el-table-column prop="taxRate" label="绋庣巼" width="80" align="center">
+ <template #default="{ row }">{{ row.taxRate }}%</template>
+ </el-table-column>
+ </el-table>
+ <template #footer>
+ <el-button type="primary" @click="confirmOutboundSelection">纭畾</el-button>
+ <el-button @click="outboundSelectVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
-import { ref, reactive, onMounted, computed } from "vue";
+import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { listCustomer } from "@/api/basicData/customer.js";
+import {
+ getOutboundBatchesByCustomer,
+ addAccountSalesCollection,
+ listPageAccountSalesCollection,
+ updateAccountSalesCollection,
+ deleteAccountSalesCollection,
+} from "@/api/financialManagement/accountSalesCollection.js";
defineOptions({
name: "鏀舵鍗�",
});
+const { proxy } = getCurrentInstance();
+const { payment_methods } = proxy.useDict("payment_methods");
+
const filters = reactive({
- receiptCode: "",
+ collectionNumber: "",
customerId: "",
- receiptMethod: "",
+ collectionMethod: "",
+ dateRange: [],
});
const pagination = reactive({
@@ -152,97 +261,365 @@
{ label: "鏀舵鍗曞彿", prop: "receiptCode", width: "150" },
{ label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
{ label: "鏀舵鏃ユ湡", prop: "receiptDate", width: "120" },
- { label: "鏀舵閲戦", prop: "amount", slot: "amount" },
- { label: "鏀舵鏂瑰紡", prop: "receiptMethod", slot: "receiptMethod" },
- { label: "鐘舵��", prop: "status", slot: "status" },
+ { label: "鏀舵閲戦", prop: "amount", dataType: "slot", slot: "amount" },
+ { label: "鏀舵鏂瑰紡", prop: "receiptMethod", dataType: "slot", slot: "receiptMethod", width: "120" },
{ label: "澶囨敞", prop: "remark", showOverflowTooltip: true },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "220", fixed: "right" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" },
];
const dataList = ref([]);
+const tableLoading = ref(false);
const dialogVisible = ref(false);
const dialogTitle = ref("");
const formRef = ref(null);
const isEdit = ref(false);
+const isView = ref(false);
const currentId = ref(null);
+const submitLoading = ref(false);
-const customerList = [
- { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
- { id: 2, name: "涓婃捣璐告槗鍏徃" },
- { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
- { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
-];
+const customerList = ref([]);
+const outboundBatchList = ref([]);
+const outboundBatchOptions = ref([]);
+const outboundBatchLoading = ref(false);
+const outboundSelectVisible = ref(false);
+const outboundTableRef = ref(null);
+const dialogOutboundSelection = ref([]);
-const outList = [
- { outCode: "CK2024001", customerId: 1 },
- { outCode: "CK2024002", customerId: 2 },
- { outCode: "CK2024003", customerId: 3 },
-];
+const getReceiptMethodLabel = (value) => {
+ if (value === undefined || value === null || value === "") return "-";
+ const item = payment_methods.value?.find((m) => String(m.value) === String(value));
+ return item?.label ?? value;
+};
+
+const getDefaultReceiptMethod = () => payment_methods.value?.[0]?.value ?? "";
const form = reactive({
receiptCode: "",
customerId: "",
receiptDate: "",
amount: 0,
- receiptMethod: "bank_transfer",
- bankAccount: "",
- relatedDocs: [],
+ receiptMethod: "",
+ stockOutRecordIds: [],
+ outboundBatches: "",
remark: "",
});
const rules = {
customerId: [{ required: true, message: "璇烽�夋嫨瀹㈡埛", trigger: "change" }],
+ stockOutRecordIds: [{ required: true, type: "array", min: 1, message: "璇烽�夋嫨鍏宠仈鍗曟嵁", trigger: "change" }],
receiptDate: [{ required: true, message: "璇烽�夋嫨鏀舵鏃ユ湡", trigger: "change" }],
amount: [{ required: true, message: "璇疯緭鍏ユ敹娆鹃噾棰�", trigger: "blur" }],
receiptMethod: [{ required: true, message: "璇烽�夋嫨鏀舵鏂瑰紡", trigger: "change" }],
};
-const mockData = [
- { id: 1, receiptCode: "SK2024001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", receiptDate: "2024-01-16", amount: 3000, receiptMethod: "bank_transfer", status: "confirmed", relatedDocs: ["CK2024001"], remark: "" },
- { id: 2, receiptCode: "SK2024002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", receiptDate: "2024-01-18", amount: 5000, receiptMethod: "cash", status: "pending", relatedDocs: ["CK2024002"], remark: "" },
- { id: 3, receiptCode: "SK2024003", customerId: 3, customerName: "骞垮窞瀹炰笟鏈夐檺鍏徃", receiptDate: "2024-01-20", amount: 8000, receiptMethod: "alipay", status: "confirmed", relatedDocs: ["CK2024003"], remark: "" },
-];
-
-const totalReceiptAmount = computed(() => {
- return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0);
-});
+const totalReceiptAmount = computed(() =>
+ dataList.value.reduce((sum, item) => sum + Number(item.amount || 0), 0)
+);
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
-const getReceiptMethodLabel = (method) => {
- const map = {
- bank_transfer: "閾惰杞处",
- cash: "鐜伴噾",
- check: "鏀エ",
- draft: "姹囩エ",
- alipay: "鏀粯瀹�",
- wechat: "寰俊",
+const parseStockOutRecordIds = (value) => {
+ if (!value) return [];
+ if (Array.isArray(value)) return value;
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .map((s) => (/^\d+$/.test(s) ? Number(s) : s));
+};
+
+const formatOutboundBatches = (value) => {
+ if (value === undefined || value === null || value === "") return "";
+ if (Array.isArray(value)) return value.filter(Boolean).join("銆�");
+ return String(value)
+ .split(/[,锛宂/)
+ .map((s) => s.trim())
+ .filter(Boolean)
+ .join("銆�");
+};
+
+const normalizeTableRow = (row) => ({
+ ...row,
+ receiptCode: row.collectionNumber ?? row.receiptCode,
+ receiptDate: row.collectionDate ?? row.receiptDate,
+ amount: row.collectionAmount ?? row.amount,
+ receiptMethod: row.collectionMethod ?? row.receiptMethod ?? "",
+ stockOutRecordIds: row.stockOutRecordIds ?? row.stockOutRecordId ?? "",
+ outboundBatches: formatOutboundBatches(row.outboundBatches),
+});
+
+const getCustomerList = () => {
+ listCustomer({ current: -1, size: -1, type: 0 }).then((res) => {
+ if (res.code === 200) {
+ customerList.value = res.data?.records || [];
+ }
+ });
+};
+
+const normalizeOutboundBatchOptions = (data) => {
+ const list = Array.isArray(data) ? data : [];
+ return list.map((item, index) => {
+ if (typeof item === "string" || typeof item === "number") {
+ const text = String(item);
+ return { label: text, value: text, outboundAmount: 0 };
+ }
+ const label =
+ item.outboundBatches ??
+ item.batchNo ??
+ item.shippingNo ??
+ item.outboundNo ??
+ item.label ??
+ `鍑哄簱鍗�${index + 1}`;
+ const value = item.id ?? item.stockOutRecordId ?? label;
+ return { label: String(label), value, outboundAmount: Number(item.outboundAmount) || 0 };
+ });
+};
+
+const isSameOutboundId = (a, b) => String(a) === String(b);
+
+const getOutboundRowId = (row) => row?.id ?? row?.stockOutRecordId;
+
+const outboundBatchDisplayText = computed(() => {
+ if (isEdit.value || isView.value) {
+ return form.outboundBatches || "";
+ }
+ if (form.outboundBatches) return form.outboundBatches;
+ const ids = form.stockOutRecordIds || [];
+ if (!ids.length) return "";
+ const labels = outboundBatchOptions.value
+ .filter((opt) => ids.some((id) => isSameOutboundId(id, opt.value)))
+ .map((opt) => opt.label);
+ if (labels.length) return labels.join("銆�");
+ return ids.join("銆�");
+});
+
+const handleOutboundInputClick = () => {
+ if (isEdit.value || isView.value) return;
+ openOutboundSelectDialog();
+};
+
+/** 涓哄凡閫� ID 琛ュ叏閫夐」锛堢紪杈�/鏌ョ湅鍥炴樉锛� */
+const ensureOutboundOptionsForSelected = () => {
+ const ids = form.stockOutRecordIds || [];
+ ids.forEach((id) => {
+ const exists = outboundBatchOptions.value.some((opt) => isSameOutboundId(opt.value, id));
+ if (exists) return;
+ const fromList = outboundBatchList.value.find((row) => isSameOutboundId(getOutboundRowId(row), id));
+ if (fromList) {
+ const [option] = normalizeOutboundBatchOptions([fromList]);
+ if (option) outboundBatchOptions.value.push(option);
+ return;
+ }
+ outboundBatchOptions.value.push({
+ label: String(id),
+ value: id,
+ outboundAmount: 0,
+ });
+ });
+};
+
+const syncCollectionAmount = () => {
+ const selected = form.stockOutRecordIds || [];
+ const sum = outboundBatchOptions.value
+ .filter((opt) => selected.some((id) => isSameOutboundId(id, opt.value)))
+ .reduce((acc, opt) => acc + (Number(opt.outboundAmount) || 0), 0);
+ form.amount = sum > 0 ? Number(sum.toFixed(2)) : 0;
+};
+
+const restoreOutboundTableSelection = () => {
+ nextTick(() => {
+ const table = outboundTableRef.value;
+ if (!table) return;
+ table.clearSelection();
+ const selectedIds = new Set((form.stockOutRecordIds || []).map((id) => String(id)));
+ outboundBatchList.value.forEach((row) => {
+ const rowId = getOutboundRowId(row);
+ if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) {
+ table.toggleRowSelection(row, true);
+ }
+ });
+ });
+};
+
+const loadOutboundBatches = (customerId, keepSelected = false) => {
+ if (!customerId) {
+ outboundBatchList.value = [];
+ outboundBatchOptions.value = [];
+ if (!keepSelected) {
+ form.stockOutRecordIds = [];
+ form.amount = 0;
+ }
+ return Promise.resolve();
+ }
+ outboundBatchLoading.value = true;
+ return getOutboundBatchesByCustomer({ customerId })
+ .then((res) => {
+ if (res.code === 200) {
+ const list = res.data?.records ?? res.data ?? [];
+ outboundBatchList.value = Array.isArray(list) ? list : [];
+ outboundBatchOptions.value = normalizeOutboundBatchOptions(list);
+ } else {
+ outboundBatchList.value = [];
+ outboundBatchOptions.value = [];
+ }
+ })
+ .catch(() => {
+ outboundBatchList.value = [];
+ outboundBatchOptions.value = [];
+ })
+ .finally(() => {
+ outboundBatchLoading.value = false;
+ if (keepSelected) {
+ ensureOutboundOptionsForSelected();
+ restoreOutboundTableSelection();
+ }
+ });
+};
+
+const handleCustomerChange = (customerId) => {
+ form.stockOutRecordIds = [];
+ form.outboundBatches = "";
+ form.amount = 0;
+ loadOutboundBatches(customerId);
+};
+
+const openOutboundSelectDialog = () => {
+ if (!form.customerId || isEdit.value || isView.value) return;
+ outboundSelectVisible.value = true;
+ loadOutboundBatches(form.customerId, true).then(() => {
+ restoreOutboundTableSelection();
+ });
+};
+
+const handleOutboundDialogSelectionChange = (selection) => {
+ dialogOutboundSelection.value = selection;
+};
+
+const confirmOutboundSelection = () => {
+ if (dialogOutboundSelection.value.length === 0) {
+ ElMessage.warning("璇疯嚦灏戦�夋嫨涓�鏉″叧鑱斿崟鎹�");
+ return;
+ }
+ form.stockOutRecordIds = dialogOutboundSelection.value
+ .map((row) => getOutboundRowId(row))
+ .filter((id) => id !== undefined && id !== null);
+ form.outboundBatches = dialogOutboundSelection.value
+ .map((row) => row.outboundBatches ?? row.batchNo ?? row.shippingNo ?? "")
+ .filter(Boolean)
+ .join("銆�");
+ outboundSelectVisible.value = false;
+ syncCollectionAmount();
+ formRef.value?.validateField("stockOutRecordIds");
+};
+
+const handleOutboundDialogClosed = () => {
+ dialogOutboundSelection.value = [];
+};
+
+const appendFilterParams = (params) => {
+ if (filters.collectionNumber) {
+ params.collectionNumber = filters.collectionNumber;
+ }
+ if (filters.customerId) {
+ params.customerId = filters.customerId;
+ }
+ if (filters.collectionMethod) {
+ params.collectionMethod = filters.collectionMethod;
+ }
+ if (filters.dateRange?.length === 2) {
+ params.startDate = filters.dateRange[0];
+ params.endDate = filters.dateRange[1];
+ }
+ return params;
+};
+
+const buildListParams = () =>
+ appendFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => appendFilterParams({});
+
+const buildSubmitPayload = (forUpdate = false) => {
+ const payload = {
+ customerId: form.customerId,
+ collectionDate: form.receiptDate,
+ collectionAmount: form.amount,
+ collectionMethod: form.receiptMethod,
+ collectionNumber: form.receiptCode || "",
+ remark: form.remark || "",
+ stockOutRecordIds: (form.stockOutRecordIds || []).join(","),
};
- return map[method] || method;
+ if (forUpdate) {
+ payload.id = currentId.value;
+ }
+ return payload;
+};
+
+const fillFormFromRow = (row) => {
+ const stockOutRecordIds = parseStockOutRecordIds(row.stockOutRecordIds ?? row.stockOutRecordId);
+ Object.assign(form, {
+ receiptCode: row.receiptCode ?? row.collectionNumber ?? "",
+ customerId: row.customerId,
+ receiptDate: row.receiptDate ?? row.collectionDate ?? "",
+ amount: Number(row.amount ?? row.collectionAmount ?? 0),
+ receiptMethod: row.receiptMethod ?? row.collectionMethod ?? "",
+ stockOutRecordIds,
+ outboundBatches: formatOutboundBatches(row.outboundBatches),
+ remark: row.remark ?? "",
+ });
+};
+
+const closeDialog = () => {
+ dialogVisible.value = false;
+ outboundSelectVisible.value = false;
+ isView.value = false;
+ isEdit.value = false;
+};
+
+const handleExport = () => {
+ const params = buildExportParams();
+ proxy.download("/accountSalesCollection/exportAccountSalesCollection", params, `鏀舵鍗昣${Date.now()}.xlsx`);
};
const getTableData = () => {
- let result = [...mockData];
- if (filters.receiptCode) {
- result = result.filter(item => item.receiptCode.includes(filters.receiptCode));
- }
- if (filters.customerId) {
- result = result.filter(item => item.customerId === filters.customerId);
- }
- if (filters.receiptMethod) {
- result = result.filter(item => item.receiptMethod === filters.receiptMethod);
- }
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ tableLoading.value = true;
+ listPageAccountSalesCollection(buildListParams())
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+ dataList.value = (res.data.records ?? []).map(normalizeTableRow);
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ dataList.value = [];
+ pagination.total = 0;
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
+};
+
+const onSearch = () => {
+ pagination.currentPage = 1;
+ getTableData();
};
const resetFilters = () => {
- filters.receiptCode = "";
+ filters.collectionNumber = "";
filters.customerId = "";
- filters.receiptMethod = "";
+ filters.collectionMethod = "";
+ filters.dateRange = [];
pagination.currentPage = 1;
getTableData();
};
@@ -255,88 +632,89 @@
const add = () => {
isEdit.value = false;
+ isView.value = false;
dialogTitle.value = "鏂板鏀舵";
Object.assign(form, {
receiptCode: "SK" + Date.now().toString().slice(-8),
customerId: "",
- receiptDate: new Date().toISOString().split('T')[0],
+ receiptDate: new Date().toISOString().split("T")[0],
amount: 0,
- receiptMethod: "bank_transfer",
- bankAccount: "",
- relatedDocs: [],
+ receiptMethod: getDefaultReceiptMethod(),
+ stockOutRecordIds: [],
+ outboundBatches: "",
remark: "",
});
+ outboundBatchList.value = [];
+ outboundBatchOptions.value = [];
dialogVisible.value = true;
};
const edit = (row) => {
isEdit.value = true;
+ isView.value = false;
currentId.value = row.id;
dialogTitle.value = "缂栬緫鏀舵";
- Object.assign(form, row);
+ fillFormFromRow(row);
dialogVisible.value = true;
};
const view = (row) => {
- ElMessage.info(`鏌ョ湅鏀舵鍗�: ${row.receiptCode}`);
-};
-
-const handleConfirm = (row) => {
- ElMessageBox.confirm("纭璇ユ敹娆惧崟鍚楋紵", "鎻愮ず", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "info",
- }).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData[index].status = "confirmed";
- }
- ElMessage.success("纭鎴愬姛");
- getTableData();
- });
+ isView.value = true;
+ isEdit.value = false;
+ dialogTitle.value = "鏌ョ湅鏀舵";
+ fillFormFromRow(row);
+ dialogVisible.value = true;
};
const handleDelete = (row) => {
- ElMessageBox.confirm("纭鍒犻櫎璇ユ敹娆惧崟鍚楋紵", "鎻愮ず", {
+ ElMessageBox.confirm(`纭鍒犻櫎鏀舵鍗曘��${row.receiptCode ?? row.collectionNumber}銆嶅悧锛焋, "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
}).then(() => {
- const index = mockData.findIndex(item => item.id === row.id);
- if (index !== -1) {
- mockData.splice(index, 1);
- }
- ElMessage.success("鍒犻櫎鎴愬姛");
- getTableData();
+ deleteAccountSalesCollection([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
});
-};
-
-const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
};
const submitForm = () => {
formRef.value.validate((valid) => {
- if (valid) {
- const customer = customerList.find(item => item.id === form.customerId);
- if (isEdit.value) {
- const index = mockData.findIndex(item => item.id === currentId.value);
- if (index !== -1) {
- mockData[index] = { ...mockData[index], ...form, customerName: customer?.name };
+ if (!valid) return;
+ submitLoading.value = true;
+ const request = isEdit.value
+ ? updateAccountSalesCollection(buildSubmitPayload(true))
+ : addAccountSalesCollection(buildSubmitPayload());
+ request
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success(isEdit.value ? "淇敼鎴愬姛" : "鏂板鎴愬姛");
+ closeDialog();
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || (isEdit.value ? "淇敼澶辫触" : "鏂板澶辫触"));
}
- ElMessage.success("缂栬緫鎴愬姛");
- } else {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" });
- ElMessage.success("鏂板鎴愬姛");
- }
- dialogVisible.value = false;
- getTableData();
- }
+ })
+ .catch(() => {
+ ElMessage.error(isEdit.value ? "淇敼澶辫触" : "鏂板澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
});
};
onMounted(() => {
+ getCustomerList();
getTableData();
});
</script>
@@ -353,4 +731,10 @@
color: #67c23a;
font-weight: bold;
}
+
+.outbound-batch-input:not(.is-disabled) {
+ :deep(.el-input__wrapper) {
+ cursor: pointer;
+ }
+}
</style>
diff --git a/src/views/financialManagement/receivable/reconciliation.vue b/src/views/financialManagement/receivable/reconciliation.vue
index 883e12e..b1bff0e 100644
--- a/src/views/financialManagement/receivable/reconciliation.vue
+++ b/src/views/financialManagement/receivable/reconciliation.vue
@@ -2,8 +2,8 @@
<div class="app-container">
<el-form :model="filters" :inline="true">
<el-form-item label="瀹㈡埛:">
- <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable style="width: 200px;">
- <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select v-model="filters.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable filterable style="width: 200px;">
+ <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="瀵硅处鏈熼棿:">
@@ -29,6 +29,7 @@
rowKey="id"
:column="columns"
:tableData="dataList"
+ :tableLoading="tableLoading"
:page="{
current: pagination.currentPage,
size: pagination.pageSize,
@@ -36,21 +37,22 @@
}"
@pagination="changePage"
>
- <template #beginBalance="{ row }">
- <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.beginBalance) }}</span>
+ <template #openingBalance="{ row }">
+ <span :class="row.openingBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.openingBalance) }}</span>
</template>
- <template #currentReceivable="{ row }">
- <span class="text-primary">楼{{ formatMoney(row.currentReceivable) }}</span>
+ <template #currentPlan="{ row }">
+ <span class="text-primary">楼{{ formatMoney(row.currentPlan) }}</span>
</template>
- <template #currentReceipt="{ row }">
- <span class="text-success">楼{{ formatMoney(row.currentReceipt) }}</span>
+ <template #currentActually="{ row }">
+ <span class="text-success">楼{{ formatMoney(row.currentActually) }}</span>
</template>
- <template #endBalance="{ row }">
- <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.endBalance) }}</span>
+ <template #closingBalance="{ row }">
+ <span :class="row.closingBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.closingBalance) }}</span>
</template>
<template #operation="{ row }">
<el-button type="primary" link @click="viewDetail(row)">鏌ョ湅鏄庣粏</el-button>
- <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button>
+ <!-- <el-button type="primary" link @click="printStatement(row)">鎵撳嵃</el-button> -->
+ <el-button type="danger" link @click="handleDelete(row)">鍒犻櫎</el-button>
</template>
</PIMTable>
</div>
@@ -60,7 +62,7 @@
<h3>{{ currentCustomer }} 搴旀敹瀵硅处鍗�</h3>
<p>瀵硅处鏈熼棿: {{ currentPeriod }}</p>
</div>
- <el-table :data="detailData" border style="width: 100%">
+ <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading">
<el-table-column prop="date" label="鏃ユ湡" width="120" />
<el-table-column prop="type" label="绫诲瀷" width="100">
<template #default="{ row }">
@@ -98,52 +100,68 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="閫夋嫨瀹㈡埛" prop="customerId">
- <el-select v-model="generateForm.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%;" @change="onCustomerChange">
- <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
+ <el-select
+ v-model="generateForm.customerId"
+ placeholder="璇烽�夋嫨瀹㈡埛"
+ style="width: 100%;"
+ filterable
+ @change="onCustomerChange"
+ >
+ <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
- <el-form-item label="瀵硅处鏈堜唤" prop="period">
- <el-date-picker v-model="generateForm.period" type="month" placeholder="閫夋嫨鏈堜唤" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" />
+ <el-form-item label="瀵硅处鏈堜唤" prop="statementMonth">
+ <el-date-picker v-model="generateForm.statementMonth" type="month" placeholder="閫夋嫨鏈堜唤" value-format="YYYY-MM" style="width: 100%;" @change="onStatementMonthChange" />
</el-form-item>
</el-col>
</el-row>
</el-form>
- <div v-if="salesData.length > 0" class="sales-section">
- <div class="section-title">鏈湀閿�鍞暟鎹�</div>
- <el-table :data="salesData" border style="width: 100%; margin-bottom: 15px;" v-loading="salesLoading" @selection-change="handleSalesSelectionChange">
+ <div v-if="statementDetailLoaded" class="sales-section">
+ <div v-if="salesData.length > 0" class="section-title">鏈湀閿�鍞暟鎹�</div>
+ <el-table
+ v-if="salesData.length > 0"
+ ref="salesTableRef"
+ :data="salesData"
+ border
+ row-key="id"
+ style="width: 100%; margin-bottom: 15px;"
+ v-loading="salesLoading"
+ @selection-change="handleSalesSelectionChange"
+ >
<el-table-column type="selection" width="55" align="center" />
- <el-table-column prop="date" label="鏃ユ湡" width="120" />
- <el-table-column prop="code" label="鍗曟嵁缂栧彿" width="150" />
+ <el-table-column prop="occurrenceDate" label="鏃ユ湡" width="120" />
+ <el-table-column prop="receiptNumber" label="鍗曟嵁缂栧彿" width="150" />
<el-table-column prop="type" label="绫诲瀷" width="100">
<template #default="{ row }">
- <el-tag :type="row.type === '鍑哄簱' ? 'success' : row.type === '鏀舵' ? 'primary' : 'danger'">{{ row.type }}</el-tag>
+ <el-tag :type="getDetailTypeTagType(row.type)">{{ row.typeLabel }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="amount" label="閲戦" width="120">
<template #default="{ row }">
- <span :class="row.type === '鍑哄簱' ? 'text-primary' : row.type === '鏀舵' ? 'text-success' : 'text-danger'">楼{{ formatMoney(row.amount) }}</span>
+ <span :class="getDetailAmountClass(row.type)">楼{{ formatMoney(row.amount) }}</span>
</template>
</el-table-column>
<el-table-column prop="remark" label="澶囨敞" />
</el-table>
+ <el-empty v-else description="璇ュ鎴锋湰鏈堟殏鏃犳槑缁嗘暟鎹�" :image-size="80" />
<div class="summary-row">
- <span>鏈熷垵浣欓: <strong class="text-primary">楼{{ formatMoney(generateForm.beginBalance) }}</strong></span>
- <span>鏈湡搴旀敹: <strong class="text-primary">楼{{ formatMoney(generateForm.currentReceivable) }}</strong></span>
- <span>鏈湡鏀舵: <strong class="text-success">楼{{ formatMoney(generateForm.currentReceipt) }}</strong></span>
- <span>鏈熸湯浣欓: <strong :class="calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt) >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt)) }}</strong></span>
+ <span>鏈熷垵浣欓: <strong class="text-primary">楼{{ formatMoney(generateForm.openingBalance) }}</strong></span>
+ <span>鏈湡搴旀敹: <strong class="text-primary">楼{{ formatMoney(generateForm.currentPlan) }}</strong></span>
+ <span>鏈湡鏀舵: <strong class="text-success">楼{{ formatMoney(generateForm.currentActually) }}</strong></span>
+ <span>鏈熸湯浣欓: <strong :class="displayClosingBalance >= 0 ? 'text-success' : 'text-danger'">楼{{ formatMoney(displayClosingBalance) }}</strong></span>
</div>
</div>
- <div v-else-if="generateForm.customerId && !salesLoading" class="empty-tip">
+ <div v-else-if="generateForm.customerId && generateForm.statementMonth && !salesLoading" class="empty-tip">
<el-empty description="璇ュ鎴锋湰鏈堟殏鏃犻攢鍞暟鎹�" />
</div>
<template #footer>
- <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">纭鐢熸垚</el-button>
+ <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate" :loading="submitLoading">纭鐢熸垚</el-button>
<el-button @click="generateDialogVisible = false">鍙栨秷</el-button>
</template>
</FormDialog>
@@ -151,9 +169,20 @@
</template>
<script setup>
-import { ref, reactive, onMounted, computed } from "vue";
-import { ElMessage } from "element-plus";
+import { ref, reactive, onMounted, computed, nextTick, getCurrentInstance } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
+import { listCustomer } from "@/api/basicData/customer.js";
+import {
+ getAccountStatementDetailsByMonth,
+ addAccountStatement,
+ listPageAccountStatement,
+ deleteAccountStatement,
+} from "@/api/financialManagement/accountStatement.js";
+
+const ACCOUNT_TYPE_RECEIVABLE = 1;
+
+const { proxy } = getCurrentInstance();
defineOptions({
name: "搴旀敹瀵硅处",
@@ -172,56 +201,192 @@
});
const columns = [
- { label: "瀵硅处鍗曞彿", prop: "statementCode", width: "150" },
+ { label: "瀵硅处鍗曞彿", prop: "statementNumber", width: "150" },
{ label: "瀹㈡埛鍚嶇О", prop: "customerName", width: "180" },
- { label: "瀵硅处鏈熼棿", prop: "period", width: "150" },
- { label: "鏈熷垵浣欓", prop: "beginBalance", slot: "beginBalance" },
- { label: "鏈湡搴旀敹", prop: "currentReceivable", slot: "currentReceivable" },
- { label: "鏈湡鏀舵", prop: "currentReceipt", slot: "currentReceipt" },
- { label: "鏈熸湯浣欓", prop: "endBalance", slot: "endBalance" },
- { label: "鎿嶄綔", prop: "operation", slot: "operation", width: "150", fixed: "right" },
+ { label: "瀵硅处鏈熼棿", prop: "statementMonth", width: "150" },
+ { label: "鏈熷垵浣欓", prop: "openingBalance", dataType: "slot", slot: "openingBalance" },
+ { label: "鏈湡搴旀敹", prop: "currentPlan", dataType: "slot", slot: "currentPlan" },
+ { label: "鏈湡鏀舵", prop: "currentActually", dataType: "slot", slot: "currentActually" },
+ { label: "鏈熸湯浣欓", prop: "closingBalance", dataType: "slot", slot: "closingBalance" },
+ { label: "鎿嶄綔", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" },
];
const dataList = ref([]);
+const tableLoading = ref(false);
+const submitLoading = ref(false);
const detailDialogVisible = ref(false);
const currentCustomer = ref("");
const currentPeriod = ref("");
const detailData = ref([]);
+const detailLoading = ref(false);
const generateDialogVisible = ref(false);
const salesLoading = ref(false);
+const statementDetailLoaded = ref(false);
const salesData = ref([]);
const selectedSales = ref([]);
+const salesTableRef = ref(null);
+const customerList = ref([]);
+
+/** 鏄庣粏 type锛�1鍑哄簱 2鍏ュ簱 3鏀舵 4浠樻 5閫�璐� */
+const STATEMENT_DETAIL_TYPE_MAP = {
+ 1: "鍑哄簱",
+ 2: "鍏ュ簱",
+ 3: "鏀舵",
+ 4: "浠樻",
+ 5: "閫�璐�",
+};
+
+const calculateEndBalance = (openingBalance, currentPlan, currentActually) => {
+ return openingBalance + currentPlan - currentActually;
+};
+
+const getDetailTypeLabel = (type) => STATEMENT_DETAIL_TYPE_MAP[Number(type)] ?? "";
+
+const getDetailTypeTagType = (type) => {
+ const t = Number(type);
+ if (t === 1) return "success";
+ if (t === 3) return "primary";
+ if (t === 5) return "danger";
+ return "info";
+};
+
+const getDetailAmountClass = (type) => {
+ const t = Number(type);
+ if (t === 1) return "text-primary";
+ if (t === 3) return "text-success";
+ return "text-danger";
+};
const generateForm = reactive({
customerId: "",
customerName: "",
- period: "",
- beginBalance: 0,
- currentReceivable: 0,
- currentReceipt: 0,
+ statementMonth: "",
+ openingBalance: 0,
+ currentPlan: 0,
+ currentActually: 0,
+ closingBalance: 0,
+});
+
+const displayClosingBalance = computed(() => {
+ return calculateEndBalance(
+ generateForm.openingBalance,
+ generateForm.currentPlan,
+ generateForm.currentActually
+ );
});
const canGenerate = computed(() => {
- return generateForm.customerId && generateForm.period && selectedSales.value.length > 0;
+ return generateForm.customerId && generateForm.statementMonth && selectedSales.value.length > 0;
});
-const customerList = [
- { id: 1, name: "鍖椾含绉戞妧鏈夐檺鍏徃" },
- { id: 2, name: "涓婃捣璐告槗鍏徃" },
- { id: 3, name: "骞垮窞瀹炰笟鏈夐檺鍏徃" },
- { id: 4, name: "娣卞湷鐢靛瓙鍏徃" },
-];
-
-const mockData = [
- { id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 },
- { id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "涓婃捣璐告槗鍏徃", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 },
- { id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "鍖椾含绉戞妧鏈夐檺鍏徃", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 },
-];
-
-const calculateEndBalance = (beginBalance, currentReceivable, currentReceipt) => {
- return beginBalance + currentReceivable - currentReceipt;
+const applyStatementSummary = (data) => {
+ generateForm.openingBalance = Number(data.openingBalance ?? 0);
+ generateForm.currentPlan = Number(data.currentPlan ?? 0);
+ generateForm.currentActually = Number(data.currentActually ?? 0);
+ generateForm.closingBalance = Number(
+ data.closingBalance ??
+ calculateEndBalance(
+ generateForm.openingBalance,
+ generateForm.currentPlan,
+ generateForm.currentActually
+ )
+ );
};
+
+const getCustomerList = () => {
+ listCustomer({ current: -1, size: -1, type: 0 }).then((res) => {
+ if (res.code === 200) {
+ customerList.value = res.data?.records || [];
+ }
+ });
+};
+
+const normalizeSalesRows = (list) => {
+ const rows = Array.isArray(list) ? list : [];
+ return rows.map((item, index) => {
+ const type = Number(item.type);
+ return {
+ id: item.id ?? `detail-${index}`,
+ accountStatementId: item.accountStatementId,
+ occurrenceDate: item.occurrenceDate ?? "",
+ receiptNumber: item.receiptNumber ?? "",
+ type,
+ typeLabel: getDetailTypeLabel(type),
+ amount: Math.abs(Number(item.amount ?? 0)),
+ remark: item.remark ?? "",
+ };
+ });
+};
+
+const selectAllSalesRows = (keepApiSummary = false) => {
+ nextTick(() => {
+ const table = salesTableRef.value;
+ if (!table) return;
+ table.clearSelection();
+ salesData.value.forEach((row) => table.toggleRowSelection(row, true));
+ selectedSales.value = [...salesData.value];
+ if (!keepApiSummary) {
+ calculateSummary();
+ }
+ });
+};
+
+const isNumericId = (id) => id !== undefined && id !== null && id !== "" && /^\d+$/.test(String(id));
+
+const buildFilterParams = (params = {}) => {
+ const result = { ...params, accountType: ACCOUNT_TYPE_RECEIVABLE };
+ if (filters.customerId) {
+ result.customerId = filters.customerId;
+ }
+ if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) {
+ result.statementMonth = filters.startMonth;
+ } else if (filters.startMonth) {
+ result.startMonth = filters.startMonth;
+ }
+ if (filters.endMonth && filters.startMonth !== filters.endMonth) {
+ result.endMonth = filters.endMonth;
+ }
+ return result;
+};
+
+const buildListParams = () =>
+ buildFilterParams({
+ current: pagination.currentPage,
+ size: pagination.pageSize,
+ });
+
+const buildExportParams = () => buildFilterParams({});
+
+const buildDetailSubmitItem = (row) => {
+ const item = {
+ occurrenceDate: row.occurrenceDate,
+ receiptNumber: row.receiptNumber,
+ type: row.type,
+ amount: row.amount,
+ remark: row.remark ?? "",
+ };
+ if (isNumericId(row.id)) {
+ item.id = Number(row.id);
+ }
+ if (row.accountStatementId) {
+ item.accountStatementId = row.accountStatementId;
+ }
+ return item;
+};
+
+const buildAddPayload = () => ({
+ customerId: generateForm.customerId,
+ customerName: generateForm.customerName,
+ statementMonth: generateForm.statementMonth,
+ accountType: ACCOUNT_TYPE_RECEIVABLE,
+ statementNumber: "",
+ openingBalance: generateForm.openingBalance,
+ currentPlan: generateForm.currentPlan,
+ currentActually: generateForm.currentActually,
+ closingBalance: generateForm.closingBalance,
+ accountStatementDetails: selectedSales.value.map(buildDetailSubmitItem),
+});
const formatMoney = (value) => {
if (value === undefined || value === null) return "0.00";
@@ -229,15 +394,27 @@
};
const getTableData = () => {
- let result = [...mockData];
- if (filters.customerId) {
- result = result.filter(item => item.customerId === filters.customerId);
- }
- if (filters.startMonth && filters.endMonth) {
- result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth);
- }
- pagination.total = result.length;
- dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize);
+ tableLoading.value = true;
+ listPageAccountStatement(buildListParams())
+ .then((res) => {
+ const ok = res.code === 200 || res.code === 0;
+ if (ok && res.data) {
+ pagination.total = res.data.total ?? 0;
+ dataList.value = res.data.records ?? [];
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ澶辫触");
+ dataList.value = [];
+ pagination.total = 0;
+ }
+ })
+ .catch(() => {
+ dataList.value = [];
+ pagination.total = 0;
+ ElMessage.error("鏌ヨ澶辫触");
+ })
+ .finally(() => {
+ tableLoading.value = false;
+ });
};
const resetFilters = () => {
@@ -257,83 +434,97 @@
const generateStatement = () => {
generateForm.customerId = "";
generateForm.customerName = "";
- generateForm.period = "";
- generateForm.beginBalance = 0;
- generateForm.currentReceivable = 0;
- generateForm.currentReceipt = 0;
+ generateForm.statementMonth = "";
+ generateForm.openingBalance = 0;
+ generateForm.currentPlan = 0;
+ generateForm.currentActually = 0;
+ generateForm.closingBalance = 0;
+ statementDetailLoaded.value = false;
salesData.value = [];
selectedSales.value = [];
generateDialogVisible.value = true;
};
const onCustomerChange = (customerId) => {
- const customer = customerList.find(item => item.id === customerId);
- if (customer) {
- generateForm.customerName = customer.name;
- }
+ const customer = customerList.value.find((item) => item.id === customerId);
+ generateForm.customerName = customer?.customerName ?? "";
loadSalesData();
};
-const onPeriodChange = () => {
+const onStatementMonthChange = () => {
loadSalesData();
};
const loadSalesData = () => {
- if (!generateForm.customerId || !generateForm.period) {
+ if (!generateForm.customerId || !generateForm.statementMonth) {
salesData.value = [];
+ selectedSales.value = [];
+ statementDetailLoaded.value = false;
+ generateForm.openingBalance = 0;
+ generateForm.currentPlan = 0;
+ generateForm.currentActually = 0;
+ generateForm.closingBalance = 0;
return;
}
salesLoading.value = true;
+ selectedSales.value = [];
+ statementDetailLoaded.value = false;
- setTimeout(() => {
- const mockSalesData = [
- { id: 1, date: generateForm.period + "-03", code: "CK2024001", type: "鍑哄簱", amount: 8000, remark: "浜у搧A閿�鍞�" },
- { id: 2, date: generateForm.period + "-08", code: "SK2024001", type: "鏀舵", amount: 5000, remark: "瀹㈡埛鍥炴" },
- { id: 3, date: generateForm.period + "-12", code: "CK2024002", type: "鍑哄簱", amount: 12000, remark: "浜у搧B閿�鍞�" },
- { id: 4, date: generateForm.period + "-15", code: "TH2024001", type: "閫�璐�", amount: 2000, remark: "璐ㄩ噺闂閫�璐�" },
- { id: 5, date: generateForm.period + "-20", code: "CK2024003", type: "鍑哄簱", amount: 5000, remark: "浜у搧C閿�鍞�" },
- { id: 6, date: generateForm.period + "-25", code: "SK2024002", type: "鏀舵", amount: 8000, remark: "瀹㈡埛鍥炴" },
- ];
+ getAccountStatementDetailsByMonth({
+ accountType: ACCOUNT_TYPE_RECEIVABLE,
+ customerId: generateForm.customerId,
+ statementMonth: generateForm.statementMonth,
+ })
+ .then((res) => {
+ if (res.code === 200) {
+ const data = res.data ?? {};
+ const details = data.accountStatementDetails;
+ const list = Array.isArray(details) ? details : [];
+ salesData.value = normalizeSalesRows(list);
+ applyStatementSummary(data);
+ statementDetailLoaded.value = true;
- salesData.value = mockSalesData;
-
- const lastPeriod = getLastPeriod(generateForm.period);
- const lastStatement = mockData.find(item =>
- item.customerId === generateForm.customerId && item.period === lastPeriod
- );
- generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0;
-
- calculateSummary();
-
- salesLoading.value = false;
- }, 500);
-};
-
-const getLastPeriod = (period) => {
- const [year, month] = period.split("-").map(Number);
- if (month === 1) {
- return `${year - 1}-12`;
- }
- return `${year}-${String(month - 1).padStart(2, "0")}`;
+ if (salesData.value.length > 0) {
+ selectAllSalesRows(true);
+ }
+ } else {
+ salesData.value = [];
+ statementDetailLoaded.value = false;
+ ElMessage.error(res.msg || "鏌ヨ瀵硅处鏄庣粏澶辫触");
+ }
+ })
+ .catch(() => {
+ salesData.value = [];
+ statementDetailLoaded.value = false;
+ ElMessage.error("鏌ヨ瀵硅处鏄庣粏澶辫触");
+ })
+ .finally(() => {
+ salesLoading.value = false;
+ });
};
const calculateSummary = () => {
let receivable = 0;
let receipt = 0;
- selectedSales.value.forEach(item => {
- if (item.type === "鍑哄簱") {
+ selectedSales.value.forEach((item) => {
+ if (item.type === 1) {
receivable += item.amount;
- } else if (item.type === "閫�璐�") {
+ } else if (item.type === 5) {
receivable -= item.amount;
- } else if (item.type === "鏀舵") {
+ } else if (item.type === 3) {
receipt += item.amount;
}
});
- generateForm.currentReceivable = receivable;
- generateForm.currentReceipt = receipt;
+ generateForm.currentPlan = receivable;
+ generateForm.currentActually = receipt;
+ generateForm.closingBalance = calculateEndBalance(
+ generateForm.openingBalance,
+ generateForm.currentPlan,
+ generateForm.currentActually
+ );
};
const handleSalesSelectionChange = (selection) => {
@@ -342,51 +533,127 @@
};
const confirmGenerate = () => {
- const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1;
- const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt);
+ if (!canGenerate.value) return;
+ submitLoading.value = true;
+ addAccountStatement(buildAddPayload())
+ .then((res) => {
+ if (res.code === 200) {
+ generateDialogVisible.value = false;
+ ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
+ pagination.currentPage = 1;
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鐢熸垚澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鐢熸垚澶辫触");
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+};
- mockData.unshift({
- id: newId,
- statementCode: "DZ" + Date.now(),
- customerId: generateForm.customerId,
- customerName: generateForm.customerName,
- period: generateForm.period,
- beginBalance: generateForm.beginBalance,
- currentReceivable: generateForm.currentReceivable,
- currentReceipt: generateForm.currentReceipt,
- endBalance,
+const handleDelete = (row) => {
+ ElMessageBox.confirm(`纭鍒犻櫎瀵硅处鍗曘��${row.statementNumber || row.id}銆嶅悧锛焋, "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }).then(() => {
+ deleteAccountStatement([row.id])
+ .then((res) => {
+ if (res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || "鍒犻櫎澶辫触");
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鍒犻櫎澶辫触");
+ });
+ });
+};
+
+const buildDetailTableFromApi = (data, statementMonth) => {
+ const details = Array.isArray(data.accountStatementDetails) ? data.accountStatementDetails : [];
+ let runningBalance = Number(data.openingBalance ?? 0);
+ const rows = [
+ {
+ date: statementMonth ?? "",
+ type: "鏈熷垵",
+ code: "-",
+ debit: 0,
+ credit: 0,
+ balance: runningBalance,
+ remark: "鏈熷垵浣欓",
+ },
+ ];
+
+ details.forEach((item) => {
+ const amount = Math.abs(Number(item.amount ?? 0));
+ const type = Number(item.type);
+ let debit = 0;
+ let credit = 0;
+
+ if (type === 1) {
+ debit = amount;
+ runningBalance += amount;
+ } else if (type === 3 || type === 5) {
+ credit = amount;
+ runningBalance -= amount;
+ }
+
+ rows.push({
+ date: item.occurrenceDate ?? "",
+ type: getDetailTypeLabel(type),
+ code: item.receiptNumber ?? "",
+ debit,
+ credit,
+ balance: runningBalance,
+ remark: item.remark ?? "",
+ });
});
- generateDialogVisible.value = false;
- ElMessage.success("瀵硅处鍗曠敓鎴愭垚鍔�");
- getTableData();
+ return rows;
};
const viewDetail = (row) => {
- currentCustomer.value = row.customerName;
- currentPeriod.value = row.period;
+ if (!row.customerId || !row.statementMonth) {
+ ElMessage.warning("缂哄皯瀹㈡埛鎴栧璐︽湀浠斤紝鏃犳硶鏌ヨ鏄庣粏");
+ return;
+ }
- const saleOutAmount = Math.floor(row.currentReceivable * 0.6);
- const returnAmount = Math.floor(row.currentReceivable * 0.1);
- const firstReceipt = Math.floor(row.currentReceipt * 0.4);
- const secondReceipt = row.currentReceipt - firstReceipt;
-
- let runningBalance = row.beginBalance;
-
- detailData.value = [
- { date: row.period + "-01", type: "鏈熷垵", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "鏈熷垵浣欓" },
- { date: row.period + "-05", type: "鍑哄簱", code: "CK2024001", debit: saleOutAmount, credit: 0, balance: runningBalance += saleOutAmount, remark: "閿�鍞嚭搴�" },
- { date: row.period + "-10", type: "鏀舵", code: "SK2024001", debit: 0, credit: firstReceipt, balance: runningBalance -= firstReceipt, remark: "瀹㈡埛鍥炴" },
- { date: row.period + "-15", type: "鍑哄簱", code: "CK2024002", debit: row.currentReceivable - saleOutAmount - returnAmount, credit: 0, balance: runningBalance += (row.currentReceivable - saleOutAmount - returnAmount), remark: "閿�鍞嚭搴�" },
- { date: row.period + "-20", type: "閫�璐�", code: "TH2024001", debit: 0, credit: returnAmount, balance: runningBalance -= returnAmount, remark: "閿�鍞��璐�" },
- { date: row.period + "-25", type: "鏀舵", code: "SK2024002", debit: 0, credit: secondReceipt, balance: runningBalance -= secondReceipt, remark: "瀹㈡埛鍥炴" },
- ];
-
+ currentCustomer.value = row.customerName ?? "";
+ currentPeriod.value = row.statementMonth ?? "";
+ detailData.value = [];
detailDialogVisible.value = true;
+ detailLoading.value = true;
+
+ getAccountStatementDetailsByMonth({
+ accountType: ACCOUNT_TYPE_RECEIVABLE,
+ customerId: row.customerId,
+ statementMonth: row.statementMonth,
+ })
+ .then((res) => {
+ if (res.code === 200) {
+ detailData.value = buildDetailTableFromApi(res.data ?? {}, row.statementMonth);
+ } else {
+ ElMessage.error(res.msg || "鏌ヨ鏄庣粏澶辫触");
+ detailDialogVisible.value = false;
+ }
+ })
+ .catch(() => {
+ ElMessage.error("鏌ヨ鏄庣粏澶辫触");
+ detailDialogVisible.value = false;
+ })
+ .finally(() => {
+ detailLoading.value = false;
+ });
};
const printStatement = (row) => {
- ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementCode}`);
+ ElMessage.info(`鎵撳嵃瀵硅处鍗�: ${row.statementNumber}`);
};
const printDetail = () => {
@@ -394,10 +661,12 @@
};
const handleOut = () => {
- ElMessage.success("瀵煎嚭鎴愬姛");
+ const params = buildExportParams();
+ proxy.download("/accountStatement/exportAccountStatement", params, `搴旀敹瀵硅处鍗昣${Date.now()}.xlsx`);
};
onMounted(() => {
+ getCustomerList();
getTableData();
});
</script>
--
Gitblit v1.9.3