feat(invoice): 添加红冲功能和相关审批流程

- 修改ApplyInvoice组件支持红冲操作,添加isRedRush属性控制红冲逻辑
- 在输入验证中修复数字验证正则表达式,支持负数输入
- 添加applyRefund API接口用于提交红冲申请
- 实现红冲审批流程,包括待审批和已审批页面
- 更新发票详情显示组件支持负数金额显示
- 在发票列表页面添加红冲按钮和相关状态判断
- 修改后端applyRefund接口接收完整对象参数
- 实现发票明细项导出功能
- 添加红冲状态和发票类型的字典数据支持
- 优化金额转中文大写函数支持负数显示
- 添加发票详情导出PDF功能
dev_1.0.1
chenhao 2025-12-30 15:20:25 +08:00
parent f0294212b2
commit 695c77ca60
19 changed files with 1062 additions and 58 deletions

View File

@ -51,6 +51,15 @@ export function redRush(id) {
})
}
// 申请红冲 (提交表单)
export function applyRefund(data) {
return request({
url: '/finance/invoice/applyRefund',
method: 'post',
data: data
})
}
// 退回销售收票单
export function returnInvoice(id) {
return request({

View File

@ -124,6 +124,11 @@ export const constantRoutes = [
component: () => import('@/views/approve/finance/receivableInvoice/approved/index.vue'),
hidden: true
},
{
path: 'receivableInvoiceRefundLog',
component: () => import('@/views/approve/finance/receivableInvoiceRefund/approved/index.vue'),
hidden: true
},
]
},
{

View File

@ -33,28 +33,43 @@
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="excelList && excelList.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="excelList" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传信息" align="center"></el-table-column>
<el-table-column prop="createTime" label="生成时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
<!-- <div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">-->
<!-- <div class="el-descriptions__title">附件信息</div>-->
<!-- <el-table :data="attachments" border style="width: 100%; margin-top: 10px;">-->
<!-- <el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>-->
<!-- <el-table-column prop="createUserName" label="上传人" align="center"></el-table-column>-->
<!-- <el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>-->
<!-- <el-table-column label="操作" align="center">-->
<!-- <template slot-scope="scope">-->
<!-- <el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>-->
<!-- <el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </div>-->
<!-- <el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">-->
<!-- <iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>-->
<!-- </el-dialog>-->
<!-- <el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">-->
<!-- <img :src="currentImageUrl" style="width: 100%;" />-->
<!-- </el-dialog>-->
</div>
</template>
@ -77,6 +92,7 @@ export default {
data() {
return {
attachments: [],
excelList: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
@ -84,7 +100,20 @@ export default {
};
},
watch: {
data: {
handler(val) {
if (val && val.updateTime) {
this.excelList = [{
fileName: '电子发票--购买方公司信息.xlsx',
createUserName: '系统生成',
createTime: val.updateTime,
isSystemGenerated: true
}];
}
},
immediate: true,
deep: true
}
},
methods: {
@ -112,6 +141,15 @@ export default {
}
},
handleDownload(row) {
if (row.isSystemGenerated) {
request({
url: '/finance/invoice/export/' + this.data.invoiceBillCode,
method: 'get'
}).then(res => {
window.location.href = process.env.VUE_APP_BASE_API + "/common/download?fileName=" + encodeURIComponent(res.msg) + "&delete=true";
});
return;
}
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';

View File

@ -0,0 +1,215 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="开票编号" prop="invoiceBillCode">
<el-input v-model="queryParams.invoiceBillCode" placeholder="请输入开票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="客户" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入客户" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="开票编号" align="center" prop="invoiceBillCode" />
<el-table-column label="客户" align="center" prop="partnerName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax < 0 ? 'color: red' : ''">{{ scope.row.totalPriceWithTax }}</span>
</template>
</el-table-column>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime) }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="开票单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="开票单详情">
<receivable-invoice-detail :data="form"></receivable-invoice-detail>
<template #footer>
<span>开票编号: {{ form.invoiceBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag size="small">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceApproved, getInvoiceDetail } from "@/api/finance/invoice";
import { listCompletedFlows } from "@/api/flow";
import ReceivableInvoiceDetail from "../components/ReceivableInvoiceDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceivableInvoiceRefundApproved",
components: { ReceivableInvoiceDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_invoice_refound',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentInvoiceId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceApproved(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentInvoiceId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceDetail(this.currentInvoiceId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.invoiceBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `开票单-${this.form.invoiceBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -0,0 +1,182 @@
<template>
<div class="invoice-detail">
<el-descriptions title="开票单信息" :column="3" border>
<el-descriptions-item label="销售-开票单编号">{{ data.invoiceBillCode }}</el-descriptions-item>
<el-descriptions-item :span="2" label="进货商">{{ data.partnerName }}</el-descriptions-item>
<el-descriptions-item label="含税总价(元)">
<span :style="data.totalPriceWithTax < 0 ? 'color: red' : ''">{{ data.totalPriceWithTax }}</span>
</el-descriptions-item>
<el-descriptions-item label="未税总价(元)">
<span :style="data.totalPriceWithoutTax < 0 ? 'color: red' : ''">{{ data.totalPriceWithoutTax }}</span>
</el-descriptions-item>
<el-descriptions-item label="税额(元)">
<span :style="data.taxAmount < 0 ? 'color: red' : ''">{{ data.taxAmount }}</span>
</el-descriptions-item>
<!-- <el-descriptions-item label="银行账号">{{ data.buyerBankAccount }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="账户名称">{{ data.buyerName }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="银行开户行">{{ data.buyerBank }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item> -->
</el-descriptions>
<div class="section" style="margin-top: 20px;">
<div class="el-descriptions__title">发票信息</div>
<invoice-info-view :data="data" />
</div>
<div class="section" style="margin-top: 20px;" v-show="data.detailDTOList && data.detailDTOList.length>0">
<div class="el-descriptions__title">应收单列表</div>
<el-table :data="data.detailDTOList" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="receivableBillCode" label="销售-应收单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<el-table-column prop="productType" label="产品类型" align="center">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax < 0 ? 'color: red' : ''">{{ scope.row.totalPriceWithTax }}</span>
</template>
</el-table-column>
<el-table-column prop="receiptAmount" label="本次开票金额" align="center">
<template slot-scope="scope">
<span :style="scope.row.receiptAmount < 0 ? 'color: red' : ''">{{ scope.row.receiptAmount }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="excelList && excelList.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="excelList" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传信息" align="center"></el-table-column>
<el-table-column prop="createTime" label="生成时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="section" style="margin-top: 20px;" v-if="attachments && attachments.length > 0">
<div class="el-descriptions__title">附件信息</div>
<el-table :data="attachments" border style="width: 100%; margin-top: 10px;">
<el-table-column prop="fileName" label="附件名称" align="center"></el-table-column>
<el-table-column prop="createUserName" label="上传人" align="center"></el-table-column>
<el-table-column prop="createTime" label="上传时间" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handlePreview(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :visible.sync="pdfPreviewVisible" width="80%" append-to-body top="5vh" title="PDF预览">
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<el-dialog :visible.sync="imagePreviewVisible" width="60%" append-to-body top="5vh" title="图片预览">
<img :src="currentImageUrl" style="width: 100%;" />
</el-dialog>
</div>
</template>
<script>
import { getInvoiceAttachments } from "@/api/finance/invoice";
import request from '@/utils/request';
import InvoiceInfoView from '@/views/finance/invoice/components/InvoiceInfoView';
export default {
name: "ReceivableInvoiceDetail",
components: { InvoiceInfoView },
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['product_type'],
data() {
return {
attachments: [],
excelList: [],
pdfPreviewVisible: false,
currentPdfUrl: '',
imagePreviewVisible: false,
currentImageUrl: ''
};
},
watch: {
data: {
handler(val) {
if (val && val.updateTime) {
this.excelList = [{
fileName: '电子发票--购买方公司信息.xlsx',
createUserName: '系统生成',
createTime: val.updateTime,
isSystemGenerated: true
}];
}
},
immediate: true,
deep: true
}
},
methods: {
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
handlePreview(row) {
if (this.isPdf(row.filePath)) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: row.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
this.currentPdfUrl = URL.createObjectURL(blob);
this.pdfPreviewVisible = true;
});
} else {
this.currentImageUrl = this.getImageUrl(row.filePath);
this.imagePreviewVisible = true;
}
},
handleDownload(row) {
if (row.isSystemGenerated) {
request({
url: '/finance/invoice/export/' + this.data.invoiceBillCode,
method: 'get'
}).then(res => {
window.location.href = process.env.VUE_APP_BASE_API + "/common/download?fileName=" + encodeURIComponent(res.msg) + "&delete=true";
});
return;
}
const link = document.createElement('a');
link.href = this.getImageUrl(row.filePath);
link.download = row.fileName || 'attachment';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
</script>
<style scoped>
.invoice-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="开票编号" prop="invoiceBillCode">
<el-input v-model="queryParams.invoiceBillCode" placeholder="请输入开票编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="进货商" prop="partnerName">
<el-input v-model="queryParams.partnerName" placeholder="请输入进货商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="提交日期">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="invoiceList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="开票编号" align="center" prop="invoiceBillCode" />
<el-table-column label="进货商" align="center" prop="partnerName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax">
<template slot-scope="scope">
<span :style="scope.row.totalPriceWithTax < 0 ? 'color: red' : ''">{{ scope.row.totalPriceWithTax }}</span>
</template>
</el-table-column>
<el-table-column label="提交日期" align="center" prop="applyTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.applyTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="开票单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<div style="display: flex;flex-direction: row-reverse; margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="el-icon-download"
@click="exportPDF"
:loading="pdfExporting"
>导出PDF</el-button>
</div>
<div class="approve-container" :class="{ 'exporting-pdf': pdfExporting }">
<ApproveLayout ref="approveLayout" title="开票单详情">
<receivable-invoice-detail :data="form"></receivable-invoice-detail>
<template #footer>
<span>{{ form.invoiceBillCode }}</span>
</template>
</ApproveLayout>
</div>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag size="small">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listInvoiceApprove, getInvoiceDetail } from "@/api/finance/invoice";
import { approveTask, listCompletedFlows } from "@/api/flow";
import ReceivableInvoiceDetail from "./components/ReceivableInvoiceDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
import { exportElementToPDF } from "@/views/approve/finance/pdfUtils";
export default {
name: "ReceivableInvoiceRefundApprove",
components: { ReceivableInvoiceDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
invoiceList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
invoiceBillCode: null,
partnerName: null,
projectName: null,
processKey: 'finance_invoice_refound',
},
dateRange: [],
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_invoice_refound',
taskId: null,
currentInvoiceId: null,
pdfExporting: false
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listInvoiceApprove(this.addDateRange(this.queryParams, this.dateRange, 'ApplyTime')).then(response => {
this.invoiceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push('/approve/receivableInvoiceRefundLog')
},
handleApprove(row) {
this.resetDetailForm();
this.currentInvoiceId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getInvoiceDetail(this.currentInvoiceId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.invoiceBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
let keys = [];
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.invoiceBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
},
async exportPDF() {
this.pdfExporting = true;
try {
const element = this.$refs.approveLayout.$el;
const fileName = `开票单-${this.form.invoiceBillCode || ''}.pdf`;
await exportElementToPDF(element, fileName);
this.$modal.msgSuccess('PDF导出成功');
} catch (error) {
console.error('PDF导出失败:', error);
this.$modal.msgError('PDF导出失败请稍后重试');
} finally {
this.pdfExporting = false;
}
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
/* 导出PDF时的特殊样式 */
.approve-container.exporting-pdf ::v-deep .el-button--primary,
.approve-container.exporting-pdf ::v-deep .el-button--text {
display: none;
}
.approve-container.exporting-pdf ::v-deep .el-input__inner,
.approve-container.exporting-pdf ::v-deep .el-textarea__inner {
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
resize: none !important;
padding: 0 !important;
}
.approve-container.exporting-pdf ::v-deep .el-input__suffix {
display: none;
}
</style>

View File

@ -110,7 +110,7 @@
<el-table-column label="数量" prop="quantity" align="center" width="80">
<template slot-scope="scope">
<el-input v-model="scope.row.quantity" size="mini" class="no-border-input"
oninput="value=value.replace(/[^\d.]/g,'')"
oninput="value=value.replace(/[^\d.-]/g,'')"
@input="calculateAmount(scope.row)"/>
</template>
</el-table-column>
@ -185,7 +185,7 @@
</template>
<script>
import {getInvoiceProducts, applyInvoice} from "@/api/finance/invoice";
import {getInvoiceProducts, applyInvoice, applyRefund} from "@/api/finance/invoice";
import { listCompanyInfo } from "@/api/system/companyInfo";
export default {
@ -199,6 +199,10 @@ export default {
rowData: {
type: Object,
default: () => ({})
},
isRedRush: {
type: Boolean,
default: false
}
},
data() {
@ -217,6 +221,7 @@ export default {
sellerBankAccount: undefined,
remark: undefined,
invoiceBillCode: this.rowData.invoiceBillCode,
id: this.rowData.id,
detailItemList: [
{
projectName: '',
@ -246,8 +251,8 @@ export default {
let total = 0;
this.form.detailItemList.forEach(item => {
const amount = parseFloat(item.amount) || 0;
const tax = parseFloat(item.taxAmount) || 0;
total += (amount + tax);
// const tax = parseFloat(item.taxAmount) || 0;
total += amount ;
});
return total.toFixed(2);
},
@ -309,6 +314,7 @@ export default {
}
this.form.invoiceBillCode = this.rowData.invoiceBillCode;
this.form.id = this.rowData.id;
this.updateRemark();
@ -316,6 +322,16 @@ export default {
getInvoiceProducts(this.rowData.invoiceBillCode).then(response => {
if (response.data && response.data.length > 0) {
this.form.detailItemList = response.data.map(item => {
let quantity = item.quantity;
let amount = item.allPrice;
let taxAmount = item.taxAmount;
if (this.isRedRush) {
quantity = -Math.abs(quantity);
amount = -Math.abs(amount);
taxAmount = -Math.abs(taxAmount);
}
const row = {
projectName: item.projectName,
id: item.id,
@ -324,11 +340,11 @@ export default {
productModel: item.productModel, // Mapping projectCode to productModel as requested
productName: item.productName, // Mapping projectCode to productModel as requested
unit: item.unit || '',
quantity: item.quantity,
quantity: quantity,
unitPrice: item.price, // Mapping price to unitPrice
taxRate: item.taxRate,
amount: item.allPrice,
taxAmount: item.taxAmount
amount: amount,
taxAmount: taxAmount
};
// Calculate initial amounts
// this.calculateAmount(row);
@ -430,8 +446,7 @@ export default {
}
for (let i = 0; i < this.form.detailItemList.length; i++) {
const item = this.form.detailItemList[i];
if (!item.productName || !item.productModel || (item.quantity??false) || !item.unit || !item.unitPrice || !item.amount || !item.taxAmount || !item.taxRate) {
if (!item.productName || !item.productModel || isNaN(item.quantity) || !item.unit || !item.unitPrice || !item.amount || !item.taxAmount || !item.taxRate) {
this.$modal.msgError(`表格第 ${i + 1} 行数据不完整,请填写所有必填字段`);
return;
}
@ -440,8 +455,9 @@ export default {
item.allPrice=item.amount;
}
this.loading = true;
applyInvoice(this.form).then(response => {
this.$modal.msgSuccess("申请提交成功");
const apiCall = this.isRedRush ? applyRefund : applyInvoice;
apiCall(this.form).then(response => {
this.$modal.msgSuccess(this.isRedRush ? "申请红冲成功" : "申请提交成功");
this.loading = false;
this.$emit("update:visible", false);
this.$emit("submit", this.form);
@ -464,11 +480,16 @@ export default {
let decimalNum;
let chineseStr = '';
let parts;
let prefix = '';
if (money === '') {
return '';
}
money = parseFloat(money);
if (money < 0) {
prefix = '(负数)';
money = Math.abs(money);
}
if (money >= 999999999999) {
return '';
}
@ -526,7 +547,7 @@ export default {
} else if (decimalNum === '') {
chineseStr += cnInteger;
}
return chineseStr;
return prefix + chineseStr;
}
}
};

View File

@ -161,6 +161,12 @@ export default {
if (money >= 999999999999) return '';
if (money === 0) return cnNums[0] + cnIntLast + cnInteger;
let prefix = "";
if (money < 0) {
prefix = "(负数)";
money = Math.abs(money);
}
money = money.toString();
if (money.indexOf('.') === -1) {
integerNum = money;
@ -201,7 +207,7 @@ export default {
if (chineseStr === '') chineseStr += cnNums[0] + cnIntLast + cnInteger;
else if (decimalNum === '') chineseStr += cnInteger;
return chineseStr;
return prefix + chineseStr;
},
}
};

View File

@ -41,9 +41,9 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="票据类型" prop="invoiceBillType">
<el-select v-model="queryParams.invoiceBillType" placeholder="请选择收票单类型" clearable>
<el-option v-for="dict in dict.type.invoice_bill_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
<el-form-item label="票据类型" prop="invoiceType">
<el-select v-model="queryParams.invoiceType" placeholder="票据类型" clearable>
<el-option v-for="dict in dict.type.finance_invoice_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approveStatus">
@ -198,7 +198,7 @@
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.approveStatus === '2' && scope.row.invoiceStatus === '1'"
v-show="scope.row.approveStatus === '2' && scope.row.invoiceStatus === '2'"
@click="handleRedRush(scope.row)"
>申请红冲</el-button>
<el-button
@ -227,7 +227,7 @@
<!-- 收票附件弹窗 -->
<invoice-dialog :visible.sync="receiptOpen" :invoice-data="currentRow" :dicts="dict.type"></invoice-dialog>
<!-- 申请开票弹窗 -->
<apply-invoice :visible.sync="applyOpen" :row-data="currentRow" @submit="getList"></apply-invoice>
<apply-invoice :visible.sync="applyOpen" :row-data="currentRow" :is-red-rush="isRedRush" @submit="getList"></apply-invoice>
</div>
</template>
@ -248,7 +248,7 @@ export default {
InvoiceDialog,
ApplyInvoice
},
dicts:['invoice_bill_type','approve_status','invoice_bill_status'],
dicts:['invoice_bill_type','approve_status','invoice_bill_status','finance_invoice_type'],
data() {
return {
//
@ -266,6 +266,7 @@ export default {
projectCode: null,
projectName: null,
invoiceBillCode: null,
invoiceType: null,
partnerName: null,
receivableBillCode: null,
invoiceBillType: null,
@ -284,6 +285,8 @@ export default {
addOpen: false,
//
applyOpen: false,
//
isRedRush: false,
//
receiptOpen: false,
currentRow: {}
@ -350,17 +353,15 @@ export default {
},
/** 申请开票按钮操作 */
handleApplyInvoice(row) {
this.isRedRush = false;
this.currentRow = row;
this.applyOpen = true;
},
/** 红冲按钮操作 */
handleRedRush(row) {
this.$modal.confirm('是否确认收票单编号为"' + row.invoiceBillCode + '"的数据项进行红冲,并提交财务审批?').then(function() {
return redRush(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("申请红冲成功");
}).catch(() => {});
this.isRedRush = true;
this.currentRow = row;
this.applyOpen = true;
},
handleReturn(row) {
this.$modal.confirm('是否确认退回收票单编号为"' + row.invoiceBillCode + '"的数据项?').then(function() {

View File

@ -182,11 +182,11 @@ public class OmsInvoiceBillController extends BaseController
/**
*
*/
@GetMapping("/applyRefund/{id}")
@PostMapping("/applyRefund")
@ResponseBody
public AjaxResult applyRefund(@PathVariable("id") Long id) {
public AjaxResult applyRefund(@RequestBody OmsInvoiceBill omsInvoiceBill) {
try {
return omsInvoiceBillService.applyRefund(id);
return omsInvoiceBillService.applyRefund(omsInvoiceBill);
} catch (Exception e) {
logger.error("申请红冲失败", e);
return AjaxResult.error("操作失败:" + e.getMessage());
@ -242,4 +242,9 @@ public class OmsInvoiceBillController extends BaseController
public AjaxResult listProduct(@PathVariable("code") String code) {
return AjaxResult.success(omsInvoiceBillService.listProduct(code));
}
@GetMapping("/export/{code}")
@ResponseBody
public AjaxResult exportItem(@PathVariable("code") String code) {
return omsInvoiceBillService.exportItem(code);
}
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.sip.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.Date;
/**
* oms_inventory_delivery
*
* @author ruoyi
* @date 2025-08-12
*/
@Data
@ToString
public class InvoiceDetailItemExcelDto extends BaseEntity
{
private static final long serialVersionUID = 1L;
private String index;
private String buyerName;
private String buyerCreditCode;
private String buyerBank;
private String buyerBankAccount;
/**
*
*/
private String productName;
/**
*
*/
private String productModel;
private String unit;
private Long quantity;
/**
*
*/
private BigDecimal price;
/**
*
*/
private BigDecimal allPrice;
/**
*
*/
private BigDecimal taxAmount;
/**
*
*/
private BigDecimal taxRate;
private String invoiceType;
private String remark;
private String buyerAddress;
}

View File

@ -1,6 +1,7 @@
package com.ruoyi.sip.mapper;
import com.ruoyi.sip.domain.OmsReceivableInvoiceDetailItem;
import com.ruoyi.sip.dto.InvoiceDetailItemExcelDto;
import java.util.List;
@ -49,4 +50,6 @@ public interface OmsReceivableInvoiceDetailItemMapper {
int insertBatch(List<OmsReceivableInvoiceDetailItem> omsReceivableInvoiceDetailItems);
void updateBatch(List<OmsReceivableInvoiceDetailItem> updateList);
List<InvoiceDetailItemExcelDto> exportItem(String code);
}

View File

@ -104,7 +104,7 @@ public interface IOmsInvoiceBillService
* @param id ID
* @return
*/
public AjaxResult applyRefund(Long id);
public AjaxResult applyRefund(OmsInvoiceBill omsInvoiceBill);
/**
* 退
@ -122,4 +122,5 @@ public interface IOmsInvoiceBillService
void returnTicketWriteOff(List<String> collect, List<OmsReceivableInvoiceDetail> omsReceivableInvoiceDetails);
AjaxResult exportItem(String code);
}

View File

@ -1,6 +1,7 @@
package com.ruoyi.sip.service;
import com.ruoyi.sip.domain.OmsReceivableInvoiceDetailItem;
import com.ruoyi.sip.dto.InvoiceDetailItemExcelDto;
import java.util.List;
@ -43,6 +44,7 @@ public interface IOmsReceivableInvoiceDetailItemService {
*/
int batchRemove(Long[] ids);
List<InvoiceDetailItemExcelDto> exportItem(String code);
}

View File

@ -3,26 +3,35 @@ package com.ruoyi.sip.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.config.flow.ProcessConfig;
import com.ruoyi.common.enums.ApproveStatusEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.domain.dto.InvoiceProductDto;
import com.ruoyi.sip.domain.dto.ReceiptDetailDTO;
import com.ruoyi.sip.dto.InvoiceDetailItemExcelDto;
import com.ruoyi.sip.dto.WriteOffInvoiceRequestDto;
import com.ruoyi.sip.dto.inventory.InventoryDeliveryDetailExcelDto;
import com.ruoyi.sip.flowable.domain.Todo;
import com.ruoyi.sip.service.*;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -46,6 +55,8 @@ import com.ruoyi.sip.flowable.service.TodoService;
* @date 2025-12-22
*/
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class OmsInvoiceBillServiceImpl implements IOmsInvoiceBillService, TodoCommonTemplate
{
@Autowired
@ -228,7 +239,8 @@ public class OmsInvoiceBillServiceImpl implements IOmsInvoiceBillService, TodoCo
}
existBill.setActualInvoiceTime(DateUtils.getNowDate());
existBill.setInvoiceStatus(OmsInvoiceBill.InvoiceStatusEnum.INVOICE.getCode());
existBill.setInvoiceStatus(existBill.getInvoiceBillType().equals(OmsInvoiceBill.InvoiceBillTypeEnum.FROM_RECEIVABLE.getCode())?
OmsInvoiceBill.InvoiceStatusEnum.INVOICE.getCode():OmsInvoiceBill.InvoiceStatusEnum.RED_RUSH.getCode());
existBill.setInvoicePriceWithTax(bill.getInvoicePriceWithTax());
existBill.setInvoicePriceWithoutTax(bill.getInvoicePriceWithoutTax());
existBill.setInvoiceType(bill.getInvoiceType());
@ -299,8 +311,8 @@ public class OmsInvoiceBillServiceImpl implements IOmsInvoiceBillService, TodoCo
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AjaxResult applyRefund(Long id) {
OmsInvoiceBill originalBill = selectOmsInvoiceBillById(id);
public AjaxResult applyRefund(OmsInvoiceBill omsInvoiceBill) {
OmsInvoiceBill originalBill = selectOmsInvoiceBillById(omsInvoiceBill.getId());
if (originalBill == null) {
return AjaxResult.error("开票单不存在");
}
@ -328,9 +340,9 @@ public class OmsInvoiceBillServiceImpl implements IOmsInvoiceBillService, TodoCo
refundBill.setInvoicePriceWithTax(originalBill.getInvoicePriceWithTax().negate());
refundBill.setInvoicePriceWithoutTax(originalBill.getInvoicePriceWithoutTax().negate());
refundBill.setTaxRate(originalBill.getTaxRate());
refundBill.setApproveStatus(ApproveStatusEnum.WAIT_COMMIT.getCode());
refundBill.setApproveStatus(ApproveStatusEnum.WAIT_APPROVE.getCode());
refundBill.setInvoiceBillType(OmsInvoiceBill.InvoiceBillTypeEnum.RED_RUSH.getCode());
refundBill.setInvoiceStatus(OmsInvoiceBill.InvoiceStatusEnum.INVOICE.getCode());
refundBill.setInvoiceStatus(OmsInvoiceBill.InvoiceStatusEnum.WAIT_RED_RUSH.getCode());
refundBill.setRefundStatus(OmsInvoiceBill.RefundStatusEnum.REFUNDED.getCode());
refundBill.setBuyerName(originalBill.getBuyerName());
@ -352,11 +364,22 @@ public class OmsInvoiceBillServiceImpl implements IOmsInvoiceBillService, TodoCo
// 更新原单据的红冲状态
originalBill.setRefundStatus(OmsInvoiceBill.RefundStatusEnum.REFUNDED.getCode());
originalBill.setUpdateTime(DateUtils.getNowDate());
updateOmsInvoiceBill(originalBill);
//创建开票明细
detailService.applyRefund(originalBill.getInvoiceBillCode(), refundBill.getInvoiceBillCode());
for (OmsReceivableInvoiceDetailItem omsReceivableInvoiceDetailItem : omsInvoiceBill.getDetailItemList()) {
omsReceivableInvoiceDetailItem.setId(null);
omsReceivableInvoiceDetailItem.setInvoiceBillCode(refundBill.getInvoiceBillCode());
}
detailItemService.saveBatch(omsInvoiceBill.getDetailItemList());
todoService.startProcessDeleteBefore(refundBill.getInvoiceBillCode(), refundBill.getInvoiceBillCode()
, new HashMap<String, Object>() {{
put("applyUserName", ShiroUtils.getSysUser().getUserName());
put("applyUser", ShiroUtils.getUserId());
}}
, processConfig.getDefinition().getFinanceInvoiceRefound());
return AjaxResult.success("红冲申请已提交");
}
@ -586,6 +609,96 @@ public class OmsInvoiceBillServiceImpl implements IOmsInvoiceBillService, TodoCo
}
}
@Override
public AjaxResult exportItem(String code) {
try {
// 获取项目信息列表
List<InvoiceDetailItemExcelDto> list = detailItemService.exportItem(code);
// 构建 Excel 表头和数据
List<List<String>> header = buildExcelHeader();
List<List<String>> data = buildExcelData(list);
// 导出 Excel 文件
return AjaxResult.success(writeExcelToFile(header, data));
} catch (Exception e) {
log.error("导出项目信息失败", e);
throw new ServiceException("导出项目信息失败,请稍后重试");
}
}
private List<List<String>> buildExcelData(List<InvoiceDetailItemExcelDto> list) {
List<List<String>> dataList = new ArrayList<>();
AtomicInteger integer=new AtomicInteger(1);
BigDecimal reduce = list.stream().map(InvoiceDetailItemExcelDto::getAllPrice).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
for (InvoiceDetailItemExcelDto item : list) {
List<String> row = new ArrayList<>();
row.add(String.valueOf(integer.getAndIncrement()));
row.add(item.getBuyerName());
row.add(item.getBuyerCreditCode());
row.add(item.getBuyerAddress());
row.add("开户行:"+item.getBuyerBank()+"\n"+"账号:"+item.getBuyerBankAccount());
row.add("");
row.add(item.getProductName());
row.add(item.getProductModel());
row.add(item.getUnit());
row.add(String.valueOf(item.getQuantity()));
row.add(String.valueOf(item.getPrice()));
row.add(String.valueOf(item.getAllPrice()));
row.add(String.valueOf(item.getTaxRate()));
row.add(String.valueOf(reduce));
row.add(DictUtils.getDictLabel("finance_invoice_type",item.getInvoiceType()));
row.add(item.getRemark());
dataList.add(row);
}
return dataList;
}
private List<List<String>> buildExcelHeader() {
List<List<String>> headerList = new ArrayList<>();
headerList.add(Collections.singletonList("序号"));
headerList.add(Collections.singletonList("购方名称"));
headerList.add(Collections.singletonList("购方税号"));
headerList.add(Collections.singletonList("购方地址电话"));
headerList.add(Collections.singletonList("购方银行帐号"));
headerList.add(Collections.singletonList("税收编码"));
headerList.add(Collections.singletonList("产品名称"));
headerList.add(Collections.singletonList("规格"));
headerList.add(Collections.singletonList("计量单位"));
headerList.add(Collections.singletonList("数量"));
headerList.add(Collections.singletonList("单价(含税)"));
headerList.add(Collections.singletonList("金额(含税)"));
headerList.add(Collections.singletonList("税率"));
headerList.add(Collections.singletonList("合计金额(含税)"));
headerList.add(Collections.singletonList("票据类型"));
headerList.add(Collections.singletonList("备注"));
return headerList;
}
private String writeExcelToFile(List<List<String>> header, List<List<String>> data) {
ExcelUtil<ProjectInfo> util = new ExcelUtil<>(ProjectInfo.class);
String fileName = util.encodingFilename("电子发票-购买方公司信息");
String filePath = util.getAbsoluteFile(fileName);
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setWrapped(true);
// 创建工作簿并写入数据
EasyExcel.write(filePath).head(header)
.sheet("发票信息")
// 注册自定义列宽策略
.registerWriteHandler(new ProjectInfoServiceImpl.CustomColumnWidthStyleStrategy())
// 注册自动换行
.registerWriteHandler(new HorizontalCellStyleStrategy(null, contentWriteCellStyle))
.doWrite(data);
return fileName;
}
@Override
public Object todoDetail(String businessKey, String processKey, String todoId) {
return null;

View File

@ -1,12 +1,18 @@
package com.ruoyi.sip.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.OmsReceivableInvoiceDetail;
import com.ruoyi.sip.domain.OmsReceivableInvoiceDetailItem;
import com.ruoyi.sip.dto.InvoiceDetailItemExcelDto;
import com.ruoyi.sip.mapper.OmsReceivableInvoiceDetailItemMapper;
import com.ruoyi.sip.service.IOmsReceivableInvoiceDetailItemService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -78,6 +84,11 @@ public class OmsReceivableInvoiceDetailItemServiceImpl implements IOmsReceivable
return omsReceivableInvoiceDetailItemMapper.batchRemove(ids);
}
@Override
public List<InvoiceDetailItemExcelDto> exportItem(String code) {
return omsReceivableInvoiceDetailItemMapper.exportItem(code);
}
}

View File

@ -678,7 +678,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
}
// 自定义列宽策略类
private static class CustomColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
public static class CustomColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {

View File

@ -85,6 +85,31 @@
FROM oms_receivable_invoice_detail_item
WHERE id = #{id}
LIMIT 1
</select>
<select id="exportItem" resultType="com.ruoyi.sip.dto.InvoiceDetailItemExcelDto">
SELECT t1.id,
t1. invoice_bill_code,
t1. project_name,
t1. order_code,
t1. product_code,
t1.product_name,
t1. product_model,
t1. quantity,
t1. price,
t1. unit,
t1.all_price,
t1.tax_amount,
t1.tax_rate,
t2.buyer_bank,
t2.buyer_name,t2.buyer_credit_code,t2.buyer_bank_account,t2.invoice_type,t3.address as buyer_address,t2.remark
FROM oms_receivable_invoice_detail_item t1
left join oms_invoice_bill t2 ON t1.invoice_bill_code=t2.invoice_bill_code
left join partner_info t3 on t2.partner_code=t3.partner_code
where t1.invoice_bill_code=#{code}
</select>

View File

@ -560,7 +560,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
seller_bank_account = #{sellerBankAccount},
approve_status=#{approveStatus},
invoice_type=#{invoiceType},
remark=#{remark}
remark=#{remark},
update_time=#{updateTime}
where invoice_bill_code = #{invoiceBillCode}
</update>