From 24fd22f14f72918223dcf084cb1e63d0f31e5b86 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 7 Dec 2025 05:42:28 +0000 Subject: [PATCH 1/6] Refactor batch invoice functionality and state management Co-authored-by: ming10655 --- .../src/api/system/batchInvoice.js | 52 +++ packages/order-system/src/store/getters.js | 12 +- .../order-system/src/store/modules/excel.js | 114 ++---- .../components/common/BatchInvoicePanel.vue | 383 ++++++------------ .../components/common/InvoiceCompanysList.vue | 44 +- .../components/common/InvoiceOptionPanel.vue | 11 +- .../components/common/QueueInvoiceList.vue | 242 +++++------ .../dashboard/components/common/ReadyList.vue | 27 +- 8 files changed, 372 insertions(+), 513 deletions(-) diff --git a/packages/order-system/src/api/system/batchInvoice.js b/packages/order-system/src/api/system/batchInvoice.js index d8385c44b..d474ee4f5 100644 --- a/packages/order-system/src/api/system/batchInvoice.js +++ b/packages/order-system/src/api/system/batchInvoice.js @@ -17,6 +17,32 @@ export function listBatchInvoiceIn(query) { }); } +// 获取进项批量开票的公司聚合信息 +export function getBatchInvoiceInCompanySummary(voucher) { + return request({ + url: `${BASE_IN}/companySummary/${encode(voucher)}`, + method: 'get' + }); +} + +// 更新进项批量开票记录的开票状态 +export function updateBatchInvoiceInInvoiced(id, invoiced, invoiceId = null) { + return request({ + url: `${BASE_IN}/updateInvoiced`, + method: 'put', + data: { id, invoiced, invoiceId } + }); +} + +// 批量更新进项开票记录的开票状态 +export function batchUpdateBatchInvoiceInInvoiced(ids, invoiced, invoiceIds = []) { + return request({ + url: `${BASE_IN}/batchUpdateInvoiced`, + method: 'put', + data: { ids, invoiced, invoiceIds } + }); +} + export function importBatchInvoiceInData(data) { return request({ url: `${BASE_IN}/importData`, @@ -67,6 +93,32 @@ export function listBatchInvoiceOut(query) { }); } +// 获取销项批量开票的公司聚合信息 +export function getBatchInvoiceOutCompanySummary(voucher) { + return request({ + url: `${BASE_OUT}/companySummary/${encode(voucher)}`, + method: 'get' + }); +} + +// 更新销项批量开票记录的开票状态 +export function updateBatchInvoiceOutInvoiced(id, invoiced, invoiceId = null) { + return request({ + url: `${BASE_OUT}/updateInvoiced`, + method: 'put', + data: { id, invoiced, invoiceId } + }); +} + +// 批量更新销项开票记录的开票状态 +export function batchUpdateBatchInvoiceOutInvoiced(ids, invoiced, invoiceIds = []) { + return request({ + url: `${BASE_OUT}/batchUpdateInvoiced`, + method: 'put', + data: { ids, invoiced, invoiceIds } + }); +} + export function importBatchInvoiceOutData(data) { return request({ url: `${BASE_OUT}/importData`, diff --git a/packages/order-system/src/store/getters.js b/packages/order-system/src/store/getters.js index 57e8e88f1..0ee8ee007 100644 --- a/packages/order-system/src/store/getters.js +++ b/packages/order-system/src/store/getters.js @@ -44,8 +44,6 @@ const getters = { freightInfo: state => state.trash.freightInfo, // 出差信息中的部门信息 deptName: state => state.trash.deptName, - // excel数据 - excelData: state => state.excel.excelData, // 公共票点 ticketPoint: state => state.excel.ticketPoint, // 备注 @@ -56,10 +54,12 @@ const getters = { selectedInvoiceList: state => state.excel.selectedInvoiceList, // 开票的金额 invoiceAmount: state => state.excel.invoiceAmount, - // 暂存购买的信息 - purchaseTempInfo: state => state.excel.purchaseTempInfo, - sellerTempInfo: state => state.excel.sellerTempInfo, - companyList: state => state.excel.companyList, + // 批量开票详情数据 + batchDetailRows: state => state.excel.batchDetailRows, + // 当前批量开票凭证号 + currentVoucher: state => state.excel.currentVoucher, + // 当前批量开票模式 + currentMode: state => state.excel.currentMode, // 下载进度 downloadProgress: state => state.downloadOnce.percent, diff --git a/packages/order-system/src/store/modules/excel.js b/packages/order-system/src/store/modules/excel.js index 75e8a1285..c1d7d5528 100644 --- a/packages/order-system/src/store/modules/excel.js +++ b/packages/order-system/src/store/modules/excel.js @@ -1,36 +1,25 @@ // 维护开票金额的模块 const state = { - // 读取的excel数据sheets列表 - excelData: [], // 公共票点 ticketPoint: 0, // 公共备注 comment: '', // 打开的批量开票页面 已经选中的订单列表 selectedOrders: [], - // 选中的批量开票的列表 需要对订单列表进行处理 差一个选中订单后对订单的数据处理为开票的信息 然后交给右边组件进行批处理 + // 选中的批量开票的列表 selectedInvoiceList: [], // 开票金额 用于判断是否超过了金额 超过不允许开票 invoiceAmount: 0, - // 购买方临时信息存储 类型为数组 - purchaseTempInfo: [], - // 卖家临时信息存储 类型为数组 - sellerTempInfo: [], - // 导入模板数据(按购买方/销方) - purchaseTemplateData: [], - sellerTemplateData: [], - // 批量开票批次校验信息 - batchMetaMap: {} + // 批量开票详情数据(从后端获取,用于生成发票) + batchDetailRows: [], + // 当前批量开票的凭证号 + currentVoucher: '', + // 当前批量开票的模式(in/out) + currentMode: 'in' }; const mutations = { - SET_EXCEL_DATA: (state, data) => { - state.excelData = data; - }, - CLEAR_EXCEL_DATA: state => { - state.excelData = []; - }, SET_TICKET_POINT: (state, data) => { state.ticketPoint = data; }, @@ -61,71 +50,51 @@ const mutations = { CLEAR_INVOICE_AMOUNT: state => { state.invoiceAmount = 0; }, - // 加上开票金额 ADD_INVOICE_AMOUNT: (state, data) => { - // 开票金额不能为负数 if (data < 0) { console.error('开票金额不能为负数'); return; } state.invoiceAmount = state.invoiceAmount + data; }, - // 扣除开票金额 SUBTRACT_INVOICE_AMOUNT: (state, data) => { - // 开票金额不能为负数 if (data < 0) { console.error('开票金额不能为负数'); return; } state.invoiceAmount = state.invoiceAmount - data; }, - - SET_PURCHASE_TEMP_INFO: (state, data) => { - state.purchaseTempInfo = data; - }, - CLEAR_PURCHASE_TEMP_INFO: state => { - state.purchaseTempInfo = []; - }, - SET_SELLER_TEMP_INFO: (state, data) => { - state.sellerTempInfo = data; + SET_BATCH_DETAIL_ROWS: (state, data) => { + state.batchDetailRows = data || []; }, - CLEAR_SELLER_TEMP_INFO: state => { - state.sellerTempInfo = []; + CLEAR_BATCH_DETAIL_ROWS: state => { + state.batchDetailRows = []; }, - SET_PURCHASE_TEMPLATE: (state, data) => { - state.purchaseTemplateData = data; + SET_CURRENT_VOUCHER: (state, data) => { + state.currentVoucher = data || ''; }, - CLEAR_PURCHASE_TEMPLATE: state => { - state.purchaseTemplateData = []; + SET_CURRENT_MODE: (state, data) => { + state.currentMode = data || 'in'; }, - SET_SELLER_TEMPLATE: (state, data) => { - state.sellerTemplateData = data; - }, - CLEAR_SELLER_TEMPLATE: state => { - state.sellerTemplateData = []; - }, - SET_BATCH_META_MAP: (state, data) => { - state.batchMetaMap = data || {}; - }, - CLEAR_BATCH_META_MAP: state => { - state.batchMetaMap = {}; + // 更新批次详情中某条记录的开票状态 + UPDATE_BATCH_ROW_INVOICED: (state, { id, invoiced, invoiceId }) => { + const row = state.batchDetailRows.find(r => r.id === id); + if (row) { + row.invoiced = invoiced; + if (invoiceId) { + row.invoiceId = invoiceId; + } + } } }; const actions = { - setExcelData({ commit }, data) { - commit('SET_EXCEL_DATA', data); - }, - clearExcelData({ commit }) { - commit('CLEAR_EXCEL_DATA'); - }, setTicketPoint({ commit }, data) { commit('SET_TICKET_POINT', data); }, clearTicketPoint({ commit }) { commit('CLEAR_TICKET_POINT'); }, - setComment({ commit }, data) { commit('SET_COMMENT', data); }, @@ -156,35 +125,20 @@ const actions = { subtractInvoiceAmount({ commit }, data) { commit('SUBTRACT_INVOICE_AMOUNT', data); }, - setPurchaseTempInfo({ commit }, data) { - commit('SET_PURCHASE_TEMP_INFO', data); - }, - clearPurchaseTempInfo({ commit }) { - commit('CLEAR_PURCHASE_TEMP_INFO'); - }, - setSellerTempInfo({ commit }, data) { - commit('SET_SELLER_TEMP_INFO', data); - }, - clearSellerTempInfo({ commit }) { - commit('CLEAR_SELLER_TEMP_INFO'); - }, - setPurchaseTemplateData({ commit }, data) { - commit('SET_PURCHASE_TEMPLATE', data); - }, - clearPurchaseTemplateData({ commit }) { - commit('CLEAR_PURCHASE_TEMPLATE'); + setBatchDetailRows({ commit }, data) { + commit('SET_BATCH_DETAIL_ROWS', data); }, - setSellerTemplateData({ commit }, data) { - commit('SET_SELLER_TEMPLATE', data); + clearBatchDetailRows({ commit }) { + commit('CLEAR_BATCH_DETAIL_ROWS'); }, - clearSellerTemplateData({ commit }) { - commit('CLEAR_SELLER_TEMPLATE'); + setCurrentVoucher({ commit }, data) { + commit('SET_CURRENT_VOUCHER', data); }, - setBatchMetaMap({ commit }, data) { - commit('SET_BATCH_META_MAP', data); + setCurrentMode({ commit }, data) { + commit('SET_CURRENT_MODE', data); }, - clearBatchMetaMap({ commit }) { - commit('CLEAR_BATCH_META_MAP'); + updateBatchRowInvoiced({ commit }, payload) { + commit('UPDATE_BATCH_ROW_INVOICED', payload); } }; diff --git a/packages/order-system/src/views/dashboard/components/common/BatchInvoicePanel.vue b/packages/order-system/src/views/dashboard/components/common/BatchInvoicePanel.vue index 3d2cb6194..22dcb6b68 100644 --- a/packages/order-system/src/views/dashboard/components/common/BatchInvoicePanel.vue +++ b/packages/order-system/src/views/dashboard/components/common/BatchInvoicePanel.vue @@ -2,13 +2,19 @@ import { create, all } from 'mathjs'; import { getCompany } from '@/api/system/company'; import InvoiceCompanysList from '@/views/dashboard/components/common/InvoiceCompanysList.vue'; -// import CompanyInformation from '@/views/dashboard/components/common/CompanyInformation.vue'; import QueueInvoiceList from '@/views/dashboard/components/common/QueueInvoiceList.vue'; import SelectGoods from '@/views/dashboard/components/common/SelectGoods.vue'; -import { mixin_excel_server } from '@/views/dashboard/components/common/utils/excelServer'; import DragDiv from '@/components/DragDiv/index.vue'; -import { getOperatedMap } from '@/api/excelTemplateStore'; -import { listBatchInvoiceIn, listBatchInvoiceOut, deleteBatchInvoiceInByVoucher, deleteBatchInvoiceInById, deleteBatchInvoiceInInvoice, deleteBatchInvoiceOutByVoucher, deleteBatchInvoiceOutById, deleteBatchInvoiceOutInvoice } from '@/api/system/batchInvoice'; +import { + listBatchInvoiceIn, + listBatchInvoiceOut, + deleteBatchInvoiceInByVoucher, + deleteBatchInvoiceInById, + deleteBatchInvoiceInInvoice, + deleteBatchInvoiceOutByVoucher, + deleteBatchInvoiceOutById, + deleteBatchInvoiceOutInvoice +} from '@/api/system/batchInvoice'; // 默认导出组件 export default { @@ -18,7 +24,6 @@ export default { currentSide() { const hasPurchase = this.purchaseTotalInfo && this.purchaseTotalInfo.length > 0; const hasSeller = this.sellerTotalInfo && this.sellerTotalInfo.length > 0; - // 优先显示购买方,如果购买方有数据就显示购买方,否则显示销方 return hasPurchase ? 'purchase' : hasSeller ? 'seller' : 'purchase'; }, // 当前显示的公司列表数据 @@ -40,12 +45,9 @@ export default { components: { InvoiceCompanysList, QueueInvoiceList, - // CompanyInformation, SelectGoods, DragDiv }, - mixins: [mixin_excel_server], - // 接收文件读取到的sheetList 渲染出来给用户看 并且可以选择看哪一个 props: { mode: { type: String, @@ -74,45 +76,32 @@ export default { total: 0 }, currentVoucher: '', - // 左上角供应商的信息 companyInfo: {}, - // 本批开的票点 - // 订单选择弹窗 invoiceAllVisible: false, - // 供应商价税合计表 invoiceSupplierList: [], - // 购买方统计 + // 购买方统计(从后端获取) purchaseTotalInfo: [], - // 销方统计 + // 销方统计(从后端获取) sellerTotalInfo: [], + // 原始批次数据(从后端获取) + batchDetailRows: [], // 我方公司搜索字段 myCompany: null, // 对方公司搜索字段 otherCompany: null, // 已操作状态搜索字段(null: 全部, true: 已操作, false: 未操作) operatedStatus: null, - // 减去的金额 - minusValue: 0, // 统计信息 statisticsInfo: { - // 购买方统计 purchaseStats: { - suppliers: { total: 0, count: 0 }, // 供应商作为购买方的统计 - customers: { total: 0, count: 0 } // 客户作为购买方的统计 + suppliers: { total: 0, count: 0 }, + customers: { total: 0, count: 0 } }, - // 销方统计 sellerStats: { - suppliers: { total: 0, count: 0 }, // 供应商作为销方的统计 - customers: { total: 0, count: 0 } // 客户作为销方的统计 + suppliers: { total: 0, count: 0 }, + customers: { total: 0, count: 0 } } - }, - // 模板数据(按对方身份拆分) - // 本次导入的版本号(时间戳) - currentVersion: null, - // 已操作映射(来自 IndexedDB) - templateOperatedMap: {} - // 当前Sheet名称 - // currentSheetName 已废弃 + } }; }, watch: { @@ -129,30 +118,11 @@ export default { this.math = create(all, { number: 'BigNumber', precision: 64 }); }, mounted() { - // 支持外部触发"继续上次开票" - this.$bus.$on('excel:resume', this.openFromSession); - // 监听已操作状态变更,实时刷新映射 - this.$bus.$on('excel:operated-updated', async payload => { - const { companyIds } = payload || {}; - // 乐观更新:先把传入的公司ID标记为已操作 - if (Array.isArray(companyIds) && companyIds.length > 0) { - const nextMap = { ...(this.templateOperatedMap || {}) }; - companyIds.forEach(id => (nextMap[id] = true)); - this.templateOperatedMap = nextMap; - } - // 再从 IndexedDB 拉一次,确保最终一致 - try { - const map = await getOperatedMap(); - this.templateOperatedMap = map || {}; - this.$nextTick(() => {}); - } catch (e) { - console.error('刷新已操作映射失败:', e); - } - }); + // 监听开票成功事件,刷新数据 + this.$bus.$on('batch-invoice:refresh', this.handleRefreshBatchData); }, beforeDestroy() { - this.$bus.$off('excel:resume', this.openFromSession); - this.$bus.$off('excel:operated-updated'); + this.$bus.$off('batch-invoice:refresh', this.handleRefreshBatchData); }, methods: { getApiHandlers() { @@ -270,25 +240,27 @@ export default { hasInvoiceInfo(row) { return !!(row && row.invoiceId); }, + // 打开批量开票全屏弹窗 async handleOpenBatch(row) { if (!row || !row.voucher) { this.$message.warning('未找到有效的批次号'); return; } this.reset(); - this.handleClearPurchaseInfo(); - this.handleClearSellerInfo(); this.currentVoucher = row.voucher; + // 从后端获取批次详情 const batchRows = await this.fetchVoucherDetails(row.voucher); if (!batchRows || batchRows.length === 0) { this.$message.warning('该批次暂无明细数据'); return; } - this.currentVersion = Date.now(); - await this.processExcelData(batchRows); + // 保存原始数据 + this.batchDetailRows = batchRows; + // 处理并聚合数据 + this.processAndAggregateData(batchRows); this.invoiceAllVisible = true; - this.saveBatchInvoiceSession({ voucher: row.voucher }); }, + // 从后端获取批次详情 async fetchVoucherDetails(voucher) { const api = this.getApiHandlers(); if (!api || !api.list) { @@ -307,173 +279,104 @@ export default { return []; } }, - // 保存当前批量开票会话(模板/聚合/统计/已生成列表) - saveBatchInvoiceSession(extra = {}) { - try { - const payload = { - purchaseTemplateData: this.$store?.state?.excel?.purchaseTemplateData || [], - sellerTemplateData: this.$store?.state?.excel?.sellerTemplateData || [], - purchaseTotalInfo: this.purchaseTotalInfo || [], - sellerTotalInfo: this.sellerTotalInfo || [], - statisticsInfo: this.statisticsInfo || {}, - generatedInvoices: this.$store?.getters?.selectedInvoiceList || [], - timestamp: Date.now(), - ...extra - }; - localStorage.setItem('batch-invoice-session', JSON.stringify(payload)); - } catch (e) { - console.error('保存批量开票会话失败:', e); + // 刷新批次数据(开票成功后调用) + async handleRefreshBatchData() { + if (!this.currentVoucher) { + return; } - }, - // 从本地恢复批量开票会话,并直接打开全屏弹窗 - openFromSession() { - try { - const raw = localStorage.getItem('batch-invoice-session'); - if (!raw) { - this.$message.info('暂无上次开票会话'); - return; - } - const session = JSON.parse(raw); - // 恢复版本号 - this.currentVersion = session && session.timestamp ? session.timestamp : null; - // 恢复模板到 Vuex - if (Array.isArray(session.purchaseTemplateData)) { - this.$store.dispatch('excel/setPurchaseTemplateData', session.purchaseTemplateData); - } - if (Array.isArray(session.sellerTemplateData)) { - this.$store.dispatch('excel/setSellerTemplateData', session.sellerTemplateData); - } - // 恢复聚合与统计 - this.purchaseTotalInfo = Array.isArray(session.purchaseTotalInfo) ? session.purchaseTotalInfo : []; - this.sellerTotalInfo = Array.isArray(session.sellerTotalInfo) ? session.sellerTotalInfo : []; - this.statisticsInfo = session.statisticsInfo || { - purchaseStats: { suppliers: { total: 0, count: 0 }, customers: { total: 0, count: 0 } }, - sellerStats: { suppliers: { total: 0, count: 0 }, customers: { total: 0, count: 0 } } - }; - // 恢复已生成的发票清单(如有) - if (Array.isArray(session.generatedInvoices)) { - this.$store.dispatch('excel/setSelectedInvoiceList', session.generatedInvoices); - } - // 打开全屏弹窗 - this.invoiceAllVisible = true; - // 加载已操作映射 - getOperatedMap().then(map => (this.templateOperatedMap = map || {})); - } catch (e) { - console.error('恢复批量开票会话失败:', e); - this.$message.error('无法恢复上次开票会话'); + const batchRows = await this.fetchVoucherDetails(this.currentVoucher); + if (batchRows && batchRows.length > 0) { + this.batchDetailRows = batchRows; + this.processAndAggregateData(batchRows); } + // 同时刷新列表 + this.fetchBatchList(); }, - /** - * 处理批量数据 - * @param {Array} rows - 批次明细 - */ - async processExcelData(rows = []) { - let arr = []; + // 处理并聚合后端数据(纯计算,不存入 Vuex) + processAndAggregateData(rows = []) { let purchaseMap = new Map(); let sellerMap = new Map(); + const arr = []; - for (let item of rows) { - const mapped = this.mapperParams(item); + for (const item of rows) { + const mapped = this.mapBackendData(item); if (mapped) { arr.push(mapped); } } - // 过滤掉arr中 属性全部为undefined的元素 - arr = arr.filter(item => item && !Object.values(item).every(value => value === undefined || value === null)); - // 检查excel中是否有同时存在的 - let ok = arr.every(item => this.purchaseHandler(item)); - if (!ok) { - this.$message.error('存在订单中存在购买方和销方的信息,请检查'); + // 过滤无效数据 + const validArr = arr.filter(item => item && !Object.values(item).every(v => v === undefined || v === null)); + + // 检查数据有效性 + const isValid = validArr.every(item => this.validateCompanyData(item)); + if (!isValid) { + this.$message.error('数据中存在同时包含购买方和销方信息的记录,请检查'); return; } - // 构建批次校验信息 - const batchMetaMap = {}; - arr.forEach(element => { - const batchId = element.batchInvoiceId || element.id; - if (batchId && batchMetaMap[batchId] === undefined) { - batchMetaMap[batchId] = { - totalAmount: element.total || 0, - voucher: element.voucher || this.currentVoucher - }; - } - }); - this.$store.dispatch('excel/setBatchMetaMap', batchMetaMap); - // 对数组每一个进行遍历 收集元素 - arr.forEach(element => { - // 判断对方是否是购买方 + + // 聚合公司信息 + validArr.forEach(element => { + // 判断对方是否是购买方(sellerId 为 0 表示对方是购买方) const isPurchase = element.sellerId === 0; - // 根据判断选择 Map const map = isPurchase ? purchaseMap : sellerMap; - // 购买方或销售方的 id const id = isPurchase ? element.purchaseId : element.sellerId; - // 购买方或销售方的 name const name = isPurchase ? element.purchaseName : element.sellerName; - // 购买方或销售方的 type const type = isPurchase ? element.purchaseType : element.sellerType; - // 必然有一方是我方 对方如果是购买方 那么我方就是销售方 反之一样 + // 我方公司名称 const us = isPurchase ? element.sellerName : element.purchaseName; - // 确保 id 不为 undefined 或空值 + if (id == null || id === '') { - return; // 跳过当前元素 + return; } - // 唯一键 - const _onlyKey = id + us; - // 获取当前 Map 中的记录,如果存在则累加总数,不存在则直接插入 + + // 唯一键:公司ID + 我方公司 + const _onlyKey = id + '::' + us; const _existing = map.get(_onlyKey); if (_existing) { - const totalSum = this.math.add(this.math.bignumber(_existing.total || 0), this.math.bignumber(element.total || 0)); - const ticketSum = this.math.add(this.math.bignumber(_existing.ticketPointAmount || 0), this.math.bignumber(element.ticketPointAmount || 0)); - _existing.total = Number(this.math.format(totalSum, { precision: 12, notation: 'fixed' })); - _existing.ticketPointAmount = Number(this.math.format(ticketSum, { precision: 12, notation: 'fixed' })); + // 累加金额(仅累加未开票的记录) + if (!element.invoiced) { + const totalSum = this.math.add(this.math.bignumber(_existing.total || 0), this.math.bignumber(element.total || 0)); + const ticketSum = this.math.add(this.math.bignumber(_existing.ticketPointAmount || 0), this.math.bignumber(element.ticketPointAmount || 0)); + _existing.total = Number(this.math.format(totalSum, { precision: 12, notation: 'fixed' })); + _existing.ticketPointAmount = Number(this.math.format(ticketSum, { precision: 12, notation: 'fixed' })); + } + // 记录相关的批次ID + if (!_existing.batchIds.includes(element.id)) { + _existing.batchIds.push(element.id); + } + // 更新已操作状态(只要有一条已开票,就标记为已操作) + if (element.invoiced) { + _existing.invoiced = true; + } } else { map.set(_onlyKey, { id, type, name, us, - total: element.total, + total: element.invoiced ? 0 : element.total, ticketPoint: element.ticketPoint, - ticketPointAmount: element.ticketPointAmount + ticketPointAmount: element.invoiced ? 0 : element.ticketPointAmount, + invoiced: element.invoiced || false, + batchIds: [element.id] }); } }); + this.purchaseTotalInfo = Array.from(purchaseMap.values()); this.sellerTotalInfo = Array.from(sellerMap.values()); - // 保存模板原始数据到 Vuex,按对方身份拆分 - this.$store.dispatch( - 'excel/setPurchaseTemplateData', - arr.filter(e => e && e.sellerId === 0) - ); - this.$store.dispatch( - 'excel/setSellerTemplateData', - arr.filter(e => e && e.sellerId !== 0) - ); - // 计算统计信息 - this.calculateStatistics(arr); - - // 暂存购买方和销方的信息 - this.handleStorePurchaseInfo(this.purchaseTotalInfo); - this.handleStoreSellerInfo(this.sellerTotalInfo); - // 保存一次会话快照 - this.saveBatchInvoiceSession(); - getOperatedMap().then(map => (this.templateOperatedMap = map || {})); + this.calculateStatistics(validArr); + + // 存储原始数据供 QueueInvoiceList 使用 + this.$store.dispatch('excel/setBatchDetailRows', validArr); }, - // 映射参数:将后端数据或Excel原始数据转换为统一格式 - mapperParams(item) { + // 映射后端数据为统一格式 + mapBackendData(item) { if (!item) return null; - // 已结构化的后端数据 - if (item.sellerId !== undefined || item.purchaseId !== undefined || item.batchInvoiceId) { - return this.mapStructuredData(item); - } - // Excel 原始数据(中文表头) - return this.mapExcelData(item); - }, - // 映射已结构化的后端数据 - mapStructuredData(item) { - const totalAmount = Number(item.totalAmount ?? item.total ?? item.invoiceAmount ?? 0); + const totalAmount = Number(item.totalAmount ?? item.total ?? 0); const ticketPoint = Number(item.taxPoint ?? item.ticketPoint ?? 0); const ticketPointAmountRaw = item.ticketPointAmount; const normalizedTicketPointAmount = ticketPointAmountRaw !== undefined && ticketPointAmountRaw !== null ? Number(ticketPointAmountRaw) : this.calculateTicketPointAmount(totalAmount, ticketPoint); @@ -485,34 +388,16 @@ export default { purchaseId: Number(item.buyerId ?? item.purchaseId) || 0, purchaseType: item.buyerType ?? item.purchaseType ?? '', purchaseName: item.buyerName ?? item.purchaseName ?? '', - total: Number(totalAmount), - ticketPoint: ticketPoint, - ticketPointAmount: Number(normalizedTicketPointAmount), - batchInvoiceId: item.batchInvoiceId || item.id || null, - voucher: item.voucher || '', - comments: item.comments || '', - params: item.params || {}, - id: item.id - }; - }, - // 映射Excel原始数据 - mapExcelData(item) { - const ticketPoint = Number(item['票点']) || 0; - const totalAmount = Number(item['价税合计']) || 0; - const ticketPointAmount = this.calculateTicketPointAmount(totalAmount, ticketPoint); - - return { - sellerId: item['销方ID'], - sellerName: item['销方名称'], - sellerType: item['销方类型'], - purchaseId: item['购买方ID'], - purchaseType: item['购买方类型'], - purchaseName: item['购买方名称'], total: totalAmount, ticketPoint: ticketPoint, - ticketPointAmount: Number(ticketPointAmount.toFixed(2)) + ticketPointAmount: normalizedTicketPointAmount, + id: item.id, + voucher: item.voucher || '', + invoiced: item.invoiced || false, + invoiceId: item.invoiceId || null }; }, + // 计算票点金额 calculateTicketPointAmount(totalAmount, ticketPoint) { const total = this.math ? this.math.bignumber(totalAmount || 0) : totalAmount || 0; const rate = this.math ? this.math.bignumber(ticketPoint || 0) : ticketPoint || 0; @@ -529,7 +414,6 @@ export default { }, // 计算统计信息 calculateStatistics(dataArray) { - // 重置统计信息 this.statisticsInfo = { purchaseStats: { suppliers: { total: 0, count: 0 }, @@ -541,49 +425,51 @@ export default { } }; - // 用唯一 ID 去重计数,避免同一公司被重复计数 const purchaseSupplierIds = new Set(); const purchaseCustomerIds = new Set(); const sellerSupplierIds = new Set(); const sellerCustomerIds = new Set(); dataArray.forEach(element => { + // 只统计未开票的记录 + if (element.invoiced) { + return; + } const amount = Number(element.total) || 0; - // 统计购买方(排除己方公司) + // 统计购买方 if (element.purchaseType && element.purchaseType !== '己方公司') { const pid = element.purchaseId; if (element.purchaseType === '供应商') { this.statisticsInfo.purchaseStats.suppliers.total += amount; - if (pid !== undefined && pid !== null && pid !== '' && Number(pid) !== 0) { + if (pid && Number(pid) !== 0) { purchaseSupplierIds.add(String(pid)); } } else if (element.purchaseType === '客户') { this.statisticsInfo.purchaseStats.customers.total += amount; - if (pid !== undefined && pid !== null && pid !== '' && Number(pid) !== 0) { + if (pid && Number(pid) !== 0) { purchaseCustomerIds.add(String(pid)); } } } - // 统计销方(排除己方公司) + // 统计销方 if (element.sellerType && element.sellerType !== '己方公司') { const sid = element.sellerId; if (element.sellerType === '供应商') { this.statisticsInfo.sellerStats.suppliers.total += amount; - if (sid !== undefined && sid !== null && sid !== '' && Number(sid) !== 0) { + if (sid && Number(sid) !== 0) { sellerSupplierIds.add(String(sid)); } } else if (element.sellerType === '客户') { this.statisticsInfo.sellerStats.customers.total += amount; - if (sid !== undefined && sid !== null && sid !== '' && Number(sid) !== 0) { + if (sid && Number(sid) !== 0) { sellerCustomerIds.add(String(sid)); } } } }); - // 用去重后的 ID 数量作为 count this.statisticsInfo.purchaseStats.suppliers.count = purchaseSupplierIds.size; this.statisticsInfo.purchaseStats.customers.count = purchaseCustomerIds.size; this.statisticsInfo.sellerStats.suppliers.count = sellerSupplierIds.size; @@ -595,25 +481,17 @@ export default { this.statisticsInfo.sellerStats.suppliers.total = Number(this.statisticsInfo.sellerStats.suppliers.total.toFixed(2)); this.statisticsInfo.sellerStats.customers.total = Number(this.statisticsInfo.sellerStats.customers.total.toFixed(2)); }, - // 对公司进行校验 - purchaseHandler(item) { - // 如果都为0 + // 校验公司数据有效性 + validateCompanyData(item) { if (item.purchaseId === 0 && item.sellerId === 0) { return false; - // 如果购买方和销方的id都不为0 - } else return !(item.purchaseId !== 0 && item.sellerId !== 0); - }, - // 判断公司是否已操作 - isCompanyOperated(row) { - if (!row || !row.id) return false; - const companyId = Number(row.id); - return !!(companyId && this.templateOperatedMap && this.templateOperatedMap[companyId]); + } + return !(item.purchaseId !== 0 && item.sellerId !== 0); }, // 弹窗左侧供应商列表的筛选 handleFilter() { - // 从暂存数据中获取原始数据 - const purchaseTempData = this.$store.getters.purchaseTempInfo || []; - const sellerTempData = this.$store.getters.sellerTempInfo || []; + // 重新从原始数据聚合 + this.processAndAggregateData(this.batchDetailRows); // 通用筛选函数 const filterItems = items => { @@ -630,10 +508,9 @@ export default { return false; } } - // 已操作状态筛选 + // 已操作状态筛选(基于后端的 invoiced 字段) if (this.operatedStatus !== null) { - const isOperated = this.isCompanyOperated(item); - if (isOperated !== this.operatedStatus) { + if (item.invoiced !== this.operatedStatus) { return false; } } @@ -641,15 +518,15 @@ export default { }); }; - // 根据当前显示的一方进行筛选 + // 应用筛选 if (this.currentSide === 'purchase') { - this.purchaseTotalInfo = filterItems(purchaseTempData); + this.purchaseTotalInfo = filterItems(this.purchaseTotalInfo); } else { - this.sellerTotalInfo = filterItems(sellerTempData); + this.sellerTotalInfo = filterItems(this.sellerTotalInfo); } }, - //查看某一个公司的信息 + // 查看某一个公司的信息 handleCheck(row) { this.handleResetCompanyInfo(); this.companyInfo.supplierLoading = true; @@ -676,12 +553,8 @@ export default { }, // 重置筛选结果 handleReset() { - // 根据当前显示的一方重置数据 - if (this.currentSide === 'purchase') { - this.purchaseTotalInfo = this.$store.getters.purchaseTempInfo || []; - } else { - this.sellerTotalInfo = this.$store.getters.sellerTempInfo || []; - } + // 重新从原始数据聚合 + this.processAndAggregateData(this.batchDetailRows); // 清空搜索条件 this.myCompany = null; this.otherCompany = null; @@ -714,12 +587,19 @@ export default { clearSessionStorage() { sessionStorage.removeItem('us'); sessionStorage.removeItem('invoiceAmount'); + sessionStorage.removeItem('companyList_selected_company_id'); + sessionStorage.removeItem('merged_company_info'); }, // 清除组件状态 clearComponentState() { this.handleResetCompanyInfo(); this.handleResetOrderList(); - this.handleReset(); + this.purchaseTotalInfo = []; + this.sellerTotalInfo = []; + this.batchDetailRows = []; + this.myCompany = null; + this.otherCompany = null; + this.operatedStatus = null; this.statisticsInfo = { purchaseStats: { suppliers: { total: 0, count: 0 }, @@ -735,9 +615,9 @@ export default { clearVuexState() { this.$store.dispatch('excel/clearTicketPoint'); this.$store.dispatch('excel/clearComment'); - this.$store.dispatch('excel/clearPurchaseTemplateData'); - this.$store.dispatch('excel/clearSellerTemplateData'); - this.$store.dispatch('excel/clearBatchMetaMap'); + this.$store.dispatch('excel/clearBatchDetailRows'); + this.$store.dispatch('excel/clearSelectedInvoiceList'); + this.$store.dispatch('excel/clearInvoiceAmount'); } } }; @@ -867,7 +747,8 @@ export default { :side="currentSide" :company-total-info="currentCompanyTotalInfo" :statistics-info="currentStatisticsInfo" - :operated-map="templateOperatedMap" + :mode="mode" + :voucher="currentVoucher" @handleCheck="handleCheck" />
@@ -881,7 +762,7 @@ export default { diff --git a/packages/order-system/src/views/dashboard/components/common/InvoiceCompanysList.vue b/packages/order-system/src/views/dashboard/components/common/InvoiceCompanysList.vue index e059c9b09..6fb6eae37 100644 --- a/packages/order-system/src/views/dashboard/components/common/InvoiceCompanysList.vue +++ b/packages/order-system/src/views/dashboard/components/common/InvoiceCompanysList.vue @@ -27,10 +27,15 @@ export default { }; } }, - // 来自父组件(BatchInvoicePanel)的 IndexedDB 已操作映射:{ 'id::name': true/false } - operatedMap: { - type: Object, - default: () => ({}) + // 当前批量开票模式 + mode: { + type: String, + default: 'in' + }, + // 当前批量开票凭证号 + voucher: { + type: String, + default: '' } }, data() { @@ -40,41 +45,34 @@ export default { }; }, computed: { - // 从 Vuex 获取模板数据 - purchaseTemplateData() { - return this.$store.state.excel.purchaseTemplateData || []; - }, - sellerTemplateData() { - return this.$store.state.excel.sellerTemplateData || []; + // 从 Vuex 获取批次详情数据 + batchDetailRows() { + return this.$store.getters.batchDetailRows || []; }, selectedTemplateData() { - return this.side === 'purchase' ? this.purchaseTemplateData : this.sellerTemplateData; + // 根据 side 筛选批次数据 + if (this.side === 'purchase') { + return this.batchDetailRows.filter(row => row.sellerId === 0); + } + return this.batchDetailRows.filter(row => row.sellerId !== 0); } }, mounted() { // 重置行的样式 this.$bus.$on('select-goods-row:update', () => (this.selectedRowId = null)); - - // 监听已操作状态更新事件 - this.$bus.$on('excel:operated-updated', payload => { - // 强制重新渲染组件以显示最新状态 - this.$forceUpdate(); - }); }, beforeDestroy() { - // 清除事件监听 防止内存泄漏 - this.$bus.$off('select-goods:update'); // 清理事件监听 - this.$bus.$off('excel:operated-updated'); // 清理已操作状态更新监听 + this.$bus.$off('select-goods-row:update'); }, methods: { - // 获取公司ID(直接使用row.id作为公司ID) + // 获取公司ID getCompanyId(row) { if (!row) return null; return Number(row.id) || null; }, + // 判断是否已操作(基于后端返回的 invoiced 字段) isOperated(row) { - const companyId = this.getCompanyId(row); - return !!(companyId && this.operatedMap && this.operatedMap[companyId]); + return !!(row && row.invoiced); }, openTemplateViewer() { // 仅在有数据时打开 diff --git a/packages/order-system/src/views/dashboard/components/common/InvoiceOptionPanel.vue b/packages/order-system/src/views/dashboard/components/common/InvoiceOptionPanel.vue index 38de3c79a..af1017fda 100644 --- a/packages/order-system/src/views/dashboard/components/common/InvoiceOptionPanel.vue +++ b/packages/order-system/src/views/dashboard/components/common/InvoiceOptionPanel.vue @@ -39,15 +39,10 @@ export default { } }, methods: { - // 恢复上次开票流程,直接打开批量开票全屏弹窗 + // 查看开票记录(打开管理记录弹窗) resumeLast() { - const raw = localStorage.getItem('batch-invoice-session'); - if (!raw) { - this.$message.info('暂无上次开票会话'); - return; - } - this.$bus.$emit('excel:resume'); - this.dialogVisible = true; + this.$message.info('请在管理记录中查看和继续开票'); + this.handleManage(); }, // 点击上传按钮 handleUpload() { diff --git a/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue b/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue index c7678b325..15e9bd675 100644 --- a/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue +++ b/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue @@ -8,7 +8,10 @@ import { getUuid } from '@/utils/trash/utils'; import { TableName } from '@/api/tool/enums'; import { common_dialog } from '@/views/dashboard/mixins/common/common_dialog'; import { batchInvoice } from '@/api/system/excel'; -import { markCompanyOperated } from '@/api/excelTemplateStore'; +import { + batchUpdateBatchInvoiceInInvoiced, + batchUpdateBatchInvoiceOutInvoiced +} from '@/api/system/batchInvoice'; import INVOICE_OUT from '@/components/NeedToShow/INVOICE_OUT.vue'; import INVOICE_IN from '@/components/NeedToShow/INVOICE_IN.vue'; @@ -16,7 +19,12 @@ export default { name: 'QueueInvoiceList', components: { InvoiceItem }, mixins: [common_dialog], - props: {}, + props: { + mode: { + type: String, + default: 'in' + } + }, watch: { // 监听选择订单的变化 selectedOrder: { @@ -50,27 +58,16 @@ export default { PUBLIC_DICT_TYPE() { return PUBLIC_DICT_TYPE; }, - ...mapGetters(['selectedInvoiceList', 'selectedOrder', 'ticketPoint', 'comment', 'invoiceAmount']), + ...mapGetters(['selectedInvoiceList', 'selectedOrder', 'ticketPoint', 'comment', 'invoiceAmount', 'batchDetailRows']), // 是否已有生成的发票列表,用于控制“开具发票”按钮是否可用 hasGeneratedInvoices() { return Array.isArray(this.selectedInvoiceList) && this.selectedInvoiceList.length > 0; } }, methods: { - // 保存当前生成结果到本地(与 BatchInvoicePanel 会话格式兼容) - saveGeneratedInvoicesSession() { - try { - const raw = localStorage.getItem('batch-invoice-session'); - const base = raw ? JSON.parse(raw) : {}; - // 保持版本号(timestamp)不变,仅更新已生成清单 - const payload = { - ...base, - generatedInvoices: this.$store?.getters?.selectedInvoiceList || [] - }; - localStorage.setItem('batch-invoice-session', JSON.stringify(payload)); - } catch (e) { - console.error('保存开票生成结果失败:', e); - } + // 获取更新开票状态的API + getUpdateInvoicedApi() { + return this.mode === 'out' ? batchUpdateBatchInvoiceOutInvoiced : batchUpdateBatchInvoiceInInvoiced; }, // 创建发票对象的工具函数 createInvoiceObject(params, extra = {}) { @@ -148,6 +145,18 @@ export default { // 如果成功 if (result.flag) { + // 收集需要更新的批次记录ID + const batchIds = filteredInvoices.map(inv => inv.batchInvoiceId).filter(id => id != null); + // 调用后端API更新开票状态 + if (batchIds.length > 0) { + try { + const updateApi = this.getUpdateInvoicedApi(); + await updateApi(batchIds, true); + } catch (e) { + console.error('更新后端开票状态失败:', e); + } + } + // 告诉订单列表重新加载 this.$bus.$emit('select-goods:update'); @@ -156,47 +165,19 @@ export default { this.$store.dispatch('excel/clearSelectedInvoiceList'); this.$store.dispatch('excel/clearInvoiceAmount'); } + // 清理 sessionStorage 中的临时开票数据 sessionStorage.removeItem('invoiceAmount'); sessionStorage.removeItem('us'); - // 标记公司为已操作——放在清理事件之前,避免 sessionStorage 被清空 - try { - // 优先根据 InvoiceCompanysList 选中行的公司ID精确标记 - const selectedCompanyId = sessionStorage.getItem('companyList_selected_company_id'); - console.log('selectedCompanyId', selectedCompanyId); - if (selectedCompanyId && selectedCompanyId !== 'null' && selectedCompanyId !== 'undefined') { - await markCompanyOperated(Number(selectedCompanyId)); - console.log('已标记公司为已操作:', selectedCompanyId); - this.$nextTick(() => { - this.$bus.$emit('excel:operated-updated', { companyIds: [Number(selectedCompanyId)] }); - }); - } else { - // 退化为根据当前公司信息批量标记 - let companyId = null; - if (this.invoiceType === this.PUBLIC_DICT_TYPE.SUPPLIER && this.supplierId) { - companyId = this.supplierId; - } - if (this.invoiceType === this.PUBLIC_DICT_TYPE.CUSTOMER) { - const anyOrder = (this.$store.getters.selectedOrder || [])[0]; - companyId = anyOrder ? anyOrder.customerID : null; - } - if (companyId !== null && companyId !== undefined) { - await markCompanyOperated(Number(companyId)); - console.log('已根据公司ID标记为已操作:', companyId); - this.$nextTick(() => { - this.$bus.$emit('excel:operated-updated', { companyIds: [Number(companyId)] }); - }); - } - } - } catch (e) { - console.error('标记公司已操作失败:', e); - } + sessionStorage.removeItem('companyList_selected_company_id'); + sessionStorage.removeItem('merged_company_info'); - // 广播清理事件(放在回写之后) + // 广播清理事件 this.$bus.$emit('invoice-clear'); this.$message.success('本批开票成功'); - // 成功后保存一次快照,记录清空后的状态 - this.saveGeneratedInvoicesSession(); + + // 通知父组件刷新数据 + this.$bus.$emit('batch-invoice:refresh'); } else { this.$message.error('本批开票有误 请检查错误信息后重新提交'); @@ -208,10 +189,24 @@ export default { }, validateBatchAmounts(invoices = []) { - const metaMap = this.$store?.state?.excel?.batchMetaMap || {}; if (!invoices.length) { return true; } + // 从 batchDetailRows 构建校验信息 + const batchDetailRows = this.$store?.getters?.batchDetailRows || []; + if (!batchDetailRows.length) { + return true; // 无原始数据时跳过校验 + } + + // 构建每个批次记录ID的预期金额映射 + const expectedAmountMap = {}; + batchDetailRows.forEach(row => { + if (row.id && !row.invoiced) { + expectedAmountMap[row.id] = Number(row.total || 0); + } + }); + + // 计算每个批次记录的开票金额 const sums = {}; invoices.forEach(item => { const batchId = item?.batchInvoiceId; @@ -224,18 +219,18 @@ export default { sums[batchId] = this.math.add(sums[batchId], this.math.bignumber(item.invoiceAmount || 0)); }); + // 校验金额是否一致 for (const batchId of Object.keys(sums)) { - const meta = metaMap[batchId]; - if (!meta || meta.totalAmount === undefined || meta.totalAmount === null) { - this.$message.error(`批次 ${batchId} 缺少导入金额,请重新载入批次数据`); - return false; + const expectedAmount = expectedAmountMap[batchId]; + if (expectedAmount === undefined) { + continue; // 跳过无法校验的记录 } - const expected = this.math.bignumber(meta.totalAmount || 0); + const expected = this.math.bignumber(expectedAmount); const diff = this.math.subtract(sums[batchId], expected); if (!this.math.equal(this.math.round(diff, 2), this.math.bignumber(0))) { const sumFormatted = Number(this.math.format(sums[batchId], { precision: 12, notation: 'fixed' })).toFixed(2); const expectedFormatted = Number(this.math.format(expected, { precision: 12, notation: 'fixed' })).toFixed(2); - this.$message.error(`批次 ${batchId} 的开票金额 ${sumFormatted} 与导入金额 ${expectedFormatted} 不一致`); + this.$message.error(`批次记录 ${batchId} 的开票金额 ${sumFormatted} 与导入金额 ${expectedFormatted} 不一致`); return false; } } @@ -299,119 +294,97 @@ export default { }, /** - * 生成发票(按模板分配) - * 从订单和模板数据中自动生成发票列表 + * 生成发票(按批次数据分配) + * 从订单和后端批次数据中自动生成发票列表 */ generateInvoicesByTemplates() { - // 获取当前选择订单 const orders = this.$store.getters.selectedOrder || []; if (!orders || orders.length === 0) { - return; // 静默返回,避免频繁提示 + return; } - // 读取并合并模板数据(购买方+销方) - const purchaseTemplates = this.$store.state.excel.purchaseTemplateData || []; - const sellerTemplates = this.$store.state.excel.sellerTemplateData || []; - const templates = purchaseTemplates.concat(sellerTemplates); - if (!templates || templates.length === 0) { - this.$message.warning('暂无模板数据,无法生成发票'); + + // 从 Vuex 获取批次详情数据(已从后端获取) + const batchRows = this.$store.getters.batchDetailRows || []; + if (!batchRows || batchRows.length === 0) { + this.$message.warning('暂无批次数据,无法生成发票'); return; } - // 这部分逻辑是筛选公司 使用该公司模板数据 + + // 筛选未开票的记录 const selectedCompanyId = this.supplierId; - const preferInvoiceType = this.invoiceType; // PUBLIC_DICT_TYPE - const isCustomerTypeStr = s => { - if (!s) return false; - try { - const lower = String(s).toLowerCase(); - return lower.includes('客户') || lower.includes('customer'); - } catch (e) { - return false; - } - }; - let filtered = templates; + let filtered = batchRows.filter(row => !row.invoiced); + + // 按公司ID筛选 if (selectedCompanyId) { - // 优先按销方 id 过滤;当偏好为 CUSTOMER 时,优先匹配 sellerType 为客户的模板 - let sellerMatches = templates.filter(tpl => tpl && tpl.sellerId && String(tpl.sellerId) === String(selectedCompanyId)); + const sellerMatches = filtered.filter(row => row.sellerId && String(row.sellerId) === String(selectedCompanyId)); if (sellerMatches.length > 0) { - if (preferInvoiceType === this.PUBLIC_DICT_TYPE.CUSTOMER) { - const customerSellerMatches = sellerMatches.filter(tpl => isCustomerTypeStr(tpl.sellerType)); - if (customerSellerMatches.length > 0) sellerMatches = customerSellerMatches; - } filtered = sellerMatches; } else { - // 尝试按购买方 id 过滤 - const purchaseMatches = templates.filter(tpl => tpl && tpl.purchaseId && String(tpl.purchaseId) === String(selectedCompanyId)); - if (purchaseMatches.length > 0) filtered = purchaseMatches; + const purchaseMatches = filtered.filter(row => row.purchaseId && String(row.purchaseId) === String(selectedCompanyId)); + if (purchaseMatches.length > 0) { + filtered = purchaseMatches; + } } } - // 深拷贝模板,避免修改原始 Vuex 数据 - let templatePool = filtered.map(t => ({ ...t })); - // 生成发票列表 + + if (filtered.length === 0) { + this.$message.warning('该公司暂无未开票的批次记录'); + return; + } + + // 深拷贝批次数据,避免修改原始数据 + const templatePool = filtered.map(t => ({ ...t })); const resultInvoices = []; - // 使用 mathjs BigNumber 做精确计算 const b = v => this.math.bignumber(v || 0); - for (let order of orders) { - // 每个订单的 remainingAmount 是订单的已开票金额(allPayments) - let remaining = b(order.allPayments - order.params.totalInvoiceAmount || 0); + + for (const order of orders) { + let remaining = b(order.allPayments - (order.params?.totalInvoiceAmount || 0)); let orderFullyInvoiced = false; - // 如果订单的剩余开票金额等于0 + if (this.math.equal(remaining, b(0))) { - continue; // 跳过该订单,继续下一个订单 + continue; } + for (let i = 0; i < templatePool.length; i++) { const tpl = templatePool[i]; let tplAmount = b(tpl.total || 0); - // 没有可用模板金额则跳过 + if (this.math.equal(tplAmount, b(0))) continue; - // 计算本次要使用的金额:used = min(tplAmount, remaining) - let used; - if (this.math.largerEq(tplAmount, remaining)) { - used = remaining; - } else { - used = tplAmount; - } - // 根据模板行判断 companyType/companyID/companyName:优先判断销方(sellerId),否则判断购买方(purchaseId) + + const used = this.math.largerEq(tplAmount, remaining) ? remaining : tplAmount; + + // 确定公司信息 let companyTypeConst = this.invoiceType; let companyID = tpl.sellerId || tpl.purchaseId || null; - let companyName = tpl.sellerName || tpl.purchaseName || tpl.invoiceCompanyName; - // 根据模板确定我方公司名称(invoiceObject) - // 当sellerId=0时,sellerName是我方公司;当purchaseId=0时,purchaseName是我方公司 + let companyName = tpl.sellerName || tpl.purchaseName || ''; let invoiceObject = null; + if (tpl.sellerId && Number(tpl.sellerId) !== 0) { companyTypeConst = tpl.sellerType; companyID = tpl.sellerId; companyName = tpl.sellerName || companyName; - // 对方是销方,我方是购买方 invoiceObject = tpl.purchaseName || null; } else if (tpl.purchaseId && Number(tpl.purchaseId) !== 0) { companyTypeConst = tpl.purchaseType; companyID = tpl.purchaseId; companyName = tpl.purchaseName || companyName; - // 对方是购买方,我方是销方 invoiceObject = tpl.sellerName || null; } - // 如果模板中没有明确的我方公司信息,则使用sessionStorage中的值 + // 从 sessionStorage 获取我方公司信息 if (!invoiceObject) { const storedUs = sessionStorage.getItem('us'); if (storedUs) { try { - // 尝试解析为JSON数组(合并情况) const parsedUs = JSON.parse(storedUs); - if (Array.isArray(parsedUs) && parsedUs.length > 0) { - // 如果是数组,使用第一个(或者可以根据模板匹配,这里简化处理) - invoiceObject = parsedUs[0]; - } else { - invoiceObject = storedUs; - } + invoiceObject = Array.isArray(parsedUs) && parsedUs.length > 0 ? parsedUs[0] : storedUs; } catch (e) { - // 不是JSON,直接使用字符串 invoiceObject = storedUs; } } } - // 生成发票对象(本次使用的金额 used) + // 生成发票对象 const invoice = this.createInvoiceObject( { invoiceDate: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'), @@ -421,40 +394,34 @@ export default { companyName: companyName, companyID: companyID, invoiceCompanyName: companyName, - ticketPoint: tpl.ticketPoint || tpl.ticketPointAmount || 0, + ticketPoint: tpl.ticketPoint || 0, ticketPointAmount: Number(this.math.format(this.math.multiply(used, b(tpl.ticketPoint || 0)), { precision: 2, notation: 'fixed' })), isOrderTax: order.id, comments: this.comment }, { - batchInvoiceId: tpl.batchInvoiceId || tpl.id || null, - batchVoucher: tpl.voucher || this.currentVoucher || '' + batchInvoiceId: tpl.id || null, + batchVoucher: tpl.voucher || '' } ); resultInvoices.push(invoice); - // 更新订单剩余和模板剩余 + + // 更新剩余金额 remaining = this.math.subtract(remaining, used); - const tplRemainAfter = this.math.subtract(tplAmount, used); - // 将剩余模板金额写回 pool(转为普通数字),便于后续继续使用 - templatePool[i].total = Number(this.math.format(tplRemainAfter, { precision: 2, notation: 'fixed' })); - // 如果订单已被完全抵扣,则结束当前订单的模板匹配 + templatePool[i].total = Number(this.math.format(this.math.subtract(tplAmount, used), { precision: 2, notation: 'fixed' })); + if (this.math.largerEq(b(0), remaining) || this.math.equal(remaining, b(0))) { orderFullyInvoiced = true; break; } - // 否则继续使用下一个模板行 } - // 如果订单在模板循环后被标记为已开完,则继续下一个订单 if (orderFullyInvoiced) continue; } - // 将生成的发票列表写入 Vuex,触发界面更新 if (resultInvoices.length > 0) { this.$store.dispatch('excel/setSelectedInvoiceList', resultInvoices); this.$message.success(`已自动生成 ${resultInvoices.length} 条发票记录`); - // 保存生成结果到本地,便于恢复 - this.saveGeneratedInvoicesSession(); } }, @@ -554,9 +521,8 @@ export default { sessionStorage.removeItem('invoiceAmount'); sessionStorage.removeItem('us'); sessionStorage.removeItem('companyList_selected_company_id'); - sessionStorage.removeItem('merged_company_info'); // 清理合并信息 + sessionStorage.removeItem('merged_company_info'); this.$store.dispatch('excel/clearInvoiceAmount'); - this.$store.dispatch('excel/clearBatchMetaMap'); // 重置开票列表 this.$store.dispatch('excel/clearSelectedInvoiceList'); // 清除右上角公司信息 diff --git a/packages/order-system/src/views/dashboard/components/common/ReadyList.vue b/packages/order-system/src/views/dashboard/components/common/ReadyList.vue index 1e2121bb5..a832871dd 100644 --- a/packages/order-system/src/views/dashboard/components/common/ReadyList.vue +++ b/packages/order-system/src/views/dashboard/components/common/ReadyList.vue @@ -131,10 +131,23 @@ export default { return Promise.resolve(); }, validateBatchAmounts(invoices = []) { - const metaMap = this.$store?.state?.excel?.batchMetaMap || {}; if (!invoices.length) { return true; } + // 从 batchDetailRows 构建校验信息 + const batchDetailRows = this.$store?.getters?.batchDetailRows || []; + if (!batchDetailRows.length) { + return true; + } + + // 构建每个批次记录ID的预期金额映射 + const expectedAmountMap = {}; + batchDetailRows.forEach(row => { + if (row.id && !row.invoiced) { + expectedAmountMap[row.id] = Number(row.total || 0); + } + }); + const sums = {}; invoices.forEach(item => { const batchId = item?.batchInvoiceId; @@ -144,18 +157,18 @@ export default { } sums[batchId] = math.add(sums[batchId], math.bignumber(item.invoiceAmount || 0)); }); + for (const batchId of Object.keys(sums)) { - const meta = metaMap[batchId]; - if (!meta || meta.totalAmount === undefined || meta.totalAmount === null) { - this.$message.error(`批次 ${batchId} 缺少导入金额,请重新载入批次数据`); - return false; + const expectedAmount = expectedAmountMap[batchId]; + if (expectedAmount === undefined) { + continue; } - const expected = math.bignumber(meta.totalAmount || 0); + const expected = math.bignumber(expectedAmount); const diff = math.subtract(sums[batchId], expected); if (!math.equal(math.round(diff, 2), math.bignumber(0))) { const sumFormatted = Number(math.format(sums[batchId], { precision: 12, notation: 'fixed' })).toFixed(2); const expectedFormatted = Number(math.format(expected, { precision: 12, notation: 'fixed' })).toFixed(2); - this.$message.error(`批次 ${batchId} 的开票金额 ${sumFormatted} 与导入金额 ${expectedFormatted} 不一致`); + this.$message.error(`批次记录 ${batchId} 的开票金额 ${sumFormatted} 与导入金额 ${expectedFormatted} 不一致`); return false; } } From 03a4817c5bfda7c36475f2863a56e2d5ee32667e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 7 Dec 2025 06:20:55 +0000 Subject: [PATCH 2/6] Remove unused batch invoice update API calls Co-authored-by: ming10655 --- .../src/api/system/batchInvoice.js | 52 ------------------- .../components/common/QueueInvoiceList.vue | 24 +-------- 2 files changed, 2 insertions(+), 74 deletions(-) diff --git a/packages/order-system/src/api/system/batchInvoice.js b/packages/order-system/src/api/system/batchInvoice.js index d474ee4f5..d8385c44b 100644 --- a/packages/order-system/src/api/system/batchInvoice.js +++ b/packages/order-system/src/api/system/batchInvoice.js @@ -17,32 +17,6 @@ export function listBatchInvoiceIn(query) { }); } -// 获取进项批量开票的公司聚合信息 -export function getBatchInvoiceInCompanySummary(voucher) { - return request({ - url: `${BASE_IN}/companySummary/${encode(voucher)}`, - method: 'get' - }); -} - -// 更新进项批量开票记录的开票状态 -export function updateBatchInvoiceInInvoiced(id, invoiced, invoiceId = null) { - return request({ - url: `${BASE_IN}/updateInvoiced`, - method: 'put', - data: { id, invoiced, invoiceId } - }); -} - -// 批量更新进项开票记录的开票状态 -export function batchUpdateBatchInvoiceInInvoiced(ids, invoiced, invoiceIds = []) { - return request({ - url: `${BASE_IN}/batchUpdateInvoiced`, - method: 'put', - data: { ids, invoiced, invoiceIds } - }); -} - export function importBatchInvoiceInData(data) { return request({ url: `${BASE_IN}/importData`, @@ -93,32 +67,6 @@ export function listBatchInvoiceOut(query) { }); } -// 获取销项批量开票的公司聚合信息 -export function getBatchInvoiceOutCompanySummary(voucher) { - return request({ - url: `${BASE_OUT}/companySummary/${encode(voucher)}`, - method: 'get' - }); -} - -// 更新销项批量开票记录的开票状态 -export function updateBatchInvoiceOutInvoiced(id, invoiced, invoiceId = null) { - return request({ - url: `${BASE_OUT}/updateInvoiced`, - method: 'put', - data: { id, invoiced, invoiceId } - }); -} - -// 批量更新销项开票记录的开票状态 -export function batchUpdateBatchInvoiceOutInvoiced(ids, invoiced, invoiceIds = []) { - return request({ - url: `${BASE_OUT}/batchUpdateInvoiced`, - method: 'put', - data: { ids, invoiced, invoiceIds } - }); -} - export function importBatchInvoiceOutData(data) { return request({ url: `${BASE_OUT}/importData`, diff --git a/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue b/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue index 15e9bd675..03617505a 100644 --- a/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue +++ b/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue @@ -8,10 +8,6 @@ import { getUuid } from '@/utils/trash/utils'; import { TableName } from '@/api/tool/enums'; import { common_dialog } from '@/views/dashboard/mixins/common/common_dialog'; import { batchInvoice } from '@/api/system/excel'; -import { - batchUpdateBatchInvoiceInInvoiced, - batchUpdateBatchInvoiceOutInvoiced -} from '@/api/system/batchInvoice'; import INVOICE_OUT from '@/components/NeedToShow/INVOICE_OUT.vue'; import INVOICE_IN from '@/components/NeedToShow/INVOICE_IN.vue'; @@ -65,10 +61,6 @@ export default { } }, methods: { - // 获取更新开票状态的API - getUpdateInvoicedApi() { - return this.mode === 'out' ? batchUpdateBatchInvoiceOutInvoiced : batchUpdateBatchInvoiceInInvoiced; - }, // 创建发票对象的工具函数 createInvoiceObject(params, extra = {}) { const { invoiceDate, invoiceObject, invoiceAmount, companyType, companyName, companyID, invoiceCompanyName, ticketPoint = 0, ticketPointAmount, isOrderTax, comments } = params; @@ -145,18 +137,6 @@ export default { // 如果成功 if (result.flag) { - // 收集需要更新的批次记录ID - const batchIds = filteredInvoices.map(inv => inv.batchInvoiceId).filter(id => id != null); - // 调用后端API更新开票状态 - if (batchIds.length > 0) { - try { - const updateApi = this.getUpdateInvoicedApi(); - await updateApi(batchIds, true); - } catch (e) { - console.error('更新后端开票状态失败:', e); - } - } - // 告诉订单列表重新加载 this.$bus.$emit('select-goods:update'); @@ -166,7 +146,7 @@ export default { this.$store.dispatch('excel/clearInvoiceAmount'); } - // 清理 sessionStorage 中的临时开票数据 + // 清理 sessionStorage 中的临时数据 sessionStorage.removeItem('invoiceAmount'); sessionStorage.removeItem('us'); sessionStorage.removeItem('companyList_selected_company_id'); @@ -176,7 +156,7 @@ export default { this.$bus.$emit('invoice-clear'); this.$message.success('本批开票成功'); - // 通知父组件刷新数据 + // 通知父组件刷新数据(从后端重新获取列表,invoiced 状态会自动更新) this.$bus.$emit('batch-invoice:refresh'); } else { this.$message.error('本批开票有误 请检查错误信息后重新提交'); From efb797adef09316638425a346ee87d3734d396dc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 7 Dec 2025 06:26:43 +0000 Subject: [PATCH 3/6] Fix: Improve layout and scrolling in QueueInvoiceList Co-authored-by: ming10655 --- .../components/common/QueueInvoiceList.vue | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue b/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue index 03617505a..d970cea28 100644 --- a/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue +++ b/packages/order-system/src/views/dashboard/components/common/QueueInvoiceList.vue @@ -561,12 +561,15 @@ export default {