Files
supplier-dispatch-h5/src/views/index/uploadInvoice.vue
2025-01-13 10:15:28 +08:00

599 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="wrap">
<div class="navBar">
<van-nav-bar
title="上传发票"
left-arrow
left-arrow-color="#FFFFFF"
:border="false"
:fixed="true"
:safe-area-inset-top="true"
@click-left="h5GoBack"
/>
</div>
<div class="contentWrap">
<div class="content_top">
<div class="content_tip">如结算金额确认无误后请开具正规增值税专用发票开票项为施救费救授费拖车费帝引费任选其一如有路桥费产生请开在救援发票中</div>
<div class="top_detail">
<div class="detail_title detail_color">
<div class="title"> <span class="squire"></span> 应开金额</div>
<div class="money">{{ invoiceTotal }}</div>
</div>
<div class="border"></div>
<div class="detail_content" v-for="(item, index) in list" :key="index + '_1'">
<div class="content_item">
{{item.batchCode}}
<span class="content_money">{{item.invoiceMoney}}</span>
</div>
</div>
</div>
</div>
<div class="content_top mt10">
<div class="detail_title detail_color2 pd_no">
<div class="title"> <span class="squire squire2"></span> 实开金额</div>
<div class="money">{{ invoiceMoney || 0 }}</div>
</div>
<div class="border"></div>
<div class="invoice_wrap mt10" v-for="(item, index) in tableData" :key="index" @click="goInvoiceDetail(item)">
<div class="invoice_title">
<div>{{item.accountName}}</div>
<div class="btn_del" @click.stop="deleteHandler(item)">删除</div>
</div>
<div class="border"></div>
<div class="invoice_content">
<div>
<span class="label">发票号码:</span>
<span>{{item.invoiceNumber}}</span>
</div>
<div>
<span class="label">发票金额:</span>
<span class="invoice_money">{{item.invoiceMoney}}</span>
</div>
<div class="invoice_flex">
<div>
<span class="label">开票日期:</span>
<span>{{item.invoiceDate}}</span>
</div>
<van-icon class="icon" name="arrow" />
</div>
</div>
</div>
</div>
</div>
<div class="upload_btn_wrap">
<div class="btn_save" @click="createInvoiceHandler">保存</div>
<van-uploader :before-read="beforeRead" :after-read="afterRead" accept=".png,.jpg,.jpeg,.pdf,.ofd">
<div class="btn_upload">
<img class="icon_upload" src="@/assets/icon_upload.png" alt="">
上传发票
</div>
</van-uploader>
</div>
</div>
</template>
<script>
import { Dialog } from "vant";
import { myMixins } from "@/utils/myMixins"
import { uploadInvoice, getBillingInfo, deleteInvoice, createBatch } from "@/api/mine"
export default {
name: "uploadInvoice",
mixins:[ myMixins ],
data(){
return {
list: [],
invoiceMoneyTotal: 0,
tableData: [],
activeObj: {},
billInfo: {},
supplierId: '',
queryId: '',
batchIds: [],
shouldRate: '',
realRate: '',
invoiceType: '',
invoiceMoney: 0,
afterTaxAmount: 0,
form: {
courierNumber: '',
invoiceType: '', //电子发票还是纸质发票
},
show: true,
}
},
async mounted() {
await this.initData();
},
async activated() {
await this.initData();
this.show = true;
},
watch: {
tableData: {
immediate: true,
handler() {
this.getInvoiceMoney()
}
}
},
computed: {
invoiceTotal () {
let total = 0;
this.list?.map(item => {
total += item.invoiceMoney
})
return total
}
},
deactivated() {
if (this.show) {
this.tableData = [];
}
},
methods: {
async initData() {
this.list = JSON.parse(localStorage.getItem('list'));
this.batchIds = [];
this.list.map(a => {
this.batchIds.push(a.id)
})
console.log('this.batchIds', this.batchIds)
this.shouldRate = this.list[0]?.shouldRate
this.realRate = this.list[0]?.realRate
this.invoiceType = this.list[0]?.invoiceType
this.supplierId = this.list[0]?.supplierId
await this.getInfo();
},
async getInfo() {
if(this.supplierId) {
let res = await getBillingInfo('/supplier/info/billing/getBySupplierId/' + this.supplierId )
this.billInfo = res.data || {}
}
},
async createInvoiceHandler() {
if ( !this.form.invoiceType ) {
this.$toast('发票类型不能为空!')
return
}
if (this.invoiceTotal != this.invoiceMoney) {
this.$toast('“应开金额”与“实开金额”不一致,请核实系统的开票金额与实际开出的发票金额。')
return
}
if(this.form.invoiceType == 2){
Dialog.confirm({
title: '提示',
message: '纸质发票上传后请尽快邮寄,并在系统《开票信息》菜单,录入快递单号,以免影响结算时效!谢谢!',
}).then(async () => {
await this.saveHandle();
}).catch(() => {
});
} else {
await this.saveHandle();
}
},
async saveHandle() {
let paramsArr = this.getParams();
console.log('.......', paramsArr)
try {
this.loading = true;
let res = await createBatch({
supplierId: this.supplierId,
batchIds: this.batchIds,
detailList: paramsArr
})
console.log('res', res)
this.loading = false;
Dialog.alert({
title: '提示',
message: `发票已上传成功,待审核。开票编号为:${res.data},请前往菜单【开票信息】中查看。`,
}).then(() => {
this.h5GoBack()
});
} catch (e) {
console.log('eee', e)
Dialog.alert({
title: '提示',
message: e + '发票提交出错',
}).then(() => {
});
}finally {
this.loading = false;
}
},
getParams() {
let params = [...this.tableData]
if(params.length > 0) {
params.map(item => {
item.courierNumber = (this.form.invoiceType == 2 ? this.form.courierNumber : '')
item.invoiceType = this.form.invoiceType
if( item.taxRate.toString().indexOf('%') != -1 ) {
let tempRate = item.taxRate.replace('%', '');
item.taxRate = (Number(tempRate) / 100)
}
})
}
return params
},
deleteHandler( row ) { // 删除发票
Dialog.confirm({
title: '提示',
message: '确认要删除发票信息吗?',
}).then(async () => {
await deleteInvoice({
id: row?.id || '',
path: row?.path
});
let index = this.tableData.findIndex(a => a.invoiceNumber == row.invoiceNumber)
this.tableData.splice(index, 1)
}).catch(() => {
});
},
beforeRead (file) {
const FileExt = file.name.replace(/.+\./, "");
if (['jpg', 'png', 'jpeg', 'pdf', 'ofd'].indexOf(FileExt.toLowerCase()) === -1) {
this.$toast('请上传后缀名为jpg、jpeg、png、pdf、ofd的文件')
return false;
}
this.$set(this.activeObj, 'fileType', FileExt)
return true
},
async afterRead (file) {
try {
var formData = new FormData(); // 创建form对象
formData.append('file', file.file); // 通过append向form对象添加数据
formData.append('supplierId', this.supplierId) // 如果还需要传替他参数的话
let res = await uploadInvoice(formData)
console.log('rrerer', res)
if( res?.code == 200 ) {
let test = this.tableData.filter(item => item.invoiceNumber == res.data.ocrInvoiceRes.data.invoiceNumber) // 如果
if( test.length > 0 ) { // 发票是否已经上传过
this.$message.warning("该发票已上传,不能重复上传")
return false
} else {
let invoiceDetails = this.getInvoiceRates(res.data.ocrInvoiceRes.data.invoiceDetails)
let flag = this.legalRate(invoiceDetails)
let realFlag = this.realRateCheck(invoiceDetails)
let errorStr = "";
//电子发票验证必须是pdf版本
if((res.data.ocrInvoiceRes.data.invoiceType.indexOf("数电") != -1 || res.data.ocrInvoiceRes.data.invoiceType.indexOf("电子") != -1) && this.activeObj.fileType != 'pdf'){
errorStr = errorStr + "电票请上传PDF格式的文件;"
}
if(res.data.ocrInvoiceRes.data.invoiceType.indexOf("专用发票") == -1 && this.invoiceType == 1){
errorStr = errorStr + "上传发票类型与服务商配置发票类型不一致,设置类型为:专票;";
}
if(!this.billInfo.unitName ){
errorStr = errorStr + "未设置抬头,请联系区域经理;";
}
if(!this.billInfo.dutyParagraph ){
errorStr = errorStr + "未设置纳税人识别号,请联系区域经理;";
}
let ocrName = res.data.ocrInvoiceRes.data.sellerName.replace('(','').replace('','').replace(')','').replace('','');
let billName = ''
if (this.billInfo && this.billInfo.unitName) {
billName = this.billInfo.unitName.replace('(','').replace('','').replace(')','').replace('','');
}
let diffNumber = this.charDifference(ocrName,billName);
if(ocrName != billName && diffNumber > 2 && !res.data.ocrInvoiceRes.data.sellerName.startsWith("国家税务总局")){
errorStr = errorStr + "上传发票抬头与服务商配置不一致,设置抬头为:" + (this.billInfo.unitName || '未设置') + ",识别抬头为:" + res.data.ocrInvoiceRes.data.sellerName + ";如发票确认无误请联系结算部021-53682525";
}
let conList = res.data.ocrInvoiceRes.data.invoiceDetails.filter(a => a.itemName.includes("*"))
if(!res.data.ocrInvoiceRes.data.sellerName.startsWith("国家税务总局") &&
conList.every(a => {
let items = a.itemName.split("*")
return (!items[1].includes("现代服务") && !items[1].includes("运输服务")) || (!items[2].includes("拖车") && !items[2].includes("救援") && !items[2].includes("施救") && !items[2].includes("清障") && !items[2].includes("道路救援"))
})
){
errorStr = errorStr + "分类必须包含:现代服务/运输服务,服务名称必须包含:拖车/救援/施救/清障/道路救援 ;例如《*现代服务*拖车费》";
}
if( !flag ) {
Dialog.alert({
title: '提示',
message: '您上传的发票税率不符合要求',
}).then(() => {
this.uploadLoading=false;
return false
});
}else if(!realFlag){
Dialog.alert({
title: '提示',
message: "您上传的发票税率与服务商配置税率不一致,设置税率为:" + this.realRate + "%"
}).then(() => {
this.uploadLoading=false;
return false
});
}else if(errorStr && errorStr.length > 0){
Dialog.alert({
title: '提示',
message: errorStr,
}).then(() => {
this.uploadLoading=false;
return false
});
} else {
console.log('res', res)
console.log('file', file)
this.form.invoiceType = res.data.ocrInvoiceRes.data.invoiceType.indexOf("数电") == -1 && res.data.ocrInvoiceRes.data.invoiceType.indexOf("电子") == -1 ? 2 : 1
let isOrdinaryInvoice = res.data.ocrInvoiceRes.data.invoiceType.indexOf("普通") > -1
this.$set(this.activeObj, 'imageUrl', '')
this.$set(this.activeObj, 'fileName', '')
if (['pdf','ofd'].indexOf(this.activeObj.fileType) === -1) {
// this.activeObj.imageUrl = URL.createObjectURL(file.raw);
this.activeObj.imageUrl = res.data.url
this.$set(this.activeObj, 'fileName', file.file.name)
} else {
this.$set(this.activeObj, 'fileName', file.file.name)
}
this.$set(this.activeObj, 'path', res.data.url)
this.$set(this.activeObj, 'invoiceNumber', res.data.ocrInvoiceRes.data.invoiceNumber)
this.$set(this.activeObj, 'accountName', res.data.ocrInvoiceRes.data.sellerName)
this.$set(this.activeObj, 'invoiceMoney', res.data.ocrInvoiceRes.data.totalAmount)
this.$set(this.activeObj, 'taxRate',isOrdinaryInvoice?0: res.data.ocrInvoiceRes.data.invoiceDetails[0].taxRate)
this.$set(this.activeObj, 'taxAmount',isOrdinaryInvoice?0: res.data.ocrInvoiceRes.data.invoiceTax)
this.$set(this.activeObj, 'afterTaxAmount',isOrdinaryInvoice?res.data.ocrInvoiceRes.data.totalAmount: res.data.ocrInvoiceRes.data.invoiceAmountPreTax)
this.$set(this.activeObj, 'invoiceDate', res.data.ocrInvoiceRes.data.invoiceDate)
this.$set(this.activeObj, 'invoiceCode', res.data.ocrInvoiceRes.data.invoiceCode)
this.$set(this.activeObj, 'invoiceTypeCodeString', res.data.ocrInvoiceRes.data.invoiceType)
this.$set(this.activeObj, 'payName', res.data.ocrInvoiceRes.data.purchaserName)
this.tableData.push({...this.activeObj})
this.$forceUpdate()
}
}
}
} catch (e) {
console.log('eeee', e)
}
},
goInvoiceDetail (item) {
localStorage.setItem('invoiceDetail', JSON.stringify(item))
this.show = false;
this.goPage('showInvoice')
},
getInvoiceMoney() {
let invoiceMoney = 0;
let afterTaxAmount = 0;
if( this.tableData.length > 0){
this.tableData.map(item => {
if( item.invoiceMoney && item.afterTaxAmount ) {
invoiceMoney += (Number(item.invoiceMoney) * 100)
afterTaxAmount += ( Number(item.afterTaxAmount) * 100 )
}
})
this.invoiceMoney = invoiceMoney != 0 ? (invoiceMoney / 100) : 0
this.afterTaxAmount = afterTaxAmount != 0 ? ( afterTaxAmount / 100 ) : 0
} else {
this.invoiceMoney = invoiceMoney
this.afterTaxAmount = afterTaxAmount
}
},
charDifference(str1,str2) {
let difference = 0;
// 遍历字符串1的每个字符
for (let i = 0; i < str1.length; i++) {
// 如果字符串2中不包含当前字符或者位置不对则误差加1
if (str2.indexOf(str1[i]) === -1 || str2[i] !== str1[i]) {
difference++;
}
}
//如果前面部分都对得上,但是长度不同,则差异是长度
if(str1.length != str2.length){
difference += Math.abs(str1.length - str2.length)
}
// 同样遍历字符串2处理字符串1中不存在的字符
/*for (let i = 0; i < str2.length; i++) {
if (str1.indexOf(str2[i]) === -1) {
difference++;
}
}*/
return difference;
},
legalRate(rateList) { // 合法的 税率
let rate = rateList[0]
let flag = rateList.every(item => {
//设置的是普票,不校验税率
if(this.invoiceType == 2 && item != '免税' && item != '0%'){
return true
}
return item == rate && ['1%', '3%', '6%', '9%', '13%'].includes(item)
})
console.log('flag', flag)
return flag
},
realRateCheck(rateList) { // 和设置的税率是否符合
let rate = rateList[0]
let flag = rateList.every(item => {
//设置的是普票,不校验税率
if(this.invoiceType == 2 && item != '免税' && item != '0%'){
return true
}
return item == rate && this.realRate + '%' == item
})
console.log('flag', flag)
return flag
},
getInvoiceRates(invoiceDetails) { // 获取发票详情的税率数组
let rates = [];
invoiceDetails.map(item => {
rates.push(item.taxRate)
})
return rates
},
}
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap{
@include wh(100%,100%);
background-color: #F4F5F7;
overflow-y: auto;
}
.navBar{
height: 46px;
}
.mt10 {
margin-top: 10px;
}
.contentWrap {
padding-bottom: 70px;
.content_top {
padding: 15px 20px;
background-color: #fff;
.content_tip {
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
}
.top_detail {
.detail_content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 10px 0;
.content_item {
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
line-height: 27px;
.content_money {
font-size: 15px;
color: #D48139;
font-weight: bold;
}
}
}
}
.detail_title {
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
padding: 10px 0;
}
.title {
display: flex;
align-items: center;
}
.money {
font-size: 22px;
}
.detail_color {
color: #D48139;
}
.detail_color2 {
color: #FF5D2E;
}
.pd_no {
padding-top: 0;
}
}
}
.squire {
width: 6px;
height: 6px;
display: inline-block;
background-color: #D48139;
border-radius: 2px;
margin-right: 10px;
}
.squire2 {
background-color: #FF5D2E;
}
.border {
width: 100%;
height: 0;
border-bottom: 1px solid;
opacity: 0.16;
border-image: linear-gradient(270deg, rgba(217, 217, 217, 0.6), rgba(178, 178, 178, 1), rgba(178, 178, 178, 1), rgba(217, 217, 217, 0.6)) 1 1;
}
.invoice_wrap {
background-color: #F4FAFF;
padding: 11px 3px 15px 18px;
border-radius: 4px;
border: 1px solid #EBF2F7;
.invoice_title {
font-size: 14px;
font-weight: bold;
color: rgba(50, 54, 67, 0.76);
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.btn_del {
width: 48px;
height: 18px;
margin-right: 12px;
line-height: 18px;
text-align: center;
border-radius: 3px;
border: 1px solid #DDDDDD;
font-size: 12px;
color: rgba(0, 0, 0, 0.7);
font-weight: normal;
}
}
.invoice_content {
padding-top: 10px;
padding-right: 12px;
color: #000;
font-size: 12px;
line-height: 24px;
.label {
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
margin-right: 10px;
}
.invoice_money {
color: #FF5D2E;
font-size: 15px;
font-weight: bold;
}
}
}
.upload_btn_wrap {
position: fixed;
width: 100%;
box-sizing: border-box;
bottom: 0;
left: 0;
padding: 15px 30px;
display: flex;
color: #fff;
font-weight: bold;
font-size: 14px;
line-height: 40px;
background: #FBFBFB;
box-shadow: 0px -1px 20px 4px rgba(98,107,128,0.2);
div {
height: 40px;
border-radius: 5px;
text-align: center;
flex: 1;
}
.btn_save {
background: #354D93;
margin-right: 4px;
}
.btn_upload {
background: #354D93;
margin-left: 4px;
}
.icon_upload {
width: 13px;
height: 13px;
}
}
.invoice_flex {
display: flex;
align-items: center;
justify-content: space-between;
}
::v-deep .van-uploader__input-wrapper {
width: 100% !important;
}
</style>