feat(finance): 新增收票单管理功能

- 新增收票单新增、详情查看、附件上传、退回及红冲申请功能
- 实现收票单与应付单关联展示及合并收票逻辑
- 提供收票计划选择与金额计算功能
- 支持收票状态、审批状态等多维度查询过滤
- 集成字典标签展示收票类型、审批状态等相关字段
- 添加收票单据的增删改查接口支持
dev_1.0.0
chenhao 2025-12-11 20:06:23 +08:00
parent ed9fda7fe3
commit 4cc2d3beb1
30 changed files with 4394 additions and 15 deletions

View File

@ -0,0 +1,362 @@
<template>
<el-dialog title="合并发起收票单" :visible.sync="dialogVisible" width="80%" @close="handleClose" append-to-body>
<div class="dialog-body">
<el-form ref="form" :model="form" :inline="true" label-width="120px">
<el-row>
<el-col :span="8">
<el-form-item label="收票单类型" prop="ticketBillType">
<el-select disabled v-model="form.ticketBillType" placeholder="请选择收票单类型" clearable>
<el-option
v-for="dict in dict.type.payment_bill_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="制造商名称">
<el-input v-model="form.vendorName" readonly/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="预计收票时间" prop="ticketTime">
<el-date-picker
v-model="form.ticketTime"
type="date"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider content-position="left">采购应付单表</el-divider>
<el-table :data="payableOrdersWithPlans" border max-height="300px" style="margin-bottom: 20px;">
<el-table-column label="应付单编号" align="center" prop="payableBillCode" width="150"/>
<el-table-column label="预计收票时间" align="center" prop="estimatedPaymentTime" width="180"/>
<el-table-column label="收票计划" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.ticketPlans ? scope.row.ticketPlans.length : 0 }} 条计划</span>
</template>
</el-table-column>
<el-table-column label="项目名称" align="center" prop="projectName" width="150"/>
<el-table-column label="合同编号" align="center" prop="orderCode" width="150"/>
<el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150"/>
<el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column>
<el-table-column label="含税总价" align="center" prop="totalPriceWithTax" width="120"/>
<el-table-column label="未收票金额" align="center" prop="unInvoicedAmount" width="120"/>
<el-table-column label="本次收票金额" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentTicketAmount(scope.row.id).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="本次收票比例" align="center" width="120">
<template slot-scope="scope">
{{ calculateOrderCurrentTicketRate(scope.row.id) }}%
</template>
</el-table-column>
<el-table-column label="已收票金额" align="center" prop="invoicedAmount" width="120"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleOpenTicketPlanSelector(scope.row, scope.$index)"
>选择
</el-button>
</template>
</el-table-column>
</el-table>
<div class="total-info">
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{
totalPlannedAmount.toFixed(2)
}}</el-tag></span>
<span>计划收票比例: <el-tag type="info">{{ this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4)*100 }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleConfirm"> </el-button>
</div>
<el-dialog :title="planTitle" :visible.sync="isTicketPlanSelectorOpen" width="70%"
@close="isTicketPlanSelectorOpen=false" append-to-body>
<receiving-ticket-plan
ref="planSelector"
:payable-data="choosePayable"
:selected-plans="choosePayable.ticketPlans"
@confirm="handleTicketPlanConfirm"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isTicketPlanSelectorOpen=false"> </el-button>
<!-- <el-button type="primary" @click="handleConfirm" > </el-button>-->
<el-button type="primary" @click="handleChooseConfirm"> </el-button>
</div>
</el-dialog>
<!-- 收票计划选择器弹窗 -->
</el-dialog>
</template>
<script>
import ReceivingTicketPlan from './ReceivingTicketPlan.vue';
import {getReceivingTicketPlan} from "@/api/finance/payable";
export default {
name: "MergeReceiptDialog",
components: {ReceivingTicketPlan},
dicts: ['invoice_status','payment_bill_type'], // Add dicts for dict-tag
props: {
visible: {
type: Boolean,
default: false
},
payableOrders: {
type: Array,
default: () => []
}
},
data() {
return {
internalVisible: this.visible,
planTitle: '',
choosePayable: {},
form: {
ticketBillType: 'FROM_PAYABLE', // Default to a type, or make it dynamic
vendorName: '',
ticketTime: null,
},
payableOrdersWithPlans: [], // Each order will now have its own ticketPlans array
isTicketPlanSelectorOpen: false,
currentPayableOrderIndexForPlan: -1, // Index of the order in payableOrdersWithPlans
loadingTicketPlans: false, // Loading state for fetching ticket plans
};
},
computed: {
dialogVisible: {
get() {
return this.internalVisible;
},
set(val) {
this.internalVisible = val;
this.$emit('update:visible', val);
}
},
totalPayableAmountWithTax() {
return this.payableOrdersWithPlans.reduce((sum, order) => sum + (order.totalPriceWithTax || 0), 0);
},
totalPlannedAmount() {
return this.payableOrdersWithPlans.reduce((orderSum, order) => {
const orderPlansTotal = (order.ticketPlans || []).reduce((planSum, plan) => planSum + (plan.planAmount || 0), 0);
return orderSum + orderPlansTotal;
}, 0);
},
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.initDialogData();
}
},
payableOrders: {
handler(newVal) {
if (this.dialogVisible) {
this.initDialogData();
}
},
deep: true
}
},
methods: {
initDialogData() {
// Initialize form fields
if (this.payableOrders.length > 0) {
const firstVendorName = this.payableOrders[0].vendorName;
const allSameVendor = this.payableOrders.every(order => order.vendorName === firstVendorName);
this.form.vendorName = allSameVendor ? firstVendorName : '多个制造商';
this.form.ticketTime = null; // Reset time
} else {
this.form.vendorName = '';
this.form.ticketTime = null;
}
this.form.ticketBillType = 'FROM_PAYABLE'; // Default
// Initialize payableOrdersWithPlans
this.payableOrdersWithPlans = this.payableOrders.map(order => ({
...order,
ticketPlans: order.ticketPlans || [], // Retain existing plans if any, otherwise empty
totalPriceWithTax: order.totalPriceWithTax || 0, // Ensure numeric for calculations
unInvoicedAmount: order.unInvoicedAmount || 0,
invoicedAmount: order.invoicedAmount || 0, // Ensure numeric for calculations
}));
},
handleClose() {
this.dialogVisible = false;
this.resetForm();
},
handleChooseConfirm() {
if (!this.$refs.planSelector || !this.$refs.planSelector.selectedPlan) {
this.$modal.msgError('无法获取计划选择器组件或其选择的计划');
return;
}
const selectedPlans = this.$refs.planSelector.selectedPlan;
const orderIndex = this.payableOrdersWithPlans.findIndex(o => o.id === this.choosePayable.id);
if (orderIndex === -1) {
this.$modal.msgError('找不到要更新的应付单');
return;
}
const currentOrder = this.payableOrdersWithPlans[orderIndex];
// Ensure the ticketPlans array exists
if (!currentOrder.ticketPlans) {
this.$set(currentOrder, 'ticketPlans', []);
}
// Add new plans, checking for duplicates by plan ID
let addedCount = 0;
selectedPlans.forEach(newPlan => {
const existingPlan = currentOrder.ticketPlans.find(p => p.id === newPlan.id);
if (!existingPlan) {
currentOrder.ticketPlans.push(newPlan);
addedCount++;
}
});
this.isTicketPlanSelectorOpen = false;
if (addedCount > 0) {
this.$modal.msgSuccess(`成功补充 ${addedCount} 条收票计划`);
} else {
this.$modal.msgWarning('没有新的收票计划被添加');
}
},
handleConfirm() {
// Validate main form fields
if (!this.form.ticketBillType) {
this.$modal.msgError('请选择收票单类型');
return;
}
if (!this.form.ticketTime) {
this.$modal.msgError('请选择预计收票时间');
return;
}
// Validate each payable order's ticket plans
for (const order of this.payableOrdersWithPlans) {
if (!order.ticketPlans || order.ticketPlans.length === 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 至少需要一条收票计划`);
return;
}
for (const plan of order.ticketPlans) {
if (!plan.planTicketDate) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中预计收票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中预计收票金额必须大于0。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined) {
this.$modal.msgError(`应付单 ${order.payableBillCode} 的收票计划中收票比例不能为空。`);
return;
}
}
}
// Construct the final data to be emitted to the parent
const mergedReceiptData = {
ticketBillType: this.form.ticketBillType,
ticketTime: this.form.ticketTime,
// Collect all payable orders with their updated ticket plans
payableOrders: this.payableOrdersWithPlans.map(order => ({
id: order.id,
payableBillCode: order.payableBillCode,
ticketPlans: order.ticketPlans.map(plan => ({
planTicketDate: plan.planTicketDate,
planAmount: plan.planAmount,
planRate: plan.planRate,
remark: plan.remark,
id: plan.id,
})),
})),
totalMergeTicketAmount: this.totalPlannedAmount, // Total amount for the merged bill
};
this.$emit('confirm', mergedReceiptData);
this.dialogVisible = false;
},
handleCancel() {
this.dialogVisible = false;
this.resetForm();
},
resetForm() {
this.form = {
ticketBillType: 'FROM_PAYABLE',
vendorName: '',
ticketTime: null,
};
this.payableOrdersWithPlans = [];
this.currentPayableOrderIndexForPlan = -1;
this.loadingTicketPlans = false;
},
handleOpenTicketPlanSelector(row, index) {
this.planTitle = `选择收票计划 - ${row.payableBillCode}`;
this.choosePayable = row;
this.currentPayableOrderIndexForPlan = index;
this.isTicketPlanSelectorOpen = true;
console.log(this.choosePayable.id)
},
handleTicketPlanConfirm(updatedPlans) {
// Update the ticket plans for the specific order
if (this.currentPayableOrderIndexForPlan !== -1) {
this.$set(this.payableOrdersWithPlans[this.currentPayableOrderIndexForPlan], 'ticketPlans', updatedPlans);
}
this.isTicketPlanSelectorOpen = false;
this.currentPayableOrderIndexForPlan = -1;
},
calculateOrderCurrentTicketAmount(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.ticketPlans) {
return order.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
}
return 0;
},
calculateOrderCurrentTicketRate(orderId) {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.ticketPlans && order.unInvoicedAmount >= 0) {
const currentAmount = this.calculateOrderCurrentTicketAmount(orderId);
return this.$calc.mul(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ),100);
}
return 0;
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<div class="dialog-body">
<el-divider content-position="left">收票计划</el-divider>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSaveTicketPlan"
style="margin-bottom: 10px;">
保存收票计划
</el-button>
<el-button v-else type="primary" size="mini" @click="isEditing=true"
style="margin-bottom: 10px;">
编辑
</el-button>
<el-table :data="ticketPlans" border @selection-change="selectPlan" ref="ticketPlanTable">
<el-table-column type="selection" width="50" align="center" :selectable="selectableRow"/>
<el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
<el-table-column label="预计收票时间" align="center" width="200">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planTicketDate" type="date" style="width: 180px"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="预计收票金额" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planAmount"
:precision="2"
:step="100"
:min="0.01"
:readonly="!scope.row.detailId"
:max="totalPriceWithTax"
@change="handleAmountChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="收票比例(%)" align="center" width="230">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.planRate"
:precision="2"
:step="1"
:min="0.01"
:max="100"
:readonly="!scope.row.detailId"
@change="handleRateChange(scope.row)"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddTicketPlanRow"
>增加下行
</el-button>
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDeleteTicketPlanRow(scope.$index)"
:disabled="ticketPlans.length === 1 || scope.row.status === 'received'"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import {getReceivingTicketPlan, updateReceivingTicketPlan} from "@/api/finance/payable";
import {isNumberStr} from "@/utils";
export default {
name: "ReceivingTicketPlan",
props: {
payableData: {
type: Object,
default: () => {
}
},
isInitEdit: {
type: Boolean,
default: false
},
selectedPlans: {
type: Array,
default: () => []
}
},
data() {
return {
selectedPlan:[],
isEditing: false,
loading: false,
ticketPlans: [],
};
},
computed: {
title() {
return `选择收票计划 - ${this.payableData.payableBillCode}`;
},
totalPlannedAmount() {
return this.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
},
totalUnpaidAmount() {
return this.payableData.unInvoicedAmount || 0;
},
totalPriceWithTax() {
return this.payableData.totalPriceWithTax || 0;
}
},
watch: {
'payableData.id': {
handler(newVal, oldVal) {
if (newVal) {
this.fetchTicketPlans(newVal)
}
},
immediate: true
},
isInitEdit: {
handler(newVal) {
if (newVal) {
this.isEditing = newVal
}
},
immediate: true
}
},
methods: {
isNumberStr,
selectableRow(row, index){
return !row.detailId;
},
selectPlan( selection){
this.selectedPlan=selection
},
fetchTicketPlans(payableId) {
if (this.payableData && payableId) {
getReceivingTicketPlan(payableId).then(response => {
this.ticketPlans = response.data.map(item => ({
...item,
status: item.status || 'pending'
}));
if (this.ticketPlans.length === 0) {
this.initDefaultTicketPlan();
} else {
this.$nextTick(() => {
this.ticketPlans.forEach(plan => {
const isSelected = this.selectedPlans.some(selected => selected.id === plan.id);
if (isSelected) {
this.$refs.ticketPlanTable.toggleRowSelection(plan, true);
}
});
});
}
})
} else {
this.initDefaultTicketPlan();
}
},
initDefaultTicketPlan() {
this.ticketPlans = [{
planTicketDate: null,
planAmount: this.totalPriceWithTax,
planRate: 100,
remark: '',
status: 'pending'
}];
},
handleSaveTicketPlan() {
if (!this.validateTicketPlans()) {
return;
}
if (!this.validateTicketPlanTotals()) {
return;
}
for (let i = 0; i < this.ticketPlans.length; i++) {
const plan = this.ticketPlans[i];
if (!plan.planTicketDate) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票时间不能为空。`);
return;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount === '' || plan.planAmount < 0) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票金额不能为空。`);
return;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行收票计划的收票比例不能为空。`);
return;
}
}
updateReceivingTicketPlan(this.payableData.id, this.ticketPlans).then(() => {
this.$modal.msgSuccess("保存成功");
this.fetchTicketPlans(this.payableData.id);
});
},
handleAddTicketPlanRow() {
this.ticketPlans.push({
planTicketDate: null,
planAmount: 0,
planRate: 0,
remark: '',
status: 'pending'
});
},
handleDeleteTicketPlanRow(index) {
if (this.ticketPlans.length === 1) {
this.$modal.msgError("至少需要保留一条收票计划。");
return;
}
if (this.ticketPlans[index].status === 'received') {
this.$modal.msgError("已收票的计划不能删除。");
return;
}
this.ticketPlans.splice(index, 1);
},
handleAmountChange(row) {
if (this.totalPriceWithTax === 0) {
row.planRate = 0;
return;
}
row.planRate = this.$calc.mul((this.$calc.div(row.planAmount, this.totalPriceWithTax,4)), 100);
},
handleRateChange(row) {
row.planAmount = this.$calc.div(this.$calc.mul(this.totalPriceWithTax , row.planRate),100);
},
validateTicketPlanTotals() {
const totalAmount = this.ticketPlans.reduce((sum, plan) => sum + (plan.planAmount || 0), 0);
if (totalAmount !== this.totalPriceWithTax) {
this.$modal.msgError(`预计收票金额之和应该等于应付总金额[${this.totalPriceWithTax}]`);
return false;
}
return true;
},
validateTicketPlans() {
if (this.ticketPlans.length === 0) {
this.$modal.msgError("请至少添加一条收票计划。");
return false;
}
for (let i = 0; i < this.ticketPlans.length; i++) {
const plan = this.ticketPlans[i];
if (!plan.planTicketDate) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票时间不能为空。`);
return false;
}
if (plan.planAmount === null || plan.planAmount === undefined || plan.planAmount <= 0) {
this.$modal.msgError(`${i + 1} 行收票计划的预计收票金额必须大于0。`);
return false;
}
if (plan.planRate === null || plan.planRate === undefined || plan.planRate === '') {
this.$modal.msgError(`${i + 1} 行收票计划的应付比例不能为空。`);
return false;
}
}
return true;
}
}
};
</script>
<style scoped>
.dialog-body {
max-height: 70vh;
overflow-y: auto;
padding-right: 10px; /* To prevent scrollbar from overlapping content */
}
.total-info {
margin-top: 20px;
text-align: right;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<el-dialog title="新增收票单" :visible.sync="internalVisible" width="500px" @close="handleClose">
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="收票单类型" prop="receiptBillType">
<el-select v-model="form.receiptBillType" disabled placeholder="请选择收票单类型">
<el-option
v-for="dict in dicts.receipt_bill_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorCode">
<el-select v-model="form.vendorCode" placeholder="请选择制造商" style="width:100%" >
<el-option v-for="item in vendorOptions" :key="item.vendorCode" :label="item.vendorName" :value="item.vendorCode"></el-option>
</el-select>
</el-form-item>
<el-form-item label="预计收票时间" prop="receiptTime">
<el-date-picker
v-model="form.receiptTime"
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm:ss"
></el-date-picker>
</el-form-item>
<el-form-item label="含税总价" prop="totalPriceWithTax">
<el-input-number v-model="form.totalPriceWithTax" @change="handlePriceChange" :precision="2"
:step="1"></el-input-number>
</el-form-item>
<el-form-item label="税率" prop="taxRate">
<el-input-number v-model="form.taxRate" @change="handlePriceChange" :precision="2" :step="0.01"
:max="1"></el-input-number>
</el-form-item>
<el-form-item label="未税总价" prop="totalPriceWithoutTax">
<el-input v-model="form.totalPriceWithoutTax" :disabled="true"/>
</el-form-item>
<el-form-item label="税额" prop="taxAmount">
<el-input v-model="form.taxAmount" :disabled="true"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {listAllVendor} from "@/api/base/vendor";
export default {
name: "AddForm",
props: {
visible: {
type: Boolean,
default: false
},
dicts: {
type: Object,
default: () => ({})
}
},
data() {
return {
internalVisible: this.visible,
vendorOptions: [],
form: {
receiptBillType:'STANDARD_INVOICE', // Default type, assuming STANDARD_INVOICE exists or user changes it
vendorName: null,
receiptTime: null,
totalPriceWithTax: 0,
taxRate: 0.13, // Default tax rate
totalPriceWithoutTax: 0,
taxAmount: 0
},
rules: {
receiptBillType: [{required: true, message: "收票单类型不能为空", trigger: "change"}],
vendorName: [{required: true, message: "制造商名称不能为空", trigger: "blur"}],
receiptTime: [{required: true, message: "预计收票时间不能为空", trigger: "change"}],
totalPriceWithTax: [{required: true, message: "含税总价不能为空", trigger: "blur"}]
}
};
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.resetForm();
this.getVendorList();
}
},
internalVisible(newVal) {
this.$emit("update:visible", newVal);
}
},
methods: {
/** 获取厂商列表 */
getVendorList() {
return listAllVendor().then(res => {
this.vendorOptions = res.data;
})
},
handlePriceChange() {
const taxed = this.form.totalPriceWithTax || 0;
const rate = this.form.taxRate || 0;
const unTaxed = this.$calc.div(taxed, this.$calc.add(1, rate));
const taxAmount = this.$calc.sub(taxed, unTaxed);
this.form.totalPriceWithoutTax = unTaxed;
this.form.taxAmount = taxAmount;
},
handleClose() {
this.internalVisible = false;
},
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
this.$emit("submit", this.form);
}
});
},
resetForm() {
if (this.$refs.form) {
this.$refs.form.resetFields();
}
this.form = {
receiptBillType: 'STANDARD_INVOICE',
vendorName: null,
receiptTime: null,
totalPriceWithTax: 0,
taxRate: 0.13,
totalPriceWithoutTax: 0,
taxAmount: 0
};
this.handlePriceChange();
}
}
};
</script>

View File

@ -0,0 +1,141 @@
<template>
<el-drawer
title="收票单详情"
:visible.sync="visible"
direction="rtl"
size="70%"
@close="handleClose"
>
<div class="dialog-body" v-if="detail">
<div class="section">
<el-divider content-position="left">采购-收票单</el-divider>
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">采购-收票单编号: {{ detail.ticketBillCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">预计收票时间: {{ detail.ticketTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">制造商开票时间: {{ detail.vendorTicketTime }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">制造商名称: {{ detail.vendorName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">含税总价: {{ detail.totalPriceWithTax }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">未税总价: {{ detail.totalPriceWithoutTax }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">税额: {{ detail.taxAmount }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">发票含税总价: {{ detail.actualReceiptTime || '-'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">发票未税总价: {{ detail.actualReceiptTime || '-'}}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">发票税额: {{ detail.taxAmount }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">收票状态:
<dict-tag :options="dict.type.receipt_status" :value="detail.ticketStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">备注: {{ detail.remark }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item">审批节点: {{ detail.approveNode|| '-' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批状态:
<dict-tag :options="dict.type.approve_status" :value="detail.approveStatus"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item">审批通过时间: {{ detail.approveTime || '-'}}</div>
</el-col>
</el-row>
</div>
</div>
<div class="section">
<el-divider content-position="left">采购-应付单</el-divider>
<el-table :data="detail.detailList">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column property="projectCode" label="项目编号"></el-table-column>
<el-table-column property="projectName" label="项目名称"></el-table-column>
<el-table-column property="payableBillCode" label="采购应付单编号"></el-table-column>
<el-table-column property="totalPriceWithTax" label="含税总价"></el-table-column>
<el-table-column property="paymentAmount" label="本次收票金额"></el-table-column>
<el-table-column property="paymentRate" label="本次收票比例(%)"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
</template>
<script>
export default {
name: "DetailDrawer",
props: {
visible: {
type: Boolean,
default: false,
},
detail: {
type: Object,
default: () => null,
},
},
dicts:['receipt_bill_type','approve_status','receipt_status'],
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
};
</script>
<style scoped>
.dialog-body {
max-height: calc(100vh - 50px);
overflow-y: auto;
padding: 0 20px 20px 20px;
}
.details-container {
border: 1px solid #EBEEF5;
padding: 20px;
border-radius: 4px;
}
.detail-item {
display: flex;
border: 1px solid #EBEEF5;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
align-items: center;
}
.section {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,572 @@
<template>
<el-dialog :title="titleText" :visible.sync="dialogVisible" width="900px" @close="handleClose">
<div v-if="loading" class="loading-spinner">
<i class="el-icon-loading"></i>
</div>
<div v-else class="receipt-dialog-body">
<div v-if="canUpload" class="upload-btn-container">
<el-button type="primary" icon="el-icon-upload" @click="openUploadDialog">{{ titleText }}</el-button>
</div>
<el-timeline v-if="attachments.length > 0">
<el-timeline-item
v-for="attachment in attachments"
:key="attachment.id"
:timestamp="parseTime(attachment.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
placement="top"
>
<el-card>
<div class="receipt-card-content">
<div class="receipt-details">
<div class="detail-item">
<span class="item-label">票据类型</span>
<span class="item-value"><dict-tag :options="dict.type.finance_invoice_type" :value="attachment.ticketType"/></span>
</div>
<div class="detail-item">
<span class="item-label">{{ titleText }}</span>
<div class="item-value">
<div class="image-wrapper">
<el-image
v-if="!isPdf(attachment.filePath)"
:src="getImageUrl(attachment.filePath)"
:preview-src-list="previewList"
style="width: 200px; height: 150px;"
fit="contain"
></el-image>
<div v-else-if="pdfUrls[attachment.filePath]" class="pdf-thumbnail-container" @click="openPdfPreview(pdfUrls[attachment.filePath])">
<iframe :src="pdfUrls[attachment.filePath]" width="100%" height="150px" frameborder="0"></iframe>
<div class="pdf-hover-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
<div v-if="attachment.delFlag === '2'" class="void-overlay"></div>
</div>
<el-button
size="mini"
type="primary"
class="download-btn"
icon="el-icon-download"
@click="downloadFile(attachment)"
>下载{{ titleText }}</el-button>
</div>
</div>
<div class="detail-item">
<span class="item-label">含税金额</span>
<span class="item-value">{{ receiptData.totalPriceWithTax }}</span>
</div>
<div class="detail-item">
<span class="item-label">发票含税金额</span>
<span class="item-value">{{ receiptData.ticketPriceWithTax }}</span>
</div>
<div class="detail-item">
<span class="item-label">未税金额</span>
<span class="item-value">{{ receiptData.totalPriceWithoutTax }}</span>
</div>
<div class="detail-item">
<span class="item-label">发票未税金额</span>
<span class="item-value">{{ receiptData.ticketPriceWithoutTax }}</span>
</div>
<div class="detail-item">
<span class="item-label">税额</span>
<span class="item-value">{{ receiptData.taxAmount }}</span>
</div>
<div class="detail-item">
<span class="item-label">发票税额</span>
<span class="item-value">{{ receiptData.ticketAmount }}</span>
</div>
<div class="detail-item">
<span class="item-label">备注</span>
<span class="item-value">{{ attachment.remark }}</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else :description="'暂无' + titleText"></el-empty>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</span>
<!-- PDF Preview Dialog -->
<el-dialog
:visible.sync="pdfPreviewVisible"
width="80%"
top="5vh"
append-to-body
custom-class="pdf-preview-dialog"
>
<iframe :src="currentPdfUrl" width="100%" height="600px" frameborder="0"></iframe>
</el-dialog>
<!-- Upload Dialog -->
<el-dialog
:title="'上传' + titleText"
:visible.sync="uploadDialogVisible"
width="70vw"
append-to-body
@close="closeUploadDialog"
custom-class="upload-receipt-dialog"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form :model="uploadForm" ref="uploadForm" :rules="rules" label-width="120px" size="medium" >
<el-form-item label="票据类型" prop="ticketType" required>
<el-select v-model="uploadForm.ticketType" placeholder="请输入发票含税总价">
<el-option
v-for="item in dict.type.finance_invoice_type"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="收票附件" prop="file" required>
<div style="display: flex; flex-direction: column; align-items: flex-start;">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.pdf"
>
<el-button size="small" type="primary" icon="el-icon-upload2">{{ uploadForm.file ? '重新上传' : '点击上传' }}</el-button>
</el-upload>
<div class="el-upload__tip" style="line-height: 1.5; margin-top: 5px;">支持上传PNGJPGPDF文件格式</div>
</div>
</el-form-item>
<el-form-item label="含税总价">
<span>{{ receiptData.totalPriceWithTax }}</span>
</el-form-item>
<el-form-item label="发票含税总价" prop="ticketPriceWithTax" required>
<el-input v-model="uploadForm.ticketPriceWithTax" placeholder="请输入发票含税总价"></el-input>
</el-form-item>
<el-form-item label="未税总价">
<span>{{ receiptData.totalPriceWithoutTax }}</span>
</el-form-item>
<el-form-item label="发票未税总价" prop="ticketPriceWithoutTax" required>
<el-input v-model="uploadForm.ticketPriceWithoutTax" placeholder="请输入发票未税总价"></el-input>
</el-form-item>
<el-form-item label="税额">
<span>{{ receiptData.taxAmount }}</span>
</el-form-item>
<el-form-item label="发票税额" prop="ticketAmount" required>
<el-input v-model="uploadForm.ticketAmount" placeholder="请输入发票税额"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input
type="textarea"
v-model="uploadForm.remark"
:rows="4"
placeholder="此处备注描述..."
></el-input>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<div class="upload-preview-container" style="height: 70vh;">
<div v-if="previewUrl" class="preview-content">
<img v-if="!isPreviewPdf" :src="previewUrl" class="preview-image" />
<iframe v-else :src="previewUrl" width="100%" height="100%" frameborder="0"></iframe>
</div>
<div v-else class="preview-placeholder">
<div class="placeholder-icon">
<i class="el-icon-picture"></i>
</div>
<div class="placeholder-text">点击图片进入预览</div>
</div>
</div>
</el-col>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitNewUpload"></el-button>
<el-button @click="closeUploadDialog"></el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { getReceiptAttachments, uploadReceiptAttachment } from "@/api/finance/receipt";
import request from '@/utils/request';
export default {
name: "ReceiptDialog",
props: {
visible: {
type: Boolean,
default: false,
},
receiptData: {
type: Object,
default: () => null,
},
dicts: {
type: Object,
default: () => ({})
}
},
dicts:['finance_invoice_type'],
data() {
return {
loading: false,
attachments: [],
// Upload Dialog Data
uploadDialogVisible: false,
uploadForm: {
totalPriceWithTax: '',
ticketPriceWithTax: '',
totalPriceWithoutTax: '',
ticketPriceWithoutTax: '',
taxAmount: '',
ticketType: '',
remark: '',
file: null
},
rules: {
ticketPriceWithTax: [
{ required: true, message: "请输入发票含税总价", trigger: "blur" }
],
ticketPriceWithoutTax: [
{ required: true, message: "请输入发票未税总价", trigger: "blur" }
],
ticketAmount: [
{ required: true, message: "请输入发票税额", trigger: "blur" }
]
},
previewUrl: '',
isPreviewPdf: false,
// PDF Preview Data
pdfUrls: {},
pdfPreviewVisible: false,
currentPdfUrl: '',
};
},
computed: {
dialogVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
previewList() {
return this.attachments
.filter(att => !this.isPdf(att.filePath))
.map(att => this.getImageUrl(att.filePath));
},
canUpload() {
if (!this.attachments || this.attachments.length === 0) {
return true;
}
return this.attachments.every(att => att.delFlag === '2');
},
titleText() {
return '发票';
}
},
watch: {
visible(val) {
if (val && this.receiptData) {
this.fetchAttachments();
}
},
},
methods: {
fetchAttachments() {
if (!this.receiptData.id) return;
this.loading = true;
getReceiptAttachments(this.receiptData.id, { type: 'ticket' })
.then(response => {
const data = response.data || [];
data.sort((a, b) => new Date(b.createTime) - new Date(a.createTime));
this.attachments = data;
this.loadPdfPreviews();
this.loading = false;
})
.catch(() => {
this.attachments = [];
this.loading = false;
});
},
loadPdfPreviews() {
this.attachments.forEach(att => {
if (this.isPdf(att.filePath) && !this.pdfUrls[att.filePath]) {
request({
url: '/common/download/resource',
method: 'get',
params: { resource: att.filePath },
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
this.$set(this.pdfUrls, att.filePath, url);
}).catch(console.error);
}
});
},
openPdfPreview(url) {
if (!url) return;
this.currentPdfUrl = url;
this.pdfPreviewVisible = true;
},
getImageUrl(resource) {
return process.env.VUE_APP_BASE_API + "/common/download/resource?resource=" + resource;
},
isPdf(filePath) {
return filePath && filePath.toLowerCase().endsWith('.pdf');
},
downloadFile(attachment) {
const link = document.createElement('a');
link.href = this.getImageUrl(attachment.filePath);
link.download = attachment.fileName || 'receipt';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
handleClose() {
this.attachments = [];
// Clean up object URLs
Object.values(this.pdfUrls).forEach(url => URL.revokeObjectURL(url));
this.pdfUrls = {};
},
// New Upload Dialog Methods
openUploadDialog() {
this.uploadForm = {
totalPriceWithTax: '',
totalPriceWithoutTax: '',
taxAmount: '',
remark: '',
file: null
};
if (this.$refs.uploadForm) {
this.$refs.uploadForm.clearValidate();
}
this.previewUrl = '';
this.isPreviewPdf = false;
this.uploadDialogVisible = true;
},
closeUploadDialog() {
this.uploadDialogVisible = false;
this.uploadForm.file = null;
this.previewUrl = '';
},
handleFileChange(file) {
const isLt2M = file.size / 1024 / 1024 < 2;
const isAcceptedType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.raw.type);
if (!isAcceptedType) {
this.$message.error('上传文件只能是 JPG/PNG/PDF 格式!');
return;
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 2MB!');
return;
}
this.uploadForm.file = file.raw;
this.isPreviewPdf = file.raw.type === 'application/pdf';
this.previewUrl = URL.createObjectURL(file.raw);
},
handleFileRemove() {
this.uploadForm.file = null;
this.previewUrl = '';
},
submitNewUpload() {
this.$refs.uploadForm.validate(valid => {
if (valid) {
if (!this.uploadForm.file) {
this.$message.warning("请选择要上传的文件");
return;
}
const formData = new FormData();
formData.append("file", this.uploadForm.file);
formData.append("id", this.receiptData.id);
formData.append("remark", this.uploadForm.remark);
// formData.append("totalPriceWithTax", this.uploadForm.totalPriceWithTax);
formData.append("ticketPriceWithTax", this.uploadForm.ticketPriceWithTax);
// formData.append("totalPriceWithoutTax", this.uploadForm.totalPriceWithoutTax);
formData.append("ticketPriceWithoutTax", this.uploadForm.ticketPriceWithoutTax);
// formData.append("taxAmount", this.uploadForm.taxAmount);
formData.append("ticketType", this.uploadForm.ticketType);
uploadReceiptAttachment(formData)
.then(response => {
this.$message.success("上传成功");
this.closeUploadDialog();
this.fetchAttachments();
})
.catch(error => {
this.$message.error("上传失败");
});
}
});
},
},
};
</script>
<style scoped>
.receipt-dialog-body {
max-height: 60vh;
overflow-y: auto;
}
.loading-spinner {
text-align: center;
font-size: 24px;
padding: 20px;
}
.receipt-card-content {
display: flex;
}
.receipt-details {
flex-grow: 1;
}
.detail-item {
display: flex;
margin-bottom: 12px;
font-size: 14px;
}
.item-label {
width: 110px;
color: #606266;
text-align: right;
margin-right: 15px;
flex-shrink: 0;
}
.item-value {
color: #303133;
}
.image-wrapper {
position: relative;
width: 200px;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #DCDFE6;
border-radius: 4px;
margin-bottom: 10px;
}
.void-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-30deg);
color: red;
font-size: 48px;
font-weight: bold;
opacity: 0.7;
pointer-events: none;
}
.download-btn {
display: block;
}
.upload-btn-container {
margin-bottom: 20px;
}
.pdf-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 200px;
height: 150px;
color: #606266;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
}
.pdf-placeholder:hover {
border-color: #409EFF;
color: #409EFF;
}
.pdf-placeholder .el-icon-document {
font-size: 48px;
margin-bottom: 5px;
}
/* New Dialog Styles */
.upload-preview-container {
width: 100%;
height: 300px;
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.preview-content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.preview-pdf {
display: flex;
flex-direction: column;
align-items: center;
font-size: 16px;
color: #606266;
}
.preview-pdf .el-icon-document {
font-size: 64px;
margin-bottom: 10px;
}
.preview-placeholder {
text-align: center;
color: #909399;
}
.placeholder-icon {
font-size: 64px;
margin-bottom: 10px;
color: #c0c4cc;
}
.placeholder-text {
font-size: 14px;
}
.pdf-thumbnail-container {
position: relative;
width: 100%;
height: 150px;
cursor: pointer;
}
.pdf-hover-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
color: #fff;
font-size: 24px;
pointer-events: auto;
}
.pdf-thumbnail-container:hover .pdf-hover-overlay {
opacity: 1;
}
</style>

View File

@ -0,0 +1,345 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="项目编号" prop="projectCode">
<el-input
v-model="queryParams.projectCode"
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="收票单编号" prop="receiptBillCode">
<el-input
v-model="queryParams.receiptBillCode"
placeholder="请输入收票单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorCode">
<el-input
v-model="queryParams.vendorCode"
placeholder="请输入制造商名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="应付单编号" prop="payableBillCode">
<el-input
v-model="queryParams.payableBillCode"
placeholder="请输入应付单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="收票单类型" prop="receiptBillType">
<el-select v-model="queryParams.receiptBillType" placeholder="请选择收票单类型" clearable>
<el-option v-for="dict in dict.type.receipt_bill_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="approveStatus">
<el-select v-model="queryParams.approveStatus" placeholder="请选择审批状态" clearable>
<el-option label="待提交" value="0" />
<el-option label="审批中" value="1" />
<el-option label="已审批" value="2" />
<el-option label="已驳回" value="3" />
</el-select>
</el-form-item>
<el-form-item label="收票状态" prop="receiptStatus">
<el-select v-model="queryParams.receiptStatus" placeholder="请选择收票状态" clearable>
<el-option label="待收票" value="0" />
<el-option label="已收票" value="1" />
</el-select>
</el-form-item>
<el-form-item label="审批节点" prop="approveNode">
<el-input
v-model="queryParams.approveNode"
placeholder="请输入审批节点"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="审批通过时间">
<el-date-picker
v-model="dateRangeApproval"
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 label="预计收票时间">
<el-date-picker
v-model="dateRangeEstimated"
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 label="实际收票时间">
<el-date-picker
v-model="dateRangeActual"
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-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="receiptList">
<el-table-column label="收票单编号" align="center" prop="ticketBillCode" />
<el-table-column label="预计收票时间" align="center" prop="ticketTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.ticketTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="制造商开票时间" align="center" prop="vendorTicketTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.vendorTicketTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="制造商名称" align="center" prop="vendorName" />
<el-table-column label="含税总价" align="center" prop="totalPriceWithTax" />
<el-table-column label="未税总价" align="center" prop="totalPriceWithoutTax" />
<el-table-column label="税额" align="center" prop="taxAmount" />
<!-- <el-table-column label="收票单类型" align="center" prop="ticketBillType" >-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.ticket_bill_type" :value="scope.row.ticketBillType"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="收票状态" align="center" prop="ticketStatus" >
<template slot-scope="scope">
<dict-tag :options="dict.type.receipt_status" :value="scope.row.ticketStatus"/>
</template>
</el-table-column>
<el-table-column label="实际收票时间" align="center" prop="actualTicketTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.actualTicketTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="审批状态" align="center" prop="approveStatus" >
<template slot-scope="scope">
<dict-tag :options="dict.type.approve_status" :value="scope.row.approveStatus"/>
</template>
</el-table-column>
<el-table-column label="审批通过时间" align="center" prop="approveTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</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">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
@click="handleReceipt(scope.row)"
>收票附件</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
@click="handleReturn(scope.row)"
>退回</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.approveStatus=='2'"
@click="handleRedRush(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"
/>
<!-- 详情抽屉 -->
<detail-drawer :visible.sync="detailOpen" :detail="detailData"></detail-drawer>
<!-- 新增弹窗 -->
<add-form :visible.sync="addOpen" :dicts="dict.type" @submit="handleAddSubmit"></add-form>
<!-- 收票附件弹窗 -->
<receipt-dialog :visible.sync="receiptOpen" :receipt-data="currentRow" :dicts="dict.type"></receipt-dialog>
</div>
</template>
<script>
import {listReceipt, getReceipt, redRush, addReceipt, returnReceipt} from "@/api/finance/receipt";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
import ReceiptDialog from "./components/ReceiptDialog.vue";
export default {
name: "Receipt",
components: {
DetailDrawer,
AddForm,
ReceiptDialog
},
dicts:['receipt_bill_type','approve_status','receipt_status'],
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
receiptList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
projectCode: null,
projectName: null,
receiptBillCode: null,
vendorCode: null,
payableBillCode: null,
receiptBillType: null,
approveStatus: null,
receiptStatus: null,
approveNode: null,
},
//
dateRangeApproval: [],
dateRangeEstimated: [],
dateRangeActual: [],
//
detailOpen: false,
detailData: null,
//
addOpen: false,
//
receiptOpen: false,
currentRow: null
};
},
created() {
this.getList();
},
methods: {
addDateRange,
getList() {
this.loading = true;
let query = { ...this.queryParams };
query = this.addDateRange(query, this.dateRangeApproval, 'ApproveTime');
query = this.addDateRange(query, this.dateRangeEstimated, 'ReceiptTime');
query = this.addDateRange(query, this.dateRangeActual, 'ActualReceiptTime');
listReceipt(query).then(response => {
this.receiptList = response.rows;
this.total = response.total;
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRangeApproval = [];
this.dateRangeEstimated = [];
this.dateRangeActual = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.addOpen = true;
},
/** 新增提交 */
handleAddSubmit(form) {
addReceipt(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增收票单失败", error);
this.$modal.msgError("新增失败");
});
},
/** 详情按钮操作 */
handleDetail(row) {
getReceipt(row.id).then(response => {
this.detailData = response.data;
this.detailOpen = true;
});
},
/** 收票附件按钮操作 */
handleReceipt(row) {
this.currentRow = row;
this.receiptOpen = true;
},
/** 退回按钮操作 */
handleRedRush(row) {
this.$modal.confirm('是否确认收票单编号为"' + row.ticketBillCode + '"的数据项进行红冲,并提交财务审批?').then(function() {
return redRush(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
},
handleReturn(row) {
this.$modal.confirm('是否确认退回收票单编号为"' + row.ticketBillCode + '"的数据项?').then(function() {
return returnReceipt(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,98 @@
package com.ruoyi.sip.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.OmsPayableTicketDetail;
import com.ruoyi.sip.service.IOmsPayableTicketDetailService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* Controller
*
* @author ruoyi
* @date 2025-12-09
*/
@RestController
@RequestMapping("/sip/ticketDetail")
public class OmsPayableTicketDetailController extends BaseController
{
@Autowired
private IOmsPayableTicketDetailService omsPayableTicketDetailService;
/**
*
*/
@RequiresPermissions("sip:ticketDetail:list")
@GetMapping("/list")
public TableDataInfo list(OmsPayableTicketDetail omsPayableTicketDetail)
{
startPage();
List<OmsPayableTicketDetail> list = omsPayableTicketDetailService.selectOmsPayableTicketDetailList(omsPayableTicketDetail);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("sip:ticketDetail:export")
@Log(title = "应付单收票明细", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, OmsPayableTicketDetail omsPayableTicketDetail)
{
List<OmsPayableTicketDetail> list = omsPayableTicketDetailService.selectOmsPayableTicketDetailList(omsPayableTicketDetail);
ExcelUtil<OmsPayableTicketDetail> util = new ExcelUtil<OmsPayableTicketDetail>(OmsPayableTicketDetail.class);
util.exportExcel(response, list, "应付单收票明细数据");
}
/**
*
*/
@RequiresPermissions("sip:ticketDetail:query")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(omsPayableTicketDetailService.selectOmsPayableTicketDetailById(id));
}
/**
*
*/
@RequiresPermissions("sip:ticketDetail:add")
@Log(title = "应付单收票明细", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody OmsPayableTicketDetail omsPayableTicketDetail)
{
return toAjax(omsPayableTicketDetailService.insertOmsPayableTicketDetail(omsPayableTicketDetail));
}
/**
*
*/
@RequiresPermissions("sip:ticketDetail:edit")
@Log(title = "应付单收票明细", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody OmsPayableTicketDetail omsPayableTicketDetail)
{
return toAjax(omsPayableTicketDetailService.updateOmsPayableTicketDetail(omsPayableTicketDetail));
}
/**
*
*/
@RequiresPermissions("sip:ticketDetail:remove")
@Log(title = "应付单收票明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(omsPayableTicketDetailService.deleteOmsPayableTicketDetailByIds(ids));
}
}

View File

@ -0,0 +1,97 @@
package com.ruoyi.sip.controller;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.OmsPayablePaymentPlan;
import com.ruoyi.sip.domain.OmsPayableTicketPlan;
import com.ruoyi.sip.service.IOmsPayableTicketPlanService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* Controller
*
* @author ruoyi
* @date 2025-12-09
*/
@RestController
@RequestMapping("/finance/ticket/plan")
public class OmsPayableTicketPlanController extends BaseController
{
@Autowired
private IOmsPayableTicketPlanService omsPayableTicketPlanService;
/**
*
*/
@RequiresPermissions("sip:ticketPlan:list")
@GetMapping("/list")
public TableDataInfo list(OmsPayableTicketPlan omsPayableTicketPlan)
{
startPage();
List<OmsPayableTicketPlan> list = omsPayableTicketPlanService.selectOmsPayableTicketPlanList(omsPayableTicketPlan);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("sip:ticketPlan:export")
@Log(title = "应付单收票计划", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, OmsPayableTicketPlan omsPayableTicketPlan)
{
List<OmsPayableTicketPlan> list = omsPayableTicketPlanService.selectOmsPayableTicketPlanList(omsPayableTicketPlan);
ExcelUtil<OmsPayableTicketPlan> util = new ExcelUtil<OmsPayableTicketPlan>(OmsPayableTicketPlan.class);
util.exportExcel(response, list, "应付单收票计划数据");
}
/**
*
*/
@RequiresPermissions("sip:ticketPlan:query")
@GetMapping(value = "/{payableBillId}")
public AjaxResult getInfo(@PathVariable("payableBillId") Long payableBillId)
{
return AjaxResult.success(omsPayableTicketPlanService.listByPayableBillId(payableBillId));
}
/**
*
*/
@RequiresPermissions("sip:ticketPlan:add")
@Log(title = "应付单收票计划", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody OmsPayableTicketPlan omsPayableTicketPlan)
{
return toAjax(omsPayableTicketPlanService.insertOmsPayableTicketPlan(omsPayableTicketPlan));
}
/**
*
*/
@PostMapping("/{payableBillId}")
public AjaxResult updateTicketPlan(@PathVariable("payableBillId") Long payableBillId, @RequestBody List<OmsPayableTicketPlan> ticketPlanList) {
omsPayableTicketPlanService.updateTicketPlan(payableBillId, ticketPlanList);
return AjaxResult.success();
}
/**
*
*/
@RequiresPermissions("sip:ticketPlan:remove")
@Log(title = "应付单收票计划", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(omsPayableTicketPlanService.deleteOmsPayableTicketPlanByIds(ids));
}
}

View File

@ -211,20 +211,7 @@ public class OmsPaymentBillController extends BaseController
return AjaxResult.error("操作失败:" + e.getMessage());
}
}
//
// /**
// * 上传退款图
// */
// @PostMapping("/uploadRefundProof")
// @ResponseBody
// public AjaxResult uploadRefundProof(@RequestParam("paymentBillId") Long paymentBillId, @RequestParam("file") MultipartFile file) {
// try {
// return omsPaymentBillService.uploadRefundProof(paymentBillId, file);
// } catch (Exception e) {
// logger.error("上传退款图失败", e);
// return AjaxResult.error("操作失败:" + e.getMessage());
// }
// }
}

View File

@ -0,0 +1,170 @@
package com.ruoyi.sip.controller;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.sip.domain.OmsFinAttachment;
import com.ruoyi.sip.service.IOmsFinAttachmentService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sip.domain.OmsTicketBill;
import com.ruoyi.sip.service.IOmsTicketBillService;
import com.ruoyi.common.core.page.TableDataInfo;
import org.springframework.web.multipart.MultipartFile;
/**
* Controller
*
* @author ruoyi
* @date 2025-12-09
*/
@RestController
@RequestMapping("/finance/ticket")
public class OmsTicketBillController extends BaseController
{
@Autowired
private IOmsTicketBillService omsTicketBillService;
@Autowired
private IOmsFinAttachmentService omsFinAttachmentService;
/**
*
*/
@RequiresPermissions("sip:ticketBill:list")
@GetMapping("/list")
public TableDataInfo list(OmsTicketBill omsTicketBill)
{
startPage();
List<OmsTicketBill> list = omsTicketBillService.selectOmsTicketBillList(omsTicketBill);
return getDataTable(list);
}
/**
*
*/
@RequiresPermissions("sip:ticketBill:export")
@Log(title = "采购收票单", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, OmsTicketBill omsTicketBill)
{
List<OmsTicketBill> list = omsTicketBillService.selectOmsTicketBillList(omsTicketBill);
ExcelUtil<OmsTicketBill> util = new ExcelUtil<OmsTicketBill>(OmsTicketBill.class);
util.exportExcel(response, list, "采购收票单数据");
}
/**
*
*/
@RequiresPermissions("sip:ticketBill:query")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(omsTicketBillService.selectOmsTicketBillById(id));
}
/**
*
*/
@RequiresPermissions("sip:ticketBill:add")
@Log(title = "采购收票单", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody OmsTicketBill omsTicketBill)
{
return toAjax(omsTicketBillService.insertOmsTicketBill(omsTicketBill));
}
/**
*
*/
@RequiresPermissions("sip:ticketBill:edit")
@Log(title = "采购收票单", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody OmsTicketBill omsTicketBill)
{
return toAjax(omsTicketBillService.updateOmsTicketBill(omsTicketBill));
}
/**
*
*/
@RequiresPermissions("sip:ticketBill:remove")
@Log(title = "采购收票单", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(omsTicketBillService.deleteOmsTicketBillByIds(ids));
}
/**
*
*/
@GetMapping("/attachment/{id}")
@ResponseBody
public AjaxResult viewReceipt(@PathVariable("id") Long id, @RequestParam("type") String type)
{
return AjaxResult.success( omsFinAttachmentService.list(Collections.singletonList(id),type));
}
//
/**
*
*/
@Log(title = "上传回执单", businessType = BusinessType.UPDATE)
@PostMapping("/uploadReceipt")
@ResponseBody
public AjaxResult uploadReceipt(OmsTicketBill ticketBill, @RequestParam("file") MultipartFile file)
{
try
{
return omsTicketBillService.uploadReceipt(ticketBill, file);
}
catch (Exception e)
{
logger.error("上传回执单失败", e);
return AjaxResult.error("操作失败:" + e.getMessage());
}
}
/**
*
*/
@GetMapping("/applyRefund/{id}")
@ResponseBody
public AjaxResult applyRefund(@PathVariable("id") Long id) {
try {
return omsTicketBillService.applyRefund(id);
} catch (Exception e) {
logger.error("申请退款失败", e);
return AjaxResult.error("操作失败:" + e.getMessage());
}
}
/**
* 退
*/
@RequiresPermissions("finance:payment:return")
@Log(title = "退回付款单", businessType = BusinessType.UPDATE)
@DeleteMapping("/return/{id}")
@ResponseBody
public AjaxResult returnTicket(@PathVariable("id") Long id)
{
try {
// 验证付款单ID
if (id == null) {
return AjaxResult.error("收票ID不能为空");
}
// 调用服务层方法处理退回逻辑
return omsTicketBillService.returnTicket(id);
} catch (Exception e) {
logger.error("退回付款单失败", e);
return AjaxResult.error("操作失败:" + e.getMessage());
}
}
}

View File

@ -56,7 +56,7 @@ public class OmsFinAttachment extends BaseEntity
/** 付款单 */
PAYMENT("payment", "付款单"),
INVOICE("invoice", "收票单"),
INVOICE("ticket", "收票单"),

View File

@ -0,0 +1,59 @@
package com.ruoyi.sip.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.Getter;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* oms_payable_ticket_detail
*
* @author ruoyi
* @date 2025-12-09
*/
@Data
public class OmsPayableTicketDetail extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
private Long ticketPlanId;
private Long payableBillId;
private List<Long> payableBillIdList;
private String payableDetailType;
// 收票时间
private Date paymentTime;
// 收票金额
private BigDecimal paymentAmount;
// 收票比例
private BigDecimal paymentRate;
// 收票单号
private String ticketBillCode;
private Long ticketBillId;
// 实际收票时间
private Date actualTicketTime;
// 收票状态
private String ticketStatus;
@Getter
public enum PayableDetailTypeEnum {
TICKET("1", "收票"),
RED_RUSH("2", "红冲"),
;
private final String code;
private final String desc;
PayableDetailTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

View File

@ -0,0 +1,36 @@
package com.ruoyi.sip.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* oms_payable_ticket_plan
*
* @author ruoyi
* @date 2025-12-09
*/
@Data
public class OmsPayableTicketPlan extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 应付单ID */
private Long payableBillId;
/** 计划收票时间 */
private Date planTicketDate;
/** 计划收票金额 */
private BigDecimal planAmount;
/** 计划收票比例 */
private BigDecimal planRate;
private Long detailId;
}

View File

@ -0,0 +1,180 @@
package com.ruoyi.sip.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* oms_ticket_bill
*
* @author ruoyi
* @date 2025-12-09
*/
@Data
public class OmsTicketBill extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 收票单编号 */
@Excel(name = "收票单编号")
private String ticketBillCode;
/** 票据类型 */
@Excel(name = "票据类型")
private String ticketType;
/** 收票单类型 (FROM_PAYABLE, PRE_PAYMENT) */
@Excel(name = "收票单类型 (FROM_PAYABLE, PRE_PAYMENT)")
private String ticketBillType;
/** 制造商开票时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "制造商开票时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date vendorTicketTime;
/** 预计收票时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "预计收票时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date ticketTime;
/** 制造商编码 */
@Excel(name = "制造商编码")
private String vendorCode;
private String vendorName;
/** 含税总价 */
@Excel(name = "含税总价")
private BigDecimal totalPriceWithTax;
private BigDecimal ticketPriceWithTax;
/** 未税总价 */
@Excel(name = "未税总价")
private BigDecimal totalPriceWithoutTax;
private BigDecimal ticketPriceWithoutTax;
/** 税额 */
@Excel(name = "税额")
@Getter(value= AccessLevel.NONE)
private BigDecimal taxAmount;
@Getter(value= AccessLevel.NONE)
private BigDecimal ticketAmount;
private BigDecimal taxRate;
/** 删除标志0代表存在 2代表删除 */
private String delFlag;
/** 实际收票时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "实际收票时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date actualTicketTime;
/** 收票状态 */
@Excel(name = "收票状态")
private String ticketStatus;
/** 审批状态 */
@Excel(name = "审批状态")
private String approveStatus;
/** 审批节点 */
@Excel(name = "审批节点")
private String approveNode;
/** 审批时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Excel(name = "审批时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date approveTime;
/** 退款状态 */
private String refundStatus;
/** 应付单编号(关联查询字段) */
@Excel(name = "应付单编号")
private String payableBillCode;
/** 关联的原始收票单ID */
private Long originalBillId;
private List<PaymentBillPayableDetailDTO> detailList;
public BigDecimal getTaxAmount() {
if (null != totalPriceWithTax && null != totalPriceWithoutTax){
return totalPriceWithTax.subtract(totalPriceWithoutTax);
}
return BigDecimal.ZERO;
}
public BigDecimal getTicketAmount() {
if (null != ticketPriceWithTax && null != ticketPriceWithoutTax){
return ticketPriceWithTax.subtract(ticketPriceWithoutTax);
}
return BigDecimal.ZERO;
}
@Getter
public enum TicketBillTypeEnum {
/** 应付单生成 */
FROM_PAYABLE("FROM_PAYABLE", "应付单生成"),
/** 预付单 */
// PRE_PAYMENT("PRE_PAYMENT", "预付单"),
/** 退款单 */
RED_RUSH("RED_RUSH", "红冲单"),
;
private final String code;
private final String desc;
TicketBillTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
@Getter
public enum TicketStatusEnum {
/** 应付单生成 */
RED_RUSH("-1", "已红冲"),
WAIT_TICKET("1", "未收票"),
/** 预付单 */
TICKET("2", "已收票"),
;
private final String code;
private final String desc;
TicketStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
@Getter
public enum RefundStatusEnum {
/** 应付单生成 */
REFUNDED("RED_RUSH_APPLIED", "已申请红冲"),
WAIT_REFUNDED("WAIT_RED_RUSH", "未红冲")
;
private final String code;
private final String desc;
RefundStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

View File

@ -0,0 +1,15 @@
package com.ruoyi.sip.domain.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class MergedReceiptDataDto {
private String ticketBillType;
private Date ticketTime;
private List<PayableOrderReceiptDto> payableOrders;
private BigDecimal totalMergeTicketAmount;
}

View File

@ -0,0 +1,13 @@
package com.ruoyi.sip.domain.dto;
import com.ruoyi.sip.domain.OmsPayableTicketPlan;
import lombok.Data;
import java.util.List;
@Data
public class PayableOrderReceiptDto {
private Long id;
private String payableBillCode;
private List<OmsPayableTicketPlan> ticketPlans;
}

View File

@ -0,0 +1,97 @@
package com.ruoyi.sip.domain.dto;
import com.ruoyi.sip.domain.OmsPayableTicketDetail;
import java.math.BigDecimal;
import java.util.List;
/**
*
*
* @author ruoyi
* @date 2025-12-09
*/
public class TicketBillDetailDTO {
private String ticketBillCode;
private String ticketType;
private String ticketBillType;
private String vendorCode;
private String vendorName;
private BigDecimal totalPriceWithTax;
private BigDecimal totalPriceWithoutTax;
private BigDecimal taxAmount;
private List<OmsPayableTicketDetail> detailList;
public String getTicketBillCode() {
return ticketBillCode;
}
public void setTicketBillCode(String ticketBillCode) {
this.ticketBillCode = ticketBillCode;
}
public String getTicketType() {
return ticketType;
}
public void setTicketType(String ticketType) {
this.ticketType = ticketType;
}
public String getTicketBillType() {
return ticketBillType;
}
public void setTicketBillType(String ticketBillType) {
this.ticketBillType = ticketBillType;
}
public String getVendorCode() {
return vendorCode;
}
public void setVendorCode(String vendorCode) {
this.vendorCode = vendorCode;
}
public String getVendorName() {
return vendorName;
}
public void setVendorName(String vendorName) {
this.vendorName = vendorName;
}
public BigDecimal getTotalPriceWithTax() {
return totalPriceWithTax;
}
public void setTotalPriceWithTax(BigDecimal totalPriceWithTax) {
this.totalPriceWithTax = totalPriceWithTax;
}
public BigDecimal getTotalPriceWithoutTax() {
return totalPriceWithoutTax;
}
public void setTotalPriceWithoutTax(BigDecimal totalPriceWithoutTax) {
this.totalPriceWithoutTax = totalPriceWithoutTax;
}
public BigDecimal getTaxAmount() {
return taxAmount;
}
public void setTaxAmount(BigDecimal taxAmount) {
this.taxAmount = taxAmount;
}
public List<OmsPayableTicketDetail> getDetailList() {
return detailList;
}
public void setDetailList(List<OmsPayableTicketDetail> detailList) {
this.detailList = detailList;
}
}

View File

@ -0,0 +1,63 @@
package com.ruoyi.sip.domain.dto;
import java.math.BigDecimal;
import java.util.Date;
/**
*
*
* @author ruoyi
* @date 2025-12-09
*/
public class TicketPlanDto {
private Long id;
private Long payableBillId;
private Date planTicketDate;
private BigDecimal planAmount;
private BigDecimal planRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getPayableBillId() {
return payableBillId;
}
public void setPayableBillId(Long payableBillId) {
this.payableBillId = payableBillId;
}
public Date getPlanTicketDate() {
return planTicketDate;
}
public void setPlanTicketDate(Date planTicketDate) {
this.planTicketDate = planTicketDate;
}
public BigDecimal getPlanAmount() {
return planAmount;
}
public void setPlanAmount(BigDecimal planAmount) {
this.planAmount = planAmount;
}
public BigDecimal getPlanRate() {
return planRate;
}
public void setPlanRate(BigDecimal planRate) {
this.planRate = planRate;
}
}

View File

@ -0,0 +1,73 @@
package com.ruoyi.sip.mapper;
import java.util.List;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.domain.OmsPayableTicketDetail;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
/**
* Mapper
*
* @author ruoyi
* @date 2025-12-09
*/
public interface OmsPayableTicketDetailMapper
{
/**
*
*
* @param id
* @return
*/
public OmsPayableTicketDetail selectOmsPayableTicketDetailById(Long id);
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
public List<OmsPayableTicketDetail> selectOmsPayableTicketDetailList(OmsPayableTicketDetail omsPayableTicketDetail);
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
public int insertOmsPayableTicketDetail(OmsPayableTicketDetail omsPayableTicketDetail);
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
public int updateOmsPayableTicketDetail(OmsPayableTicketDetail omsPayableTicketDetail);
/**
*
*
* @param id
* @return
*/
public int deleteOmsPayableTicketDetailById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteOmsPayableTicketDetailByIds(Long[] ids);
List<PaymentBillPayableDetailDTO> listByTickerCode(List<String> codeList);
void insertBatch(List<OmsPayableTicketDetail> addList);
void clearRelationPayable(String ticketBillCode);
List<OmsPayableTicketDetail> listByPayableBillIdList(List<Long> payableBillId);
}

View File

@ -0,0 +1,61 @@
package com.ruoyi.sip.mapper;
import java.util.List;
import com.ruoyi.sip.domain.OmsPayableTicketPlan;
/**
* Mapper
*
* @author ruoyi
* @date 2025-12-09
*/
public interface OmsPayableTicketPlanMapper
{
/**
*
*
* @param id
* @return
*/
public OmsPayableTicketPlan selectOmsPayableTicketPlanById(Long id);
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
public List<OmsPayableTicketPlan> selectOmsPayableTicketPlanList(OmsPayableTicketPlan omsPayableTicketPlan);
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
public int insertOmsPayableTicketPlan(OmsPayableTicketPlan omsPayableTicketPlan);
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
public int updateOmsPayableTicketPlan(OmsPayableTicketPlan omsPayableTicketPlan);
/**
*
*
* @param id
* @return
*/
public int deleteOmsPayableTicketPlanById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteOmsPayableTicketPlanByIds(Long[] ids);
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.sip.mapper;
import java.util.List;
import com.ruoyi.sip.domain.OmsTicketBill;
/**
* Mapper
*
* @author ruoyi
* @date 2025-12-09
*/
public interface OmsTicketBillMapper
{
/**
*
*
* @param id
* @return
*/
public OmsTicketBill selectOmsTicketBillById(Long id);
/**
*
*
* @param omsTicketBill
* @return
*/
public List<OmsTicketBill> selectOmsTicketBillList(OmsTicketBill omsTicketBill);
/**
*
*
* @param omsTicketBill
* @return
*/
public int insertOmsTicketBill(OmsTicketBill omsTicketBill);
/**
*
*
* @param omsTicketBill
* @return
*/
public int updateOmsTicketBill(OmsTicketBill omsTicketBill);
/**
*
*
* @param id
* @return
*/
public int deleteOmsTicketBillById(Long id);
/**
*
*
* @param ids
* @return
*/
public int deleteOmsTicketBillByIds(Long[] ids);
public int selectMaxCodeByPrefix(String codePrefix);
}

View File

@ -0,0 +1,71 @@
package com.ruoyi.sip.service;
import java.util.List;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.domain.OmsPayableTicketDetail;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
/**
* Service
*
* @author ruoyi
* @date 2025-12-09
*/
public interface IOmsPayableTicketDetailService
{
/**
*
*
* @param id
* @return
*/
public OmsPayableTicketDetail selectOmsPayableTicketDetailById(Long id);
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
public List<OmsPayableTicketDetail> selectOmsPayableTicketDetailList(OmsPayableTicketDetail omsPayableTicketDetail);
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
public int insertOmsPayableTicketDetail(OmsPayableTicketDetail omsPayableTicketDetail);
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
public int updateOmsPayableTicketDetail(OmsPayableTicketDetail omsPayableTicketDetail);
/**
*
*
* @param ids
* @return
*/
public int deleteOmsPayableTicketDetailByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteOmsPayableTicketDetailById(Long id);
List<PaymentBillPayableDetailDTO> listByTickerCode(List<String> code);
void applyRefund(String originalId, String newCode);
void clearRelationPayable(String ticketBillCode);
List<OmsPayableTicketDetail> listByPayableBillIdList(List<Long> collect);
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.sip.service;
import java.util.List;
import com.ruoyi.sip.domain.OmsPayableTicketPlan;
/**
* Service
*
* @author ruoyi
* @date 2025-12-09
*/
public interface IOmsPayableTicketPlanService
{
/**
*
*
* @param id
* @return
*/
public List<OmsPayableTicketPlan> listByPayableBillId(Long id);
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
public List<OmsPayableTicketPlan> selectOmsPayableTicketPlanList(OmsPayableTicketPlan omsPayableTicketPlan);
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
public int insertOmsPayableTicketPlan(OmsPayableTicketPlan omsPayableTicketPlan);
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
public int updateOmsPayableTicketPlan(OmsPayableTicketPlan omsPayableTicketPlan);
/**
*
*
* @param ids
* @return
*/
public int deleteOmsPayableTicketPlanByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteOmsPayableTicketPlanById(Long id);
void updateTicketPlan(Long payableBillId, List<OmsPayableTicketPlan> ticketPlanList);
}

View File

@ -0,0 +1,70 @@
package com.ruoyi.sip.service;
import java.util.List;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.sip.domain.OmsTicketBill;
import org.springframework.web.multipart.MultipartFile;
/**
* Service
*
* @author ruoyi
* @date 2025-12-09
*/
public interface IOmsTicketBillService
{
/**
*
*
* @param id
* @return
*/
public OmsTicketBill selectOmsTicketBillById(Long id);
/**
*
*
* @param omsTicketBill
* @return
*/
public List<OmsTicketBill> selectOmsTicketBillList(OmsTicketBill omsTicketBill);
/**
*
*
* @param omsTicketBill
* @return
*/
public int insertOmsTicketBill(OmsTicketBill omsTicketBill);
/**
*
*
* @param omsTicketBill
* @return
*/
public int updateOmsTicketBill(OmsTicketBill omsTicketBill);
/**
*
*
* @param ids
* @return
*/
public int deleteOmsTicketBillByIds(Long[] ids);
/**
*
*
* @param id
* @return
*/
public int deleteOmsTicketBillById(Long id);
AjaxResult uploadReceipt(OmsTicketBill attachment, MultipartFile file) throws Exception;
AjaxResult applyRefund(Long id);
AjaxResult returnTicket(Long id);
}

View File

@ -0,0 +1,155 @@
package com.ruoyi.sip.service.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.OmsPayableTicketDetailMapper;
import com.ruoyi.sip.domain.OmsPayableTicketDetail;
import com.ruoyi.sip.service.IOmsPayableTicketDetailService;
/**
* Service
*
* @author ruoyi
* @date 2025-12-09
*/
@Service
public class OmsPayableTicketDetailServiceImpl implements IOmsPayableTicketDetailService
{
@Autowired
private OmsPayableTicketDetailMapper omsPayableTicketDetailMapper;
/**
*
*
* @param id
* @return
*/
@Override
public OmsPayableTicketDetail selectOmsPayableTicketDetailById(Long id)
{
return omsPayableTicketDetailMapper.selectOmsPayableTicketDetailById(id);
}
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
@Override
public List<OmsPayableTicketDetail> selectOmsPayableTicketDetailList(OmsPayableTicketDetail omsPayableTicketDetail)
{
return omsPayableTicketDetailMapper.selectOmsPayableTicketDetailList(omsPayableTicketDetail);
}
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
@Override
public int insertOmsPayableTicketDetail(OmsPayableTicketDetail omsPayableTicketDetail)
{
omsPayableTicketDetail.setCreateTime(DateUtils.getNowDate());
return omsPayableTicketDetailMapper.insertOmsPayableTicketDetail(omsPayableTicketDetail);
}
/**
*
*
* @param omsPayableTicketDetail
* @return
*/
@Override
public int updateOmsPayableTicketDetail(OmsPayableTicketDetail omsPayableTicketDetail)
{
omsPayableTicketDetail.setUpdateTime(DateUtils.getNowDate());
return omsPayableTicketDetailMapper.updateOmsPayableTicketDetail(omsPayableTicketDetail);
}
/**
*
*
* @param ids
* @return
*/
@Override
public int deleteOmsPayableTicketDetailByIds(Long[] ids)
{
return omsPayableTicketDetailMapper.deleteOmsPayableTicketDetailByIds(ids);
}
/**
*
*
* @param id
* @return
*/
@Override
public int deleteOmsPayableTicketDetailById(Long id)
{
return omsPayableTicketDetailMapper.deleteOmsPayableTicketDetailById(id);
}
@Override
public List<PaymentBillPayableDetailDTO> listByTickerCode(List<String> codeList) {
if (CollUtil.isEmpty(codeList)){
return Collections.emptyList();
}
List<PaymentBillPayableDetailDTO> paymentBillPayableDetailDTOS = omsPayableTicketDetailMapper.listByTickerCode(codeList);
for (PaymentBillPayableDetailDTO paymentBillPayableDetailDTO : paymentBillPayableDetailDTOS) {
paymentBillPayableDetailDTO.setPaymentRate(paymentBillPayableDetailDTO.getPaymentAmount()
.divide(paymentBillPayableDetailDTO.getTotalPriceWithTax(),4,java.math.RoundingMode.HALF_UP)
.multiply(new BigDecimal("100")));
}
return paymentBillPayableDetailDTOS;
}
@Override
public void applyRefund(String originalCode, String newCode) {
OmsPayableTicketDetail query = new OmsPayableTicketDetail();
query.setTicketBillCode(originalCode);
List<OmsPayableTicketDetail> list = omsPayableTicketDetailMapper.selectOmsPayableTicketDetailList(query);
if (CollUtil.isNotEmpty(list)){
List<OmsPayableTicketDetail> addList=new ArrayList<>();
for (OmsPayableTicketDetail detail : list) {
OmsPayableTicketDetail temp = new OmsPayableTicketDetail();
BeanUtil.copyProperties(detail,temp);
temp.setId(null);
temp.setPayableDetailType(OmsPayableTicketDetail.PayableDetailTypeEnum.RED_RUSH.getCode());
temp.setCreateBy(ShiroUtils.getUserId().toString());
temp.setPaymentAmount(detail.getPaymentAmount().negate());
temp.setPaymentRate(detail.getPaymentRate().negate());
temp.setTicketBillCode(newCode);
temp.setRemark("退款");
addList.add( temp);
}
omsPayableTicketDetailMapper.insertBatch(addList);
}
}
@Override
public void clearRelationPayable(String ticketBillCode) {
omsPayableTicketDetailMapper.clearRelationPayable(ticketBillCode);
}
@Override
public List<OmsPayableTicketDetail> listByPayableBillIdList(List<Long> payableBillId) {
return omsPayableTicketDetailMapper.listByPayableBillIdList(payableBillId);
}
}

View File

@ -0,0 +1,152 @@
package com.ruoyi.sip.service.impl;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.domain.OmsPayablePaymentPlan;
import com.ruoyi.sip.domain.OmsPayableTicketDetail;
import com.ruoyi.sip.service.IOmsPayableTicketDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.OmsPayableTicketPlanMapper;
import com.ruoyi.sip.domain.OmsPayableTicketPlan;
import com.ruoyi.sip.service.IOmsPayableTicketPlanService;
/**
* Service
*
* @author ruoyi
* @date 2025-12-09
*/
@Service
public class OmsPayableTicketPlanServiceImpl implements IOmsPayableTicketPlanService {
@Autowired
private OmsPayableTicketPlanMapper omsPayableTicketPlanMapper;
@Autowired
private IOmsPayableTicketDetailService detailService;
/**
*
*
* @param id
* @return
*/
@Override
public List<OmsPayableTicketPlan> listByPayableBillId(Long payableBillId) {
OmsPayableTicketPlan omsPayableTicketPlan = new OmsPayableTicketPlan();
omsPayableTicketPlan.setPayableBillId(payableBillId);
List<OmsPayableTicketPlan> omsPayableTicketPlans = omsPayableTicketPlanMapper.selectOmsPayableTicketPlanList(omsPayableTicketPlan);
if (CollUtil.isNotEmpty(omsPayableTicketPlans)) {
List<OmsPayableTicketDetail> omsPayablePaymentDetails = detailService.listByPayableBillIdList(omsPayableTicketPlans.stream().map(OmsPayableTicketPlan::getPayableBillId).collect(Collectors.toList()));
Map<Long, OmsPayableTicketDetail> detailMap = omsPayablePaymentDetails.stream().collect(Collectors.toMap(OmsPayableTicketDetail::getTicketPlanId, Function.identity(),
(v1, v2) -> v1.getCreateTime().after(v2.getCreateTime()) ? v1 : v2));
for (OmsPayableTicketPlan plan : omsPayableTicketPlans) {
//找到最新的一条数据 如果不是退款 那么不允许再次勾选
OmsPayableTicketDetail detail = detailMap.get(plan.getId());
if (detail != null && !OmsPayableTicketDetail.PayableDetailTypeEnum.RED_RUSH.getCode().equalsIgnoreCase(detail.getPayableDetailType())) {
plan.setDetailId(detail.getId());
}
}
}
return omsPayableTicketPlans;
}
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
@Override
public List<OmsPayableTicketPlan> selectOmsPayableTicketPlanList(OmsPayableTicketPlan omsPayableTicketPlan) {
return omsPayableTicketPlanMapper.selectOmsPayableTicketPlanList(omsPayableTicketPlan);
}
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
@Override
public int insertOmsPayableTicketPlan(OmsPayableTicketPlan omsPayableTicketPlan) {
omsPayableTicketPlan.setCreateTime(DateUtils.getNowDate());
return omsPayableTicketPlanMapper.insertOmsPayableTicketPlan(omsPayableTicketPlan);
}
/**
*
*
* @param omsPayableTicketPlan
* @return
*/
@Override
public int updateOmsPayableTicketPlan(OmsPayableTicketPlan omsPayableTicketPlan) {
omsPayableTicketPlan.setUpdateTime(DateUtils.getNowDate());
return omsPayableTicketPlanMapper.updateOmsPayableTicketPlan(omsPayableTicketPlan);
}
/**
*
*
* @param ids
* @return
*/
@Override
public int deleteOmsPayableTicketPlanByIds(Long[] ids) {
return omsPayableTicketPlanMapper.deleteOmsPayableTicketPlanByIds(ids);
}
/**
*
*
* @param id
* @return
*/
@Override
public int deleteOmsPayableTicketPlanById(Long id) {
return omsPayableTicketPlanMapper.deleteOmsPayableTicketPlanById(id);
}
@Override
public void updateTicketPlan(Long payableBillId, List<OmsPayableTicketPlan> ticketPlanList) {
// 获取数据库中现有付款计划的ID
List<OmsPayableTicketPlan> omsPayableTicketPlans = listByPayableBillId(payableBillId);
Set<Long> existingPlanIdSet = omsPayableTicketPlans.stream().map(OmsPayableTicketPlan::getId).collect(Collectors.toSet());
// 用于存放前端传入的有效非新增或已删除的计划ID
Set<Long> incomingPlanIdSet = ticketPlanList.stream()
.filter(plan -> plan.getId() != null)
.map(OmsPayableTicketPlan::getId)
.collect(Collectors.toSet());
for (OmsPayableTicketPlan plan : ticketPlanList) {
plan.setPayableBillId(payableBillId);
if (plan.getId() == null) {
// 新增付款计划
plan.setCreateBy(ShiroUtils.getLoginName());
omsPayableTicketPlanMapper.insertOmsPayableTicketPlan(plan);
} else if (existingPlanIdSet.contains(plan.getId())) {
// 更新现有付款计划
omsPayableTicketPlanMapper.updateOmsPayableTicketPlan(plan);
}
// 如果plan.getId()不为null但不在existingPlanIdSet中说明是前端新添加但带有id的脏数据或者是非法数据这里不处理
}
// 删除数据库中存在但前端未提交的付款计划
existingPlanIdSet.removeAll(incomingPlanIdSet);
for (Long idToDelete : existingPlanIdSet) {
omsPayableTicketPlanMapper.deleteOmsPayableTicketPlanById(idToDelete);
}
}
}

View File

@ -0,0 +1,264 @@
package com.ruoyi.sip.service.impl;
import java.util.Collections;
import java.util.List;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.ApproveStatusEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.sip.domain.OmsFinAttachment;
import com.ruoyi.sip.domain.OmsPaymentBill;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
import com.ruoyi.sip.service.IOmsFinAttachmentService;
import com.ruoyi.sip.service.IOmsPayableTicketDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.OmsTicketBillMapper;
import com.ruoyi.sip.domain.OmsTicketBill;
import com.ruoyi.sip.service.IOmsTicketBillService;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
/**
* Service
*
* @author ruoyi
* @date 2025-12-09
*/
@Service
@Transactional
public class OmsTicketBillServiceImpl implements IOmsTicketBillService
{
@Autowired
private OmsTicketBillMapper omsTicketBillMapper;
@Autowired
private IOmsPayableTicketDetailService payableTicketDetailService;
@Autowired
private IOmsFinAttachmentService omsFinAttachmentService;
/**
*
*
* @param id
* @return
*/
@Override
public OmsTicketBill selectOmsTicketBillById(Long id)
{
OmsTicketBill omsTicketBill = omsTicketBillMapper.selectOmsTicketBillById(id);
List<PaymentBillPayableDetailDTO> paymentBillPayableDetailDTOS = payableTicketDetailService.listByTickerCode(Collections.singletonList(omsTicketBill.getTicketBillCode()));
omsTicketBill.setDetailList(paymentBillPayableDetailDTOS);
return omsTicketBill;
}
/**
*
*
* @param omsTicketBill
* @return
*/
@Override
public List<OmsTicketBill> selectOmsTicketBillList(OmsTicketBill omsTicketBill)
{
return omsTicketBillMapper.selectOmsTicketBillList(omsTicketBill);
}
/**
*
*
* @param omsTicketBill
* @return
*/
@Override
public int insertOmsTicketBill(OmsTicketBill omsTicketBill)
{
omsTicketBill.setTicketBillCode(generateTicketBillCode());
omsTicketBill.setCreateTime(DateUtils.getNowDate());
return omsTicketBillMapper.insertOmsTicketBill(omsTicketBill);
}
/**
* CS+YYMMdd+
* @return
*/
private String generateTicketBillCode() {
String prefix = "SP";
// 查询当天已有的最大序列号
String codePrefix = prefix + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN);
int maxSequence = omsTicketBillMapper.selectMaxCodeByPrefix(codePrefix);
// 生成新的序列号
int newSequence = maxSequence + 1;
// 序列号补零到4位
String sequenceStr = String.format("%04d", newSequence);
return codePrefix + sequenceStr;
}
/**
*
*
* @param omsTicketBill
* @return
*/
@Override
public int updateOmsTicketBill(OmsTicketBill omsTicketBill)
{
omsTicketBill.setUpdateTime(DateUtils.getNowDate());
return omsTicketBillMapper.updateOmsTicketBill(omsTicketBill);
}
/**
*
*
* @param ids
* @return
*/
@Override
public int deleteOmsTicketBillByIds(Long[] ids)
{
return omsTicketBillMapper.deleteOmsTicketBillByIds(ids);
}
/**
*
*
* @param id
* @return
*/
@Override
public int deleteOmsTicketBillById(Long id)
{
return omsTicketBillMapper.deleteOmsTicketBillById(id);
}
@Override
public AjaxResult uploadReceipt(OmsTicketBill bill, MultipartFile file) throws Exception {
OmsFinAttachment attachment = new OmsFinAttachment();
attachment.setRelatedBillType(OmsFinAttachment.RelatedBillTypeEnum.INVOICE.getCode());
attachment.setRemark(bill.getRemark());
attachment.setRelatedBillId(bill.getId());
OmsTicketBill omsTicketBill = selectOmsTicketBillById(bill.getId());
if (omsTicketBill == null) {
return AjaxResult.error("收票单不存在");
}
if (omsTicketBill.getTotalPriceWithTax().compareTo(bill.getTicketPriceWithTax()) != 0){
return AjaxResult.error("票据金额不一致");
}
if (file.isEmpty())
{
return AjaxResult.error("上传文件不能为空");
}
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
SysUser loginUser = ShiroUtils.getSysUser();
attachment.setFileName(file.getOriginalFilename());
attachment.setFilePath(fileName);
attachment.setFileSize(file.getSize());
attachment.setFileType(file.getContentType());
attachment.setCreateBy(loginUser.getUserId().toString());
omsFinAttachmentService.insertOmsFinAttachment(attachment);
omsTicketBill.setActualTicketTime(DateUtils.getNowDate());
omsTicketBill.setTicketStatus(OmsTicketBill.TicketStatusEnum.TICKET.getCode());
omsTicketBill.setTicketPriceWithTax(bill.getTicketPriceWithTax());
omsTicketBill.setTicketPriceWithoutTax(bill.getTicketPriceWithoutTax());
omsTicketBill.setTicketType(bill.getTicketType());
updateOmsTicketBill(omsTicketBill);
return AjaxResult.success(attachment);
}
@Override
public AjaxResult applyRefund(Long originalId) {
// 1. 验证原始付款单
OmsTicketBill originalBill = selectOmsTicketBillById(originalId);
if (originalBill == null) {
return AjaxResult.error("原始付款单不存在");
}
if (!OmsTicketBill.TicketStatusEnum.TICKET.getCode().equals(originalBill.getTicketStatus())) {
return AjaxResult.error("只有已收票的订单才能申请红冲");
}
if (OmsTicketBill.RefundStatusEnum.REFUNDED.getCode().equals(originalBill.getRefundStatus())) {
return AjaxResult.error("该付款单已申请过红冲,请勿重复操作");
}
// 2. 创建新的退款单
OmsTicketBill refundBill = new OmsTicketBill();
// 复制属性并取反金额
refundBill.setTotalPriceWithTax(originalBill.getTotalPriceWithTax().negate());
refundBill.setTotalPriceWithoutTax(originalBill.getTotalPriceWithoutTax().negate());
refundBill.setTaxAmount(originalBill.getTaxAmount().negate());
refundBill.setTicketTime(originalBill.getTicketTime());
refundBill.setVendorTicketTime(originalBill.getVendorTicketTime());
refundBill.setVendorCode(originalBill.getVendorCode());
refundBill.setTicketBillCode(generateTicketBillCode());
refundBill.setTicketType(originalBill.getTicketType());
// 设置新属性
refundBill.setTicketBillType(OmsTicketBill.TicketBillTypeEnum.RED_RUSH.getCode());
refundBill.setRefundStatus(OmsTicketBill.RefundStatusEnum.REFUNDED.getCode());
refundBill.setApproveStatus(ApproveStatusEnum.WAIT_APPROVE.getCode());
refundBill.setOriginalBillId(originalBill.getId());
refundBill.setActualTicketTime(null);
refundBill.setApproveTime(null);
refundBill.setRemark("退款-关联原付款单:" + originalBill.getTicketBillCode());
insertOmsTicketBill(refundBill);
// 3. 更新原始付款单状态
originalBill.setRefundStatus(OmsTicketBill.RefundStatusEnum.REFUNDED.getCode());
updateOmsTicketBill(originalBill);
//4 创建付款明细
payableTicketDetailService.applyRefund(originalBill.getTicketBillCode(),refundBill.getTicketBillCode());
//5.todo 开始退款审批流程
return AjaxResult.success("退款申请已提交,新的退款单号为:" + refundBill.getTicketBillCode());
}
@Override
public AjaxResult returnTicket(Long id) {
try {
// 1. 验证付款单是否存在
OmsTicketBill ticketBill = selectOmsTicketBillById(id);
if (ticketBill == null) {
return AjaxResult.error("付款单不存在");
}
// 2. 检查付款单类型只有FROM_PAYABLE类型的付款单才能退回
if (!OmsTicketBill.TicketBillTypeEnum.FROM_PAYABLE.getCode().equals(ticketBill.getTicketBillType())) {
return AjaxResult.error("只有由应付单合并生成的收票单才能执行退回操作");
}
// 3. 清楚关联
payableTicketDetailService.clearRelationPayable(ticketBill.getTicketBillCode());
// 6. 删除付款单记录
int result = deleteOmsTicketBillById(id);
if (result <= 0) {
throw new RuntimeException("删除付款单失败");
}
return AjaxResult.success("付款单退回成功!");
} catch (Exception e) {
throw new RuntimeException("退回付款单操作失败:" + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sip.mapper.OmsPayableTicketDetailMapper">
<resultMap type="com.ruoyi.sip.domain.OmsPayableTicketDetail" id="OmsPayableTicketDetailResult">
<result property="id" column="id" />
<result property="ticketPlanId" column="ticket_plan_id" />
<result property="payableBillId" column="payable_bill_id" />
<result property="paymentTime" column="payment_time" />
<result property="paymentAmount" column="payment_amount" />
<result property="paymentRate" column="payment_rate" />
<result property="ticketBillCode" column="ticket_bill_code" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="actualTicketTime" column="actual_ticket_time" />
<result property="ticketStatus" column="ticket_status" />
</resultMap>
<sql id="selectOmsPayableTicketDetailVo">
select id, ticket_plan_id, payable_bill_id, payment_time, payment_amount, payment_rate, ticket_bill_code,
create_by, create_time, update_by, update_time, remark
from oms_payable_ticket_detail
</sql>
<select id="selectOmsPayableTicketDetailList" parameterType="com.ruoyi.sip.domain.OmsPayableTicketDetail" resultMap="OmsPayableTicketDetailResult">
<include refid="selectOmsPayableTicketDetailVo"/>
<where>
<if test="ticketPlanId != null">
and ticket_plan_id = #{ticketPlanId}
</if>
<if test="payableBillId != null">
and payable_bill_id = #{payableBillId}
</if>
<if test="paymentTime != null">
and payment_time = #{paymentTime}
</if>
<if test="paymentAmount != null">
and payment_amount = #{paymentAmount}
</if>
<if test="paymentRate != null">
and payment_rate = #{paymentRate}
</if>
<if test="ticketBillCode != null and ticketBillCode != ''">
and ticket_bill_code = #{ticketBillCode}
</if>
<if test="createBy != null and createBy != ''">
and create_by = #{createBy}
</if>
<if test="createTime != null">
and create_time = #{createTime}
</if>
<if test="updateBy != null and updateBy != ''">
and update_by = #{updateBy}
</if>
<if test="updateTime != null">
and update_time = #{updateTime}
</if>
<if test="remark != null and remark != ''">
and remark like concat('%', #{remark}, '%')
</if>
</where>
</select>
<select id="selectOmsPayableTicketDetailById" parameterType="Long" resultMap="OmsPayableTicketDetailResult">
<include refid="selectOmsPayableTicketDetailVo"/>
where id = #{id}
</select>
<select id="listByTickerCode" resultType="com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO">
select t1.payment_amount, t2.payable_bill_code, t4.project_name, t4.project_code, t2.total_price_with_tax
from (SELECT sum(payment_amount) payment_amount,
payable_bill_id
FROM oms_payable_ticket_detail
WHERE ticket_bill_code in
<foreach item="item" collection="list" separator="," open="(" close=")" index="">
#{item}
</foreach>
group by payable_bill_id) t1
left join oms_payable_bill t2 on t1.payable_bill_id = t2.id
left join project_order_info t3 on t2.order_code = t3.order_code
left join project_info t4 on t3.project_id = t4.id
</select>
<select id="listByPayableBillIdList" resultType="com.ruoyi.sip.domain.OmsPayableTicketDetail">
SELECT
t1.*,t2.actual_ticket_time,t2.ticket_status,t2.id as paymentBillId
FROM
oms_payable_ticket_detail t1
LEFT JOIN oms_ticket_bill t2 ON t1.ticket_bill_code = t2.ticket_bill_code
<where>
<if test="list != null and list.size>0">
and t1.payable_bill_id in
<foreach item="item" collection="list" separator="," open="(" close=")" index="">
#{item}
</foreach>
</if>
</where>
</select>
<insert id="insertOmsPayableTicketDetail" parameterType="com.ruoyi.sip.domain.OmsPayableTicketDetail" useGeneratedKeys="true" keyProperty="id">
insert into oms_payable_ticket_detail
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="ticketPlanId != null">
ticket_plan_id,
</if>
<if test="payableBillId != null">
payable_bill_id,
</if>
<if test="paymentTime != null">
payment_time,
</if>
<if test="paymentAmount != null">
payment_amount,
</if>
<if test="paymentRate != null">
payment_rate,
</if>
<if test="ticketBillCode != null and ticketBillCode != ''">
ticket_bill_code,
</if>
<if test="createBy != null and createBy != ''">
create_by,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateBy != null and updateBy != ''">
update_by,
</if>
<if test="updateTime != null">
update_time,
</if>
<if test="remark != null and remark != ''">
remark,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="ticketPlanId != null">
#{ticketPlanId},
</if>
<if test="payableBillId != null">
#{payableBillId},
</if>
<if test="paymentTime != null">
#{paymentTime},
</if>
<if test="paymentAmount != null">
#{paymentAmount},
</if>
<if test="paymentRate != null">
#{paymentRate},
</if>
<if test="ticketBillCode != null and ticketBillCode != ''">
#{ticketBillCode},
</if>
<if test="createBy != null and createBy != ''">
#{createBy},
</if>
<if test="createTime != null">
#{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
#{updateBy},
</if>
<if test="updateTime != null">
#{updateTime},
</if>
<if test="remark != null and remark != ''">
#{remark},
</if>
</trim>
</insert>
<insert id="insertBatch">
insert into oms_payable_ticket_detail (payable_bill_id, ticket_plan_id, payment_time, payment_amount, payment_rate,
ticket_bill_code, create_by, create_time, update_by, update_time, remark) values
<foreach item="item" collection="list" separator=",">
(#{item.payableBillId}, #{item.ticketPlanId}, #{item.paymentTime}, #{item.paymentAmount}, #{item.paymentRate}, #{item.ticketBillCode},
#{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.remark})
</foreach>
</insert>
<update id="updateOmsPayableTicketDetail" parameterType="com.ruoyi.sip.domain.OmsPayableTicketDetail">
update oms_payable_ticket_detail
<trim prefix="SET" suffixOverrides=",">
<if test="ticketPlanId != null">
ticket_plan_id = #{ticketPlanId},
</if>
<if test="payableBillId != null">
payable_bill_id = #{payableBillId},
</if>
<if test="paymentTime != null">
payment_time = #{paymentTime},
</if>
<if test="paymentAmount != null">
payment_amount = #{paymentAmount},
</if>
<if test="paymentRate != null">
payment_rate = #{paymentRate},
</if>
<if test="ticketBillCode != null and ticketBillCode != ''">
ticket_bill_code = #{ticketBillCode},
</if>
<if test="createBy != null and createBy != ''">
create_by = #{createBy},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
update_by = #{updateBy},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="remark != null and remark != ''">
remark = #{remark},
</if>
</trim>
where id = #{id}
</update>
<delete id="deleteOmsPayableTicketDetailById" parameterType="Long">
delete from oms_payable_ticket_detail where id = #{id}
</delete>
<delete id="deleteOmsPayableTicketDetailByIds" parameterType="String">
delete from oms_payable_ticket_detail where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<delete id="clearRelationPayable">
delete from oms_payable_ticket_detail where ticket_bill_code = #{code}
</delete>
</mapper>

View File

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sip.mapper.OmsPayableTicketPlanMapper">
<resultMap type="com.ruoyi.sip.domain.OmsPayableTicketPlan" id="OmsPayableTicketPlanResult">
<result property="id" column="id" />
<result property="payableBillId" column="payable_bill_id" />
<result property="planTicketDate" column="plan_ticket_date" />
<result property="planAmount" column="plan_amount" />
<result property="planRate" column="plan_rate" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectOmsPayableTicketPlanVo">
select id, payable_bill_id, plan_ticket_date, plan_amount, plan_rate, create_by, create_time, update_by, update_time
from oms_payable_ticket_plan
</sql>
<select id="selectOmsPayableTicketPlanList" parameterType="com.ruoyi.sip.domain.OmsPayableTicketPlan" resultMap="OmsPayableTicketPlanResult">
<include refid="selectOmsPayableTicketPlanVo"/>
<where>
<if test="payableBillId != null">
and payable_bill_id = #{payableBillId}
</if>
<if test="planTicketDate != null">
and plan_ticket_date = #{planTicketDate}
</if>
<if test="planAmount != null">
and plan_amount = #{planAmount}
</if>
<if test="planRate != null">
and plan_rate = #{planRate}
</if>
<if test="createBy != null and createBy != ''">
and create_by = #{createBy}
</if>
<if test="createTime != null">
and create_time = #{createTime}
</if>
<if test="updateBy != null and updateBy != ''">
and update_by = #{updateBy}
</if>
<if test="updateTime != null">
and update_time = #{updateTime}
</if>
</where>
</select>
<select id="selectOmsPayableTicketPlanById" parameterType="Long" resultMap="OmsPayableTicketPlanResult">
<include refid="selectOmsPayableTicketPlanVo"/>
where id = #{id}
</select>
<insert id="insertOmsPayableTicketPlan" parameterType="com.ruoyi.sip.domain.OmsPayableTicketPlan" useGeneratedKeys="true" keyProperty="id">
insert into oms_payable_ticket_plan
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="payableBillId != null">
payable_bill_id,
</if>
<if test="planTicketDate != null">
plan_ticket_date,
</if>
<if test="planAmount != null">
plan_amount,
</if>
<if test="planRate != null">
plan_rate,
</if>
<if test="createBy != null and createBy != ''">
create_by,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateBy != null and updateBy != ''">
update_by,
</if>
<if test="updateTime != null">
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="payableBillId != null">
#{payableBillId},
</if>
<if test="planTicketDate != null">
#{planTicketDate},
</if>
<if test="planAmount != null">
#{planAmount},
</if>
<if test="planRate != null">
#{planRate},
</if>
<if test="createBy != null and createBy != ''">
#{createBy},
</if>
<if test="createTime != null">
#{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
#{updateBy},
</if>
<if test="updateTime != null">
#{updateTime},
</if>
</trim>
</insert>
<update id="updateOmsPayableTicketPlan" parameterType="com.ruoyi.sip.domain.OmsPayableTicketPlan">
update oms_payable_ticket_plan
<trim prefix="SET" suffixOverrides=",">
<if test="payableBillId != null">
payable_bill_id = #{payableBillId},
</if>
<if test="planTicketDate != null">
plan_ticket_date = #{planTicketDate},
</if>
<if test="planAmount != null">
plan_amount = #{planAmount},
</if>
<if test="planRate != null">
plan_rate = #{planRate},
</if>
<if test="createBy != null and createBy != ''">
create_by = #{createBy},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
update_by = #{updateBy},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
</trim>
where id = #{id}
</update>
<delete id="deleteOmsPayableTicketPlanById" parameterType="Long">
delete from oms_payable_ticket_plan where id = #{id}
</delete>
<delete id="deleteOmsPayableTicketPlanByIds" parameterType="String">
delete from oms_payable_ticket_plan where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,337 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sip.mapper.OmsTicketBillMapper">
<resultMap type="com.ruoyi.sip.domain.OmsTicketBill" id="OmsTicketBillResult">
<result property="id" column="id" />
<result property="ticketBillCode" column="ticket_bill_code" />
<result property="ticketType" column="ticket_type" />
<result property="ticketBillType" column="ticket_bill_type" />
<result property="vendorTicketTime" column="vendor_ticket_time" />
<result property="ticketTime" column="ticket_time" />
<result property="vendorCode" column="vendor_code" />
<result property="totalPriceWithTax" column="total_price_with_tax" />
<result property="totalPriceWithoutTax" column="total_price_without_tax" />
<result property="taxRate" column="tax_rate" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="delFlag" column="del_flag" />
<result property="actualTicketTime" column="actual_ticket_time" />
<result property="ticketStatus" column="ticket_status" />
<result property="approveStatus" column="approve_status" />
<result property="approveNode" column="approve_node" />
<result property="approveTime" column="approve_time" />
<result property="refundStatus" column="refund_status" />
</resultMap>
<sql id="selectOmsTicketBillVo">
select t1.id, t1.ticket_bill_code, t1.ticket_type, t1.ticket_bill_type, t1.vendor_ticket_time, t1.ticket_time, t1.vendor_code,
t1.total_price_with_tax, t1.total_price_without_tax, t1.ticket_price_with_tax, t1.ticket_price_without_tax, t1.tax_rate, t1.create_by, t1.create_time, t1.update_by,
t1.update_time, t1.remark, t1.del_flag, t1.actual_ticket_time, t1.ticket_status, t1.approve_status, t1.approve_node, t1.approve_time, t1.refund_status
,t2.vendor_name
from oms_ticket_bill t1
left join oms_vendor_info t2 on t1.vendor_code=t2.vendor_code
</sql>
<select id="selectOmsTicketBillList" parameterType="com.ruoyi.sip.domain.OmsTicketBill" resultMap="OmsTicketBillResult">
<include refid="selectOmsTicketBillVo"/>
<where>
<if test="ticketBillCode != null and ticketBillCode != ''">
and ticket_bill_code = #{ticketBillCode}
</if>
<if test="ticketType != null and ticketType != ''">
and ticket_type = #{ticketType}
</if>
<if test="ticketBillType != null and ticketBillType != ''">
and ticket_bill_type = #{ticketBillType}
</if>
<if test="vendorCode != null and vendorCode != ''">
and vendor_code = #{vendorCode}
</if>
<if test="totalPriceWithTax != null">
and total_price_with_tax = #{totalPriceWithTax}
</if>
<if test="totalPriceWithoutTax != null">
and total_price_without_tax = #{totalPriceWithoutTax}
</if>
<if test="taxRate != null">
and tax_rate = #{taxRate}
</if>
<if test="createBy != null and createBy != ''">
and create_by = #{createBy}
</if>
<if test="createTime != null">
and create_time = #{createTime}
</if>
<if test="updateBy != null and updateBy != ''">
and update_by = #{updateBy}
</if>
<if test="updateTime != null">
and update_time = #{updateTime}
</if>
<if test="remark != null and remark != ''">
and remark like concat('%', #{remark}, '%')
</if>
<if test="delFlag != null and delFlag != ''">
and del_flag = #{delFlag}
</if>
<if test="actualTicketTime != null">
and actual_ticket_time = #{actualTicketTime}
</if>
<if test="ticketStatus != null and ticketStatus != ''">
and ticket_status = #{ticketStatus}
</if>
<if test="approveStatus != null and approveStatus != ''">
and approve_status = #{approveStatus}
</if>
<if test="approveNode != null and approveNode != ''">
and approve_node = #{approveNode}
</if>
<if test="approveTime != null">
and approve_time = #{approveTime}
</if>
<if test="refundStatus != null and refundStatus != ''">
and refund_status = #{refundStatus}
</if>
</where>
</select>
<select id="selectOmsTicketBillById" parameterType="Long" resultMap="OmsTicketBillResult">
<include refid="selectOmsTicketBillVo"/>
where id = #{id}
</select>
<insert id="insertOmsTicketBill" parameterType="com.ruoyi.sip.domain.OmsTicketBill" useGeneratedKeys="true" keyProperty="id">
insert into oms_ticket_bill
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="ticketBillCode != null and ticketBillCode != ''">
ticket_bill_code,
</if>
<if test="ticketType != null and ticketType != ''">
ticket_type,
</if>
<if test="ticketBillType != null and ticketBillType != ''">
ticket_bill_type,
</if>
<if test="vendorTicketTime != null">
vendor_ticket_time,
</if>
<if test="ticketTime != null">
ticket_time,
</if>
<if test="vendorCode != null and vendorCode != ''">
vendor_code,
</if>
<if test="totalPriceWithTax != null">
total_price_with_tax,
</if>
<if test="totalPriceWithoutTax != null">
total_price_without_tax,
</if>
<if test="taxRate != null">
tax_rate,
</if>
<if test="createBy != null and createBy != ''">
create_by,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateBy != null and updateBy != ''">
update_by,
</if>
<if test="updateTime != null">
update_time,
</if>
<if test="remark != null and remark != ''">
remark,
</if>
<if test="delFlag != null and delFlag != ''">
del_flag,
</if>
<if test="actualTicketTime != null">
actual_ticket_time,
</if>
<if test="ticketStatus != null and ticketStatus != ''">
ticket_status,
</if>
<if test="approveStatus != null and approveStatus != ''">
approve_status,
</if>
<if test="approveNode != null and approveNode != ''">
approve_node,
</if>
<if test="approveTime != null">
approve_time,
</if>
<if test="refundStatus != null and refundStatus != ''">
refund_status,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="ticketBillCode != null and ticketBillCode != ''">
#{ticketBillCode},
</if>
<if test="ticketType != null and ticketType != ''">
#{ticketType},
</if>
<if test="ticketBillType != null and ticketBillType != ''">
#{ticketBillType},
</if>
<if test="vendorTicketTime != null">
#{vendorTicketTime},
</if>
<if test="ticketTime != null">
#{ticketTime},
</if>
<if test="vendorCode != null and vendorCode != ''">
#{vendorCode},
</if>
<if test="totalPriceWithTax != null">
#{totalPriceWithTax},
</if>
<if test="totalPriceWithoutTax != null">
#{totalPriceWithoutTax},
</if>
<if test="taxRate != null">
#{taxRate},
</if>
<if test="createBy != null and createBy != ''">
#{createBy},
</if>
<if test="createTime != null">
#{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
#{updateBy},
</if>
<if test="updateTime != null">
#{updateTime},
</if>
<if test="remark != null and remark != ''">
#{remark},
</if>
<if test="delFlag != null and delFlag != ''">
#{delFlag},
</if>
<if test="actualTicketTime != null">
#{actualTicketTime},
</if>
<if test="ticketStatus != null and ticketStatus != ''">
#{ticketStatus},
</if>
<if test="approveStatus != null and approveStatus != ''">
#{approveStatus},
</if>
<if test="approveNode != null and approveNode != ''">
#{approveNode},
</if>
<if test="approveTime != null">
#{approveTime},
</if>
<if test="refundStatus != null and refundStatus != ''">
#{refundStatus},
</if>
</trim>
</insert>
<update id="updateOmsTicketBill" parameterType="com.ruoyi.sip.domain.OmsTicketBill">
update oms_ticket_bill
<trim prefix="SET" suffixOverrides=",">
<if test="ticketBillCode != null and ticketBillCode != ''">
ticket_bill_code = #{ticketBillCode},
</if>
<if test="ticketType != null and ticketType != ''">
ticket_type = #{ticketType},
</if>
<if test="ticketBillType != null and ticketBillType != ''">
ticket_bill_type = #{ticketBillType},
</if>
<if test="vendorTicketTime != null">
vendor_ticket_time = #{vendorTicketTime},
</if>
<if test="ticketTime != null">
ticket_time = #{ticketTime},
</if>
<if test="vendorCode != null and vendorCode != ''">
vendor_code = #{vendorCode},
</if>
<if test="totalPriceWithTax != null">
total_price_with_tax = #{totalPriceWithTax},
</if>
<if test="ticketPriceWithTax != null">
ticket_price_with_tax = #{ticketPriceWithTax},
</if>
<if test="totalPriceWithoutTax != null">
total_price_without_tax = #{totalPriceWithoutTax},
</if>
<if test="ticketPriceWithoutTax != null">
ticket_price_without_tax = #{ticketPriceWithoutTax},
</if>
<if test="taxRate != null">
tax_rate = #{taxRate},
</if>
<if test="createBy != null and createBy != ''">
create_by = #{createBy},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
update_by = #{updateBy},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="remark != null and remark != ''">
remark = #{remark},
</if>
<if test="delFlag != null and delFlag != ''">
del_flag = #{delFlag},
</if>
<if test="actualTicketTime != null">
actual_ticket_time = #{actualTicketTime},
</if>
<if test="ticketStatus != null and ticketStatus != ''">
ticket_status = #{ticketStatus},
</if>
<if test="approveStatus != null and approveStatus != ''">
approve_status = #{approveStatus},
</if>
<if test="approveNode != null and approveNode != ''">
approve_node = #{approveNode},
</if>
<if test="approveTime != null">
approve_time = #{approveTime},
</if>
<if test="refundStatus != null and refundStatus != ''">
refund_status = #{refundStatus},
</if>
</trim>
where id = #{id}
</update>
<delete id="deleteOmsTicketBillById" parameterType="Long">
delete from oms_ticket_bill where id = #{id}
</delete>
<delete id="deleteOmsTicketBillByIds" parameterType="String">
delete from oms_ticket_bill where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectMaxCodeByPrefix" resultType="int">
SELECT COALESCE(MAX(CAST(SUBSTRING(ticket_bill_code, -4) AS UNSIGNED)), 0)
FROM oms_ticket_bill
WHERE ticket_bill_code LIKE CONCAT(#{codePrefix}, '%')
</select>
</mapper>