| | |
| | | </el-col> |
| | | <el-col :span="5"> |
| | | <el-form-item label="申请日期" prop="applyTime"> |
| | | <el-date-picker clearable :disabled="true" style="width: 100%" v-model="form.applyTime" type="date" |
| | | <el-date-picker clearable :disabled="true" style="width: 100%" v-model="form.applyTime" type="date" format="yyyy-MM-dd" |
| | | value-format="yyyy-MM-dd HH:mm:ss" placeholder="日 期"> |
| | | </el-date-picker> |
| | | </el-form-item> |
| | |
| | | <el-form-item label="捐献案例" prop="name"> |
| | | <el-input :disabled="true" v-model="form.donorname" placeholder="请输入捐献者姓名" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <div style="margin-left: 10px;"> |
| | | <dict-tag |
| | | :options="dict.type.sys_DonationStatus" |
| | | :value="form.invoicecount" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="10"> |
| | | <el-form-item label="申请说明" prop="remark"> |
| | |
| | | v-if="businessType == '1' || businessType == '2'"> |
| | | <template slot-scope="scope"> |
| | | <el-form-item label-width="0"> |
| | | <el-input :disabled="userprofile.nickName != '陈慕华'" v-model.number="scope.row.idcardno" |
| | | @input="tableParValueChange1(scope.row)" placeholder="身份证号" /> |
| | | <el-input :disabled="userprofile.nickName != '陈慕华' || scope.row.isunit == 1" |
| | | :placeholder="scope.row.isunit == 1 ? '单位无需填写' : '身份证号'" |
| | | v-model.number="scope.row.idcardno" @input="tableParValueChange1(scope.row)" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </el-dialog> |
| | | |
| | | <!-- 批量选取 --> |
| | | <el-dialog title="批量生成明细信息" :visible.sync="batchdialogVisible" width="30%"> |
| | | <el-dialog title="批量生成明细信息" :visible.sync="batchdialogVisible" width="35%"> |
| | | <div style="margin-bottom: 12px"> |
| | | <el-input v-model="personKeyword" clearable size="small" prefix-icon="el-icon-search" |
| | | placeholder="输入 姓名 / 单位 / 工号 / 身份证后4位 筛查" style="width: 330px" /> |
| | | <span style="margin-left: 10px; color: #909399; font-size: 12px">共 {{ filteredCityOptions.length }} 人</span> |
| | | </div> |
| | | <div style="max-height: 300px; overflow-y: auto"> |
| | | <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox> |
| | | <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选(当前筛查结果)</el-checkbox> |
| | | <div style="margin: 15px 0;"></div> |
| | | <el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange"> |
| | | <el-checkbox border v-for="city in cityOptions" :label="city" :key="city.reportNo">{{ city.reportName |
| | | }}</el-checkbox> |
| | | <el-checkbox border v-for="city in filteredCityOptions" :label="city" |
| | | :key="city.reportNo + '_' + city.reportName">{{ city.reportName |
| | | }}{{ city.unitname ? " · " + city.unitname : "" }}</el-checkbox> |
| | | </el-checkbox-group> |
| | | </div> |
| | | |
| | |
| | | getdownloadBX, |
| | | addorupdateFund, |
| | | ManualCalculation, |
| | | getdetailsByItemId |
| | | getdetailsByItemId, |
| | | validateExpertFee, |
| | | listbypower |
| | | } from "@/api/project/fund"; |
| | | |
| | | import { |
| | |
| | | import Li_area_select from "@/components/Address"; |
| | | import OrgSelecter from "@/views/project/components/orgselect"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { moneySum, moneyMul, moneySub, moneyRound } from "@/utils/money"; |
| | | import { checkFileName, checkDetails } from "@/utils/fileNameCheck"; |
| | | import CustomImageViewer from "@/components/CustomImageViewer"; // 根据你的路径调整 |
| | | |
| | | export default { |
| | |
| | | //"sys_finsubject", |
| | | //"sys_financeitemtype", |
| | | //"sys_expensetype", |
| | | 'sys_DonationStatus', |
| | | "sys_recordstatus", |
| | | "sys_professionaltitle" |
| | | ], |
| | | |
| | | data() { |
| | | data() { |
| | | // 自定义身份证号校验器 |
| | | const validateIdCard = (rule, value, callback) => { |
| | | // 1. 如果为空,交给 required 规则去处理,这里直接放行 |
| | |
| | | return callback(); |
| | | } |
| | | const regex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}([0-9]|X)$/; |
| | | if (regex.test(value)) { |
| | | } else { |
| | | callback(new Error("身份证号码格式错误")); |
| | | // 2026-10-19:格式不合法立即早退。原来写成 if/else 但 else 里没有 return, |
| | | // 会继续往下走异步查重,导致 callback 被调用两次、"身份证号码格式错误"被后续结果覆盖。 |
| | | if (!regex.test(value)) { |
| | | return callback(new Error("身份证号码格式错误")); |
| | | } |
| | | |
| | | // 2. 调用后端接口进行重复性校验 |
| | |
| | | // 网络请求头 |
| | | Networkheader: null, |
| | | batchdialogVisible: false, |
| | | //批量录入弹框的筛查关键字 |
| | | personKeyword: "", |
| | | itemlist: [], |
| | | cityOptions: [], |
| | | checkAll: false, |
| | |
| | | }, |
| | | |
| | | dialogrules: { |
| | | // 2026-10-19:账户名称(账户来源=从账户库 时的下拉)也是必输项 |
| | | accountname: [ |
| | | { required: true, message: "请选择账户名称", trigger: "change" } |
| | | ], |
| | | usertype: [ |
| | | { required: true, message: "请输入账户类型", trigger: "blur" } |
| | | ], |
| | |
| | | ], |
| | | unitTaxNo: [{ required: true, message: "请输入税号", trigger: "blur" }], |
| | | idcardno: [ |
| | | { required: true, message: '请输入身份证号', trigger: 'blur' }, |
| | | { |
| | | // 2026-10-19:加 required 标记只为让 el-form-item 标出必输星号(Element 的 isRequired 只认 rule.required)。 |
| | | // 本规则带 validator,async-validator 的 getValidationMethod 会直接返回该 validator、跳过内建 required 检查, |
| | | // 所以校验行为与改动前完全一致;另外**不能加 message**,否则会覆盖下面自定义 validator 的报错文案。 |
| | | required: true, |
| | | // 账户类型选择"单位机构"时不需要身份证号 |
| | | validator: (rule, value, callback) => { |
| | | if (this.hasUserType(this.accountselectform.usertype, "org")) { |
| | | return callback(); |
| | | } |
| | | if (!value || !String(value).trim()) { |
| | | return callback(new Error("请输入身份证号")); |
| | | } |
| | | return callback(); |
| | | }, |
| | | trigger: "blur" |
| | | }, |
| | | // 添加自定义校验规则 |
| | | { validator: validateIdCard, trigger: 'blur' } |
| | | ], |
| | |
| | | flowconclusion: null, |
| | | fundid: null |
| | | }, |
| | | // 连续审批:待审批单据列表 |
| | | pendingList: [], |
| | | |
| | | // 查询参数 |
| | | queryParams: { |
| | |
| | | }, |
| | | |
| | | //监听属性 类似于data概念 |
| | | computed: {}, |
| | | computed: { |
| | | //批量录入弹框:按关键字(姓名/单位/工号/身份证后4位)筛查后的候选人 |
| | | filteredCityOptions() { |
| | | const kw = (this.personKeyword || "").trim().toLowerCase(); |
| | | if (!kw) { |
| | | return this.cityOptions; |
| | | } |
| | | return this.cityOptions.filter(item => this.personMatchKeyword(item, kw)); |
| | | } |
| | | }, |
| | | //监控data中的数据变化 |
| | | watch: {}, |
| | | //方法集合 |
| | |
| | | this.businessType == "2" || |
| | | this.businessType == "3" |
| | | ) { |
| | | console.log(this.curCase,'this.curCase'); |
| | | |
| | | this.form.infoid = this.curCase.id; |
| | | this.form.donorname = this.curCase.name; |
| | | this.form.donorno = this.curCase.donorno; |
| | | this.form.invoicecount = this.curCase.recordstate; |
| | | } |
| | | |
| | | //初始化明细 |
| | |
| | | handleCheck() { |
| | | this.isopen = 0; |
| | | this.reset(); |
| | | // 重置审批信息,避免上一单的审批结果带入下一单 |
| | | this.checkObj = { |
| | | flowlevelone: null, |
| | | flowconclusion: null, |
| | | fundid: null |
| | | }; |
| | | // 连续审批:加载当前业务类型下的待审批列表 |
| | | this.getPendingList(); |
| | | |
| | | getFund(this.curId).then(response => { |
| | | this.form = response.data; |
| | |
| | | }, |
| | | /** 提交按钮 */ |
| | | submitForm: debounce(function () { |
| | | // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行) |
| | | if (!this.checkDetailFileNames()) { |
| | | return; |
| | | } |
| | | |
| | | if (!this.randomnumber) { |
| | | this.randomnumber = this.generateRandomString(9); |
| | | this.form.randomnumber = this.randomnumber; |
| | |
| | | this.$refs["form"].validate(valid => { |
| | | if (valid) { |
| | | this.formData = this.form; |
| | | |
| | | // 专家劳务费专用:9 条费用控制规则校验(校验不通过不允许保存) |
| | | if (this.businessType == "1") { |
| | | this.$modal.loading("正在校验专家劳务费用,请稍候!"); |
| | | this.checkExpertFeeRules("保存").then((allowed) => { |
| | | this.$modal.closeLoading(); |
| | | if (allowed) { |
| | | this.doSubmitForm(); |
| | | } |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | this.doSubmitForm(); |
| | | } |
| | | }); |
| | | }, 500), |
| | | |
| | | /** |
| | | * 保存申请单的实际落库逻辑。 |
| | | * |
| | | * 从 submitForm 中拆出,便于在专家劳务费规则校验通过后再执行; |
| | | * 其他业务类型由 submitForm 直接调用,行为与拆分前一致。 |
| | | */ |
| | | doSubmitForm() { |
| | | |
| | | //保存前校验数据 |
| | | for (let k = 0; k < this.rbDetails.length; k++) { |
| | |
| | | let tempDetail = this.rbDetails[j]; |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.amount)) || |
| | | parseFloat(tempDetail.amount) == 0 |
| | | moneyRound(tempDetail.amount) == 0 |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | |
| | | let tempDetail = this.rbDetails[j]; |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.amount)) || |
| | | parseFloat(tempDetail.amount) == 0 |
| | | moneyRound(tempDetail.amount) == 0 |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | |
| | | } |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.taxamount)) || |
| | | parseFloat(tempDetail.taxamount) == 0 |
| | | moneyRound(tempDetail.taxamount) == 0 |
| | | ) { |
| | | tempDetail.taxedamount = parseFloat(tempDetail.amount); |
| | | } else { |
| | |
| | | //关闭窗口 |
| | | this.loading = false; |
| | | // this.getfundList(); |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * 专家劳务费 9 条费用控制规则校验(提交前拦截)。 |
| | | * |
| | | * 适用范围:businessType === '1'(专家劳务费申请单),与后端 applytype === '1' 对应。 |
| | | * 其他业务类型(伦理专家费、医学成本、办公费用)不校验,直接放行。 |
| | | * |
| | | * 校验口径(同一捐献案例 infoid 下累计)由后端统一实现: |
| | | * errors 阻断型:提示违反的具体规则,不允许继续保存/审核 |
| | | * warnings 提示型:仅提醒经办人处理(规则 5、6),需二次确认后才继续 |
| | | * |
| | | * @param {string} action 当前动作,用于拼接提示文案(如“保存”“提交审核”) |
| | | * @returns {Promise<boolean>} true 允许继续,false 阻止继续 |
| | | */ |
| | | checkExpertFeeRules(action) { |
| | | if (this.businessType != "1") { |
| | | return Promise.resolve(true); |
| | | } |
| | | // 后端按「案例内已有单据 + 本次提交明细」合并计算,确保尚未落库的明细也参与校验 |
| | | const payload = Object.assign({}, this.form, { |
| | | applytype: this.form.applytype || "1", |
| | | serviceFunddetails: this.rbDetails, |
| | | }); |
| | | }, 500), |
| | | // 排查用:能直接从控制台看到到底送了什么,以及后端判定了什么 |
| | | console.log("[专家费校验] 提交校验", action, { |
| | | businessType: this.businessType, |
| | | applytype: payload.applytype, |
| | | infoid: payload.infoid, |
| | | fundId: payload.id, |
| | | 明细: (this.rbDetails || []).map((d) => ({ |
| | | itemid: d.itemid, |
| | | amount: d.amount, |
| | | taxedamount: d.taxedamount, |
| | | 场景: d.servicesscopename, |
| | | })), |
| | | }); |
| | | |
| | | return validateExpertFee(payload) |
| | | .then((res) => { |
| | | const errors = (res && res.errors) || []; |
| | | const warnings = (res && res.warnings) || []; |
| | | console.log("[专家费校验] 后端返回", { errors, warnings }); |
| | | |
| | | // 1) 阻断型:列出违反的具体规则,不允许继续 |
| | | // 用弹窗而非轻提示:错误可能有多条,轻提示会挤成一行且很快消失 |
| | | if (errors.length > 0) { |
| | | this.$modal.alertError( |
| | | "专家劳务费用不符合以下规定,无法" + |
| | | action + |
| | | ":\n\n" + |
| | | errors.map((msg, i) => i + 1 + ". " + msg).join("\n\n") |
| | | ); |
| | | return false; |
| | | } |
| | | |
| | | // 2) 提示型:需经办人确认后才能继续(当前规则 5、6 已改为硬拦截,此分支保留备用) |
| | | if (warnings.length > 0) { |
| | | const confirmText = |
| | | "专家劳务费用存在以下问题,请确认是否继续" + |
| | | action + |
| | | "?\n\n" + |
| | | warnings.map((msg, i) => i + 1 + ". " + msg).join("\n\n"); |
| | | return this.$modal |
| | | .confirm(confirmText) |
| | | .then(() => true) |
| | | .catch(() => false); |
| | | } |
| | | |
| | | return true; |
| | | }) |
| | | .catch((err) => { |
| | | // 接口异常时按“不阻断”处理,避免校验服务不可用导致完全无法报销; |
| | | // 后端 addOrUpdateNew / checkFund 仍有同样的硬拦截兜底。 |
| | | console.error("专家劳务费规则校验接口调用失败", err); |
| | | return true; |
| | | }); |
| | | }, |
| | | |
| | | checksubmit() { |
| | | //检查是否收到纸质材料 |
| | |
| | | return; |
| | | } |
| | | |
| | | this.Savenow(); |
| | | console.log("保存并审批"); |
| | | // 专家劳务费专用:9 条费用控制规则校验(仅审核通过时拦截) |
| | | const needExpertFeeCheck = this.checkObj.flowlevelone == 1; |
| | | const expertFeePromise = needExpertFeeCheck |
| | | ? this.checkExpertFeeRules("提交审核") |
| | | : Promise.resolve(true); |
| | | |
| | | expertFeePromise.then((allowed) => { |
| | | if (!allowed) { |
| | | return; |
| | | } |
| | | this.Savenow(); |
| | | console.log("保存并审批"); |
| | | }); |
| | | }, |
| | | |
| | | // 连续审批:获取当前业务类型下的待审批列表 |
| | | getPendingList() { |
| | | return listbypower({ |
| | | pageNum: 1, |
| | | pageSize: 1000, |
| | | CHECKFLAG: 1, |
| | | APPLYTYPE: this.businessType |
| | | }) |
| | | .then(res => { |
| | | this.pendingList = res.rows || []; |
| | | }) |
| | | .catch(() => { |
| | | this.pendingList = []; |
| | | }); |
| | | }, |
| | | |
| | | // 连续审批:审批完成后询问是否继续审批下一单 |
| | | continueOrExit() { |
| | | // 重新拉取待审批列表(当前单据已审批完成被移出列表) |
| | | this.getPendingList().then(() => { |
| | | const list = this.pendingList || []; |
| | | const next = list[0] || null; |
| | | if (next) { |
| | | this.$confirm("审批已完成,是否继续审批下一张待审批单据?", "提示", { |
| | | confirmButtonText: "继续审批", |
| | | cancelButtonText: "返回列表", |
| | | type: "info" |
| | | }) |
| | | .then(() => { |
| | | // 在当前页面直接加载下一张待审批单据 |
| | | this.curId = next.id; |
| | | this.handleCheck(); |
| | | // 同步更新地址栏,避免刷新后回到上一单 |
| | | this.$router |
| | | .replace({ |
| | | path: this.$route.path, |
| | | query: { |
| | | id: next.id, |
| | | businessType: this.businessType, |
| | | operationType: "check" |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | }) |
| | | .catch(() => { |
| | | this.exitToPage(); |
| | | }); |
| | | } else { |
| | | this.$modal.msgSuccess("已无待审批单据,返回列表"); |
| | | this.exitToPage(); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | // 返回列表页 |
| | | exitToPage() { |
| | | this.$store.dispatch("tagsView/delView", this.$route); |
| | | this.$router.go(-1); |
| | | }, |
| | | |
| | | // 取消按钮 |
| | |
| | | }); |
| | | }, |
| | | |
| | | /** 人员类型(多值,逗号分隔)是否包含指定类型;org = 单位机构 */ |
| | | hasUserType(usertype, code) { |
| | | if (!usertype) { |
| | | return false; |
| | | } |
| | | return ( |
| | | String(usertype) |
| | | .split(",") |
| | | .map(t => t.trim()) |
| | | .indexOf(code) > -1 |
| | | ); |
| | | }, |
| | | /** 受益人/账户是否为"单位"(人员类型含 org) */ |
| | | //获取专家、单位机构的账户列表 |
| | | getAccountList() { |
| | | listExternalperson().then(res => { |
| | |
| | | for (let j = 0; j < res.rows.length; j++) { |
| | | //单位账号列表 |
| | | if (res.rows[j].userstatus == "1") { |
| | | if (res.rows[j].usertype == "org") { |
| | | if (this.hasUserType(res.rows[j].usertype, "org")) { |
| | | this.accountlist.push({ |
| | | id: res.rows[j].id, |
| | | userno: res.rows[j].userno, |
| | |
| | | bankcardno: res.rows[j].bankcardno, |
| | | unitno: res.rows[j].unitno, |
| | | unitname: res.rows[j].unitname, |
| | | usertype: res.rows[j].usertype, |
| | | userid: |
| | | res.rows[j].username + "(" + res.rows[j].bankcardno + ")" |
| | | }); |
| | |
| | | bankcardno: res.rows[j].bankcardno, |
| | | unitno: res.rows[j].unitno, |
| | | unitname: res.rows[j].unitname, |
| | | usertype: res.rows[j].usertype, |
| | | userid: |
| | | res.rows[j].username + "(" + res.rows[j].idcardno + ")" |
| | | }); |
| | |
| | | |
| | | //计算个税:type(A:已知税前金额,计算税后金额;B:已知税后金额,计算税前金额) |
| | | calculateTax(row, type) { |
| | | let targetValue = 0; |
| | | for (let i = 0; i < this.rbDetails.length; i++) { |
| | | if (!this.rbDetails[i].amount) { |
| | | this.rbDetails[i].amount = 0; |
| | | } |
| | | targetValue += parseFloat(this.rbDetails[i].amount); // 假设每个对象中的特定值存储在'specificValue'属性下 |
| | | } |
| | | this.form.pretaxcost = targetValue; |
| | | // 合计金额必须精确累加,否则会写入 form.pretaxcost 并落库 |
| | | this.form.pretaxcost = moneySum(this.rbDetails, "amount"); |
| | | }, |
| | | |
| | | //导入所有项目 |
| | |
| | | }, |
| | | // 接受提醒立刻保存 |
| | | Savenow() { |
| | | // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行) |
| | | if (!this.checkDetailFileNames()) { |
| | | return; |
| | | } |
| | | |
| | | if (!this.randomnumber) { |
| | | this.randomnumber = this.generateRandomString(9); |
| | | this.form.randomnumber = this.randomnumber; |
| | |
| | | this.Savereminder = false; |
| | | this.Reminderquantity = 0; |
| | | this.totalquantity = 0; |
| | | this.$refs["form"].validate(valid => { |
| | | if (valid) { |
| | | this.formData = this.form; |
| | | |
| | | //保存前校验数据 |
| | | for (let k = 0; k < this.rbDetails.length; k++) { |
| | | let tempDetail = this.rbDetails[k]; |
| | | if (tempDetail.itemid == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的服务项目未选择,请先选择服务项目后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | if (this.businessType == "1") { |
| | | if (tempDetail.servicesscope == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的费用说明未选择,请选择费用说明后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | if (tempDetail.beneficiaryno == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的人员姓名为空,请选择人员后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | if (tempDetail.unitname == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的专家单位为空,请选择单位后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | //处理明细 |
| | | for (let i = 0; i < this.rbDetails.length; i++) { |
| | | let tempDetail = this.rbDetails[i]; |
| | | //获取服务项目明细 |
| | | let tempitemlist = tempDetail.itemlist; |
| | | let itemIndex = tempitemlist.findIndex( |
| | | item => tempDetail.itemid == item.id |
| | | ); |
| | | if (itemIndex > -1) { |
| | | tempDetail.itemname = tempitemlist[itemIndex].itemName; |
| | | tempDetail.itemid = tempitemlist[itemIndex].id; |
| | | } |
| | | |
| | | //获取applytypename、服务费用大类 |
| | | let applytypeIndex = this.rowfeeItemTypes.findIndex( |
| | | item => tempDetail.applytype == item.value |
| | | ); |
| | | if (applytypeIndex > -1) { |
| | | tempDetail.applytypename = this.rowfeeItemTypes[ |
| | | applytypeIndex |
| | | ].label; |
| | | tempDetail.subjecttype = this.rowfeeItemTypes[ |
| | | applytypeIndex |
| | | ].subjecttype; |
| | | tempDetail.subjecttypename = this.rowfeeItemTypes[ |
| | | applytypeIndex |
| | | ].subjecttypename; |
| | | } |
| | | this.rbDetails[i] = tempDetail; |
| | | } |
| | | //计算当前行报销内容费用合计,税前金额(amount)、扣税金额(taxamount)、税后金额(taxedamount) |
| | | if (this.businessType == "2") { |
| | | for (let j = 0; j < this.rbDetails.length; j++) { |
| | | let tempDetail = this.rbDetails[j]; |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.amount)) || |
| | | parseFloat(tempDetail.amount) == 0 |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (j + 1).toString() + |
| | | "行的金额未输入,请先输入金额后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | } |
| | | } else if (this.businessType == "3" || this.businessType == "4") { |
| | | for (let j = 0; j < this.rbDetails.length; j++) { |
| | | let tempDetail = this.rbDetails[j]; |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.amount)) || |
| | | parseFloat(tempDetail.amount) == 0 |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (j + 1).toString() + |
| | | "行的金额未输入,请先输入金额后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.taxamount)) || |
| | | parseFloat(tempDetail.taxamount) == 0 |
| | | ) { |
| | | tempDetail.taxedamount = parseFloat(tempDetail.amount); |
| | | } else { |
| | | tempDetail.taxedamount = |
| | | parseFloat(tempDetail.amount) + |
| | | parseFloat(tempDetail.taxamount); |
| | | } |
| | | } |
| | | } |
| | | |
| | | //保存数据 |
| | | this.loading = true; |
| | | this.form.serviceFunddetails = 0; |
| | | this.rbDetails.map(item => { |
| | | this.form.serviceFunddetails += item.amount; |
| | | }); |
| | | this.form.serviceFunddetails = this.rbDetails; |
| | | |
| | | if (this.form.id != null) { |
| | | } else { |
| | | this.form.recordstatus = -1; |
| | | } |
| | | |
| | | addorupdateFund(this.form).then(response => { |
| | | if (response.code === 200) { |
| | | this.$modal.msgSuccess("申请单保存成功!"); |
| | | // 非提醒状态下执行审批解决异步调用延迟问题 |
| | | if (!this.Savereminder) { |
| | | let checkFundObj = { |
| | | // fundid: this.checkObj.fundid, |
| | | fundid: this.curId, |
| | | flowconclusion: this.checkObj.flowlevelone, |
| | | flowcontent: this.checkObj.flowconclusion |
| | | }; |
| | | this.$modal.loading("正在提交审核,请稍候!"); |
| | | checkfund(checkFundObj).then(res => { |
| | | if (res.code == 200) { |
| | | //关闭窗口 |
| | | this.$modal.closeLoading(); |
| | | this.$modal.msgSuccess("审核完成!"); |
| | | this.$store.dispatch("tagsView/delView", this.$route); |
| | | this.$router.go(-1); |
| | | } else { |
| | | this.$modal.closeLoading(); |
| | | |
| | | if (res.msg) { |
| | | this.$modal.msgError("操作失误"); |
| | | } else { |
| | | this.$modal.msgError(res.msg); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | this.Getnetworkheader(); |
| | | this.getroute(); |
| | | this.getAccountList(); |
| | | this.getUserList(); |
| | | this.getUserProfile(); |
| | | } else { |
| | | this.$modal.msgError("申请单保存失败:" + response.msg); |
| | | } |
| | | }); |
| | | |
| | | //关闭窗口 |
| | | this.loading = false; |
| | | // this.getfundList(); |
| | | // 专家劳务费专用:9 条费用控制规则校验(校验不通过不允许落库) |
| | | this.checkExpertFeeRules("保存").then((allowed) => { |
| | | if (!allowed) { |
| | | return; |
| | | } |
| | | |
| | | this.$refs["form"].validate(valid => { |
| | | if (valid) { |
| | | this.formData = this.form; |
| | | |
| | | //保存前校验数据 |
| | | for (let k = 0; k < this.rbDetails.length; k++) { |
| | | let tempDetail = this.rbDetails[k]; |
| | | if (tempDetail.itemid == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的服务项目未选择,请先选择服务项目后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | if (this.businessType == "1") { |
| | | if (tempDetail.servicesscope == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的费用说明未选择,请选择费用说明后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | if (tempDetail.beneficiaryno == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的人员姓名为空,请选择人员后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | if (tempDetail.unitname == null) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | | "行的专家单位为空,请选择单位后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | //处理明细 |
| | | for (let i = 0; i < this.rbDetails.length; i++) { |
| | | let tempDetail = this.rbDetails[i]; |
| | | //获取服务项目明细 |
| | | let tempitemlist = tempDetail.itemlist; |
| | | let itemIndex = tempitemlist.findIndex( |
| | | item => tempDetail.itemid == item.id |
| | | ); |
| | | if (itemIndex > -1) { |
| | | tempDetail.itemname = tempitemlist[itemIndex].itemName; |
| | | tempDetail.itemid = tempitemlist[itemIndex].id; |
| | | } |
| | | |
| | | //获取applytypename、服务费用大类 |
| | | let applytypeIndex = this.rowfeeItemTypes.findIndex( |
| | | item => tempDetail.applytype == item.value |
| | | ); |
| | | if (applytypeIndex > -1) { |
| | | tempDetail.applytypename = this.rowfeeItemTypes[ |
| | | applytypeIndex |
| | | ].label; |
| | | tempDetail.subjecttype = this.rowfeeItemTypes[ |
| | | applytypeIndex |
| | | ].subjecttype; |
| | | tempDetail.subjecttypename = this.rowfeeItemTypes[ |
| | | applytypeIndex |
| | | ].subjecttypename; |
| | | } |
| | | this.rbDetails[i] = tempDetail; |
| | | } |
| | | //计算当前行报销内容费用合计,税前金额(amount)、扣税金额(taxamount)、税后金额(taxedamount) |
| | | if (this.businessType == "2") { |
| | | for (let j = 0; j < this.rbDetails.length; j++) { |
| | | let tempDetail = this.rbDetails[j]; |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.amount)) || |
| | | moneyRound(tempDetail.amount) == 0 |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (j + 1).toString() + |
| | | "行的金额未输入,请先输入金额后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | } |
| | | } else if (this.businessType == "3" || this.businessType == "4") { |
| | | for (let j = 0; j < this.rbDetails.length; j++) { |
| | | let tempDetail = this.rbDetails[j]; |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.amount)) || |
| | | moneyRound(tempDetail.amount) == 0 |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (j + 1).toString() + |
| | | "行的金额未输入,请先输入金额后再提交保存!" |
| | | ); |
| | | return; |
| | | } |
| | | if ( |
| | | isNaN(parseFloat(tempDetail.taxamount)) || |
| | | moneyRound(tempDetail.taxamount) == 0 |
| | | ) { |
| | | tempDetail.taxedamount = parseFloat(tempDetail.amount); |
| | | } else { |
| | | tempDetail.taxedamount = |
| | | parseFloat(tempDetail.amount) + |
| | | parseFloat(tempDetail.taxamount); |
| | | } |
| | | } |
| | | } |
| | | |
| | | //保存数据 |
| | | this.loading = true; |
| | | this.form.serviceFunddetails = 0; |
| | | this.rbDetails.map(item => { |
| | | this.form.serviceFunddetails += item.amount; |
| | | }); |
| | | this.form.serviceFunddetails = this.rbDetails; |
| | | |
| | | if (this.form.id != null) { |
| | | } else { |
| | | this.form.recordstatus = -1; |
| | | } |
| | | |
| | | addorupdateFund(this.form).then(response => { |
| | | if (response.code === 200) { |
| | | this.$modal.msgSuccess("申请单保存成功!"); |
| | | // 非提醒状态下执行审批解决异步调用延迟问题 |
| | | if (!this.Savereminder) { |
| | | let checkFundObj = { |
| | | // fundid: this.checkObj.fundid, |
| | | fundid: this.curId, |
| | | flowconclusion: this.checkObj.flowlevelone, |
| | | flowcontent: this.checkObj.flowconclusion |
| | | }; |
| | | this.$modal.loading("正在提交审核,请稍候!"); |
| | | checkfund(checkFundObj).then(res => { |
| | | if (res.code == 200) { |
| | | //关闭窗口 |
| | | this.$modal.closeLoading(); |
| | | this.$modal.msgSuccess("审核完成!"); |
| | | // 连续审批:询问是否继续审批下一单 |
| | | this.continueOrExit(); |
| | | } else { |
| | | this.$modal.closeLoading(); |
| | | |
| | | this.$modal.msgError(res.msg || "操作失败"); |
| | | } |
| | | }); |
| | | } |
| | | this.Getnetworkheader(); |
| | | this.getroute(); |
| | | this.getAccountList(); |
| | | this.getUserList(); |
| | | this.getUserProfile(); |
| | | } else { |
| | | this.$modal.msgError("申请单保存失败:" + response.msg); |
| | | } |
| | | }); |
| | | |
| | | //关闭窗口 |
| | | this.loading = false; |
| | | // this.getfundList(); |
| | | } |
| | | }); |
| | | }); |
| | | }, |
| | | handleCheckAllChange(val) { |
| | | this.checkedCities = val ? this.cityOptions : []; |
| | | this.checkedCities = val ? this.filteredCityOptions.slice() : []; |
| | | this.isIndeterminate = false; |
| | | }, |
| | | handleCheckedCitiesChange(value) { |
| | | let checkedCount = value.length; |
| | | this.checkAll = checkedCount === this.cityOptions.length; |
| | | this.checkAll = |
| | | this.filteredCityOptions.length > 0 && |
| | | checkedCount === this.filteredCityOptions.length; |
| | | this.isIndeterminate = |
| | | checkedCount > 0 && checkedCount < this.cityOptions.length; |
| | | checkedCount > 0 && checkedCount < this.filteredCityOptions.length; |
| | | }, |
| | | // 手动计算 |
| | | Manualcalculationfn() { |
| | |
| | | // 批量录入点击 |
| | | Batchentry() { |
| | | // ethical |
| | | this.personKeyword = ""; |
| | | this.checkedCities = []; |
| | | this.checkAll = false; |
| | | this.isIndeterminate = false; |
| | | listReportname("ethical").then(res => { |
| | | this.cityOptions = res.data; |
| | | }); |
| | | this.batchdialogVisible = true; |
| | | }, |
| | | /** 批量录入弹框:姓名/单位/工号/身份证后4位 命中判断 */ |
| | | personMatchKeyword(item, kw) { |
| | | const idcard = String(item.idcrad || ""); |
| | | return ( |
| | | String(item.reportName || "") |
| | | .toLowerCase() |
| | | .indexOf(kw) > -1 || |
| | | String(item.unitname || "") |
| | | .toLowerCase() |
| | | .indexOf(kw) > -1 || |
| | | String(item.reportNo || "") |
| | | .toLowerCase() |
| | | .indexOf(kw) > -1 || |
| | | (idcard.length >= 4 && idcard.slice(-4).indexOf(kw) > -1) |
| | | ); |
| | | }, |
| | | // 确认录入 |
| | | BatchentryFn() { |
| | | // 明细中已有的人员姓名,用于重复提醒 |
| | | const existed = {}; |
| | | this.rbDetails.forEach(row => { |
| | | if (row.beneficiaryname) { |
| | | existed[String(row.beneficiaryname).trim()] = true; |
| | | } |
| | | }); |
| | | |
| | | const duplicated = []; |
| | | this.checkedCities.forEach(item => { |
| | | const nameKey = String(item.reportName || "").trim(); |
| | | if (nameKey && existed[nameKey]) { |
| | | duplicated.push(item.reportName); |
| | | return; |
| | | } |
| | | if (nameKey) { |
| | | existed[nameKey] = true; |
| | | } |
| | | let rowData = { |
| | | orderno: null, |
| | | fundid: null, |
| | | beneficiaryname: item.reportName, |
| | | beneficiaryno: item.reportNo, |
| | | isunit: 0, |
| | | unitname: item.unitname, |
| | | unitno: null, |
| | | uintuserno: null, |
| | |
| | | |
| | | this.rbDetails.push(rowData); |
| | | }); |
| | | for (let i = 0; i < this.checkedCities.length + 1; i++) { |
| | | for (let i = 0; i < this.rbDetails.length; i++) { |
| | | this.rbDetails[i].orderno = i + 1; |
| | | } |
| | | this.calculateTax(); |
| | | this.batchdialogVisible = false; |
| | | this.checkedCities = []; |
| | | if (duplicated.length) { |
| | | this.$modal.msgWarning( |
| | | "以下人员在明细中已存在,已跳过重复添加:" + duplicated.join("、") |
| | | ); |
| | | } |
| | | }, |
| | | //新增一行 |
| | | addRow(rowIndex) { |
| | |
| | | fundid: null, |
| | | beneficiaryname: null, |
| | | beneficiaryno: null, |
| | | isunit: 0, |
| | | unitname: null, |
| | | itemdescribe: null, |
| | | unitno: null, |
| | |
| | | }); |
| | | //全部删除后添加一条新行 |
| | | }, |
| | | //计算总金额 |
| | | //计算总金额(金额一律走精确运算,禁止 parseFloat 累加) |
| | | sumTotalFee(row) { |
| | | let allSum = 0; |
| | | const detailsArr = []; |
| | | if (this.form.applytype != null) { |
| | | for (let i = 0; i < this.rbDetails.length; i++) { |
| | | const detail = this.rbDetails[i]; |
| | | let totalFee = 0; |
| | | let taxFee = 0; |
| | | if (this.form.applytype == 1 || this.form.applytype == 2) { |
| | | //计算当前报销内容费用合计 |
| | | if (!isNaN(parseFloat(this.rbDetails[i].amount))) { |
| | | totalFee += parseFloat(this.rbDetails[i].amount); |
| | | taxFee += parseFloat(this.rbDetails[i].amount); |
| | | //计算当前报销内容费用合计:税前 - 扣税 |
| | | if (!isNaN(parseFloat(detail.amount))) { |
| | | totalFee = moneySub(totalFee, -parseFloat(detail.amount)); |
| | | taxFee = moneySub(taxFee, -parseFloat(detail.amount)); |
| | | } |
| | | if (!isNaN(parseFloat(this.rbDetails[i].taxamount))) { |
| | | totalFee -= parseFloat(this.rbDetails[i].taxamount); |
| | | taxFee -= parseFloat(this.rbDetails[i].taxamount); |
| | | if (!isNaN(parseFloat(detail.taxamount))) { |
| | | totalFee = moneySub(totalFee, detail.taxamount); |
| | | taxFee = moneySub(taxFee, detail.taxamount); |
| | | } |
| | | } else if (this.form.applytype == 3 || this.form.applytype == 4) { |
| | | //单价*数量=税前金额 |
| | | if ( |
| | | !isNaN(parseFloat(this.rbDetails[i].price)) && |
| | | !isNaN(parseFloat(this.rbDetails[i].quantity)) |
| | | !isNaN(parseFloat(detail.price)) && |
| | | !isNaN(parseFloat(detail.quantity)) |
| | | ) { |
| | | totalFee += |
| | | parseFloat(this.rbDetails[i].price) * |
| | | parseFloat(this.rbDetails[i].quantity); |
| | | this.rbDetails[i].amount = totalFee; |
| | | totalFee = moneyMul(detail.price, detail.quantity); |
| | | detail.amount = totalFee; |
| | | } |
| | | } |
| | | |
| | |
| | | // this.rbDetails[i].taxedamount = taxFee.toFixed(2); |
| | | // } |
| | | |
| | | allSum += totalFee; |
| | | detailsArr.push(totalFee); |
| | | } |
| | | } |
| | | const allSum = moneyRound(moneySum(detailsArr)); |
| | | // this.form.amountrequested = allSum.toFixed(2); |
| | | // this.$forceUpdate(); |
| | | }, |
| | |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。 |
| | | * 规则与后端 AttachmentNameValidator 同口径:必须含中文 / 去掉扩展名后主干 ≤20 字 / |
| | | * 不含空格及 / \ : * ? " < > | # % 等特殊字符。 |
| | | */ |
| | | checkUploadFileName(file) { |
| | | const raw = file && file.raw ? file.raw.name : file && file.name; |
| | | const r = checkFileName(raw); |
| | | if (!r.ok) { |
| | | this.$modal.msgError(r.msg); |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | |
| | | /** |
| | | * 2026-10-15:保存前校验整单明细附件文件名(含同一行内重名)。 |
| | | * 口径(业务确认):只校验"本次变更"的附件——老单据里已存在的不合规附件名不能把保存卡死 |
| | | * (否则老单据改一个金额都存不进去)。后端按 id 读旧记录放行历史附件;前端拿不到"哪些是历史" |
| | | * 的权威信息,因此**仅新增单据**做保存前拦截,编辑老单据时新增的不合规附件由后端二次校验拦下。 |
| | | */ |
| | | checkDetailFileNames() { |
| | | if (this.form && this.form.id) { |
| | | return true; |
| | | } |
| | | const c = checkDetails(this.rbDetails); |
| | | if (!c.ok) { |
| | | this.$modal.msgError(c.msg); |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | |
| | | uploadSccess(response, file, fileList) { |
| | | if (!this.checkUploadFileName(file)) { |
| | | return; |
| | | } |
| | | this.rbDetails; |
| | | const config = { |
| | | headers: { Authorization: "Bearer " + this.ICDtoken } |
| | |
| | | //专家/医疗机构/费用报销机构选择 |
| | | confirmSelectionDialog() { |
| | | this.$refs["accountselectform"].validate(valid => { |
| | | // 2026-10-19:原来没有判断 valid,校验失败也照样往下走、照样保存(星号/红字形同虚设) |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | let tempIndex = this.accountselectform.index; |
| | | if (this.selectionType == "account") { |
| | | if (this.accountfrom == "1") { |
| | |
| | | this.rbDetails[tempIndex].phone = res.telephone; |
| | | |
| | | if (res.usertype == "org") { |
| | | this.$set(this.rbDetails[tempIndex], "isunit", 1); |
| | | this.rbDetails[tempIndex].idcardno = ""; |
| | | this.rbDetails[tempIndex].title = ""; |
| | | this.rbDetails[tempIndex].unitno = ""; |
| | | this.rbDetails[tempIndex].unitname = ""; |
| | | } else { |
| | | this.$set(this.rbDetails[tempIndex], "isunit", 0); |
| | | this.rbDetails[tempIndex].idcardno = res.idcardno; |
| | | this.rbDetails[tempIndex].title = res.title; |
| | | this.rbDetails[tempIndex].unitno = parseInt(res.unitno); |
| | |
| | | this.rbDetails[tempIndex].bankcardno = res.bankcardno; |
| | | this.rbDetails[tempIndex].phone = res.telephone; |
| | | |
| | | if (res.usertype == "org") { |
| | | if (this.hasUserType(res.usertype, "org")) { |
| | | this.$set(this.rbDetails[tempIndex], "isunit", 1); |
| | | this.rbDetails[tempIndex].idcardno = ""; |
| | | this.rbDetails[tempIndex].title = ""; |
| | | this.rbDetails[tempIndex].unitno = ""; |
| | | this.rbDetails[tempIndex].unitname = ""; |
| | | } else { |
| | | this.$set(this.rbDetails[tempIndex], "isunit", 0); |
| | | this.rbDetails[tempIndex].idcardno = res.idcardno; |
| | | this.rbDetails[tempIndex].title = res.title; |
| | | this.rbDetails[tempIndex].unitno = parseInt(res.unitno); |
| | |
| | | |
| | | //生命周期 - 挂载完成(可以访问DOM元素) |
| | | mounted() { |
| | | window.addEventListener("beforeunload", e => this.beforeunloadHandler(e)); |
| | | // 存住同一个引用,否则 removeEventListener 传新箭头函数无法真正移除,会泄漏监听器 |
| | | this._beforeUnloadHandler = e => this.beforeunloadHandler(e); |
| | | window.addEventListener("beforeunload", this._beforeUnloadHandler); |
| | | this.getUserList(); |
| | | let apiaccountselectform = this.accountselectform; |
| | | let apiform = this.form; |
| | |
| | | updated() { }, //生命周期 - 更新之后 |
| | | beforeDestroy() { }, //生命周期 - 销毁之前 |
| | | destroyed() { |
| | | window.removeEventListener("beforeunload", e => this.beforeunloadFn(e)); |
| | | if (this._beforeUnloadHandler) { |
| | | window.removeEventListener("beforeunload", this._beforeUnloadHandler); |
| | | this._beforeUnloadHandler = null; |
| | | } |
| | | }, //生命周期 - 销毁完成 |
| | | activated() { } //如果页面有keep-alive缓存功能,这个函数会触发 |
| | | }; |