工会报销支付信息修改
This commit is contained in:
+60
-1
@@ -24,6 +24,8 @@ import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHist
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
@@ -116,11 +118,22 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存、提交及重新提交共用支付信息校验,并在同一事务内保存申请和发票明细。
|
||||
* @param unionReimburse 申请表单,paymentDetails 为支付方式、金额及对私收款资料组成的明细列表
|
||||
* @param stateId 1 表示保存草稿,2 表示提交;两种状态均要求支付信息完整
|
||||
* @return Result 成功或失败结果,msg 包含失败明细的行号及原因
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result saveApply(UnionReimburse unionReimburse, int stateId) {
|
||||
if (unionReimburse == null) {
|
||||
return Result.error("报销数据不能为空");
|
||||
}
|
||||
Result paymentResult = validatePaymentDetails(unionReimburse);
|
||||
if (paymentResult != null) {
|
||||
return paymentResult;
|
||||
}
|
||||
UnionReimburse oldRecord = null;
|
||||
if (StrUtil.isNotBlank(unionReimburse.getId())) {
|
||||
oldRecord = this.fetch(unionReimburse.getId());
|
||||
@@ -383,7 +396,6 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
seg.or("u.username", "like", "%" + keyword + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.and("u.id", "!=", SecurityUtil.getUserId());
|
||||
if (clubFundSource) {
|
||||
cnd.and("cu.clubId", "=", clubId);
|
||||
} else if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
@@ -668,6 +680,53 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
unionReimburse.setRealMoney(getDeclaredApplyMoney(unionReimburse).doubleValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付明细,防止绕过 PC/H5 校验直接保存。对公只需方式和金额,对私必须补齐人员及银行资料。
|
||||
* @param form 本次保存的申请,paymentDetails 必须至少包含一条完整支付明细
|
||||
* @return 校验通过返回 null,失败返回带行号和原因的 Result
|
||||
*/
|
||||
private Result validatePaymentDetails(UnionReimburse form) {
|
||||
if (Lang.isEmpty(form.getPaymentDetails())) {
|
||||
return Result.error("请至少维护一条支付信息");
|
||||
}
|
||||
for (int i = 0; i < form.getPaymentDetails().size(); i++) {
|
||||
cn.hutool.json.JSONObject detail = form.getPaymentDetails().get(i);
|
||||
String prefix = "第" + (i + 1) + "条支付信息";
|
||||
if (detail == null) {
|
||||
return Result.error(prefix + "不能为空");
|
||||
}
|
||||
String way = detail.getStr("paymentWay");
|
||||
if (!"UNION_REIMBURSE_PAYMENT_WAY_1".equals(way)
|
||||
&& !"UNION_REIMBURSE_PAYMENT_WAY_2".equals(way)) {
|
||||
return Result.error(prefix + "请选择有效的支付方式");
|
||||
}
|
||||
Object rawMoney = detail.get("money");
|
||||
String money = rawMoney == null ? "" : rawMoney.toString();
|
||||
// 先严格检查十进制格式,再比较金额;禁止字母、科学计数法、负数和超出两位的小数。
|
||||
if (!(rawMoney instanceof String || rawMoney instanceof Number)
|
||||
|| !money.matches("[0-9]+(\\.[0-9]{1,2})?")
|
||||
|| new BigDecimal(money).compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.error(prefix + "金额必须大于0且最多两位小数");
|
||||
}
|
||||
if ("UNION_REIMBURSE_PAYMENT_WAY_2".equals(way)) {
|
||||
if (StrUtil.isBlank(detail.getStr("payer"))) {
|
||||
return Result.error(prefix + "请选择付款人");
|
||||
}
|
||||
if (StrUtil.isBlank(detail.getStr("bankUserName"))) {
|
||||
return Result.error(prefix + "请填写户名");
|
||||
}
|
||||
if (StrUtil.isBlank(detail.getStr("bankOfDeposit"))) {
|
||||
return Result.error(prefix + "请填写开户行");
|
||||
}
|
||||
String card = detail.getStr("bankCardNumber");
|
||||
if (card == null || !card.matches("[1-9]([0-9]{15}|[0-9]{18})")) {
|
||||
return Result.error(prefix + "请填写正确的银行卡号");
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交前补一层后端校验,防止前端绕过校验直接提交。
|
||||
*/
|
||||
|
||||
+38
-12
@@ -489,7 +489,7 @@ layout("/layouts/platform.html"){
|
||||
<template slot-scope="scope">
|
||||
<el-form-item :prop="'paymentDetails.' + scope.$index + '.money'"
|
||||
:rules="paymentDetailRules.money">
|
||||
<el-input v-model="scope.row.money" placeholder="请输入金额"></el-input>
|
||||
<el-input :value="scope.row.money" @input="inputPaymentMoney(scope.row, $event)" inputmode="decimal" placeholder="请输入金额(最多两位小数)"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -784,13 +784,13 @@ layout("/layouts/platform.html"){
|
||||
paymentWay: [{required: true, message: "请选择支付方式", trigger: ["change", "blur"]}],
|
||||
reimburseProject: [{required: true, message: "请选择报销项目", trigger: ["change", "blur"]}],
|
||||
reimburseFundSource: [{required: true, message: "请选择经费来源", trigger: ["change", "blur"]}],
|
||||
bankUserName: [{required: true, message: "请填写户名", trigger: ["change", "blur"]}],
|
||||
bankUserName: [{required: true, whitespace: true, message: "请填写户名", trigger: ["change", "blur"]}],
|
||||
bankCardNumber: [{
|
||||
required: true,
|
||||
message: "请填写银行账号",
|
||||
trigger: ["change", "blur"]
|
||||
}, {pattern: /^([1-9]{1})(\d{15}|\d{18})$/, message: "请输入正确的银行卡号", trigger: "blur"}],
|
||||
bankOfDeposit: [{required: true, message: "请填写开户行", trigger: ["change", "blur"]}],
|
||||
bankOfDeposit: [{required: true, whitespace: true, message: "请填写开户行", trigger: ["change", "blur"]}],
|
||||
condolenceTime: [{required: true, message: "请选择慰问时间", trigger: ["change", "blur"]}],
|
||||
// invoiceNumber: [{required: true, message: "请填写发票张数", trigger: ["change", "blur"]}],
|
||||
// invoice: [{required: true, message: "请填写发票号码", trigger: ["change", "blur"]}],
|
||||
@@ -837,15 +837,20 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
paymentDetailRules: {
|
||||
paymentWay: [{required: true, message: "请选择支付方式", trigger: ["change", "blur"]}],
|
||||
money: [{required: true, message: "请填写金额", trigger: ["change", "blur"]}],
|
||||
money: [{required: true, message: "请填写金额", trigger: ["change", "blur"]}, {
|
||||
validator: (rule, value, callback) => {
|
||||
callback(this.isValidPaymentMoney(value) ? undefined : new Error("金额必须大于0且最多两位小数"))
|
||||
},
|
||||
trigger: ["change", "blur"]
|
||||
}],
|
||||
payer: [{required: true, message: "请选择付款人", trigger: ["change", "blur"]}],
|
||||
bankUserName: [{required: true, message: "请填写户名", trigger: ["change", "blur"]}],
|
||||
bankUserName: [{required: true, whitespace: true, message: "请填写户名", trigger: ["change", "blur"]}],
|
||||
bankCardNumber: [{
|
||||
required: true,
|
||||
message: "请填写银行卡号",
|
||||
trigger: ["change", "blur"]
|
||||
}, {pattern: /^([1-9]{1})(\d{15}|\d{18})$/, message: "请输入正确的银行卡号", trigger: "blur"}],
|
||||
bankOfDeposit: [{required: true, message: "请填写开户行", trigger: ["change", "blur"]}]
|
||||
bankOfDeposit: [{required: true, whitespace: true, message: "请填写开户行", trigger: ["change", "blur"]}]
|
||||
},
|
||||
chooseType: {},
|
||||
userOptions: [],
|
||||
@@ -1478,6 +1483,22 @@ layout("/layouts/platform.html"){
|
||||
this.syncLegacyPaymentFields(submitData.paymentDetails, submitData)
|
||||
return submitData
|
||||
},
|
||||
// 金额输入允许编辑中的空值和末尾小数点;非法字符或超出两位小数时恢复原值,避免改变金额含义。
|
||||
inputPaymentMoney(row, value) {
|
||||
const previous = row.money
|
||||
const text = String(value == null ? "" : value)
|
||||
this.$set(row, "money", text)
|
||||
if (!/^\d*(\.\d{0,2})?$/.test(text)) {
|
||||
this.$nextTick(() => { this.$set(row, "money", previous) })
|
||||
this.$message.warning("金额只能输入数字,最多两位小数")
|
||||
}
|
||||
},
|
||||
// 保存与提交统一要求完整的正金额,不接受科学计数法、非数字或超过两位小数。
|
||||
isValidPaymentMoney(value) {
|
||||
const text = String(value == null ? "" : value)
|
||||
return /^\d+(\.\d{1,2})?$/.test(text) && Number.isFinite(Number(text)) && Number(text) > 0
|
||||
},
|
||||
// 校验每行支付信息;对私支付还需完整的付款人和银行信息,空白文字不能视为已填写。
|
||||
validatePaymentDetailsBeforeSubmit(paymentDetails) {
|
||||
if (!paymentDetails.length) {
|
||||
this.$message.warning("请至少维护一条支付信息")
|
||||
@@ -1485,24 +1506,24 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
for (let i = 0; i < paymentDetails.length; i++) {
|
||||
const currentDetail = paymentDetails[i]
|
||||
if (!currentDetail.paymentWay) {
|
||||
if (!["UNION_REIMBURSE_PAYMENT_WAY_1", "UNION_REIMBURSE_PAYMENT_WAY_2"].includes(currentDetail.paymentWay)) {
|
||||
this.$message.warning("第" + (i + 1) + "条支付信息请选择支付方式")
|
||||
return false
|
||||
}
|
||||
if (!currentDetail.money || Number(currentDetail.money) <= 0) {
|
||||
this.$message.warning("第" + (i + 1) + "条支付信息请填写正确的金额")
|
||||
if (!this.isValidPaymentMoney(currentDetail.money)) {
|
||||
this.$message.warning("第" + (i + 1) + "条支付信息金额必须大于0且最多两位小数")
|
||||
return false
|
||||
}
|
||||
if (currentDetail.paymentWay === "UNION_REIMBURSE_PAYMENT_WAY_2") {
|
||||
if (!currentDetail.payer) {
|
||||
if (!String(currentDetail.payer || "").trim()) {
|
||||
this.$message.warning("第" + (i + 1) + "条支付信息请选择付款人")
|
||||
return false
|
||||
}
|
||||
if (!currentDetail.bankUserName) {
|
||||
if (!String(currentDetail.bankUserName || "").trim()) {
|
||||
this.$message.warning("第" + (i + 1) + "条支付信息请填写户名")
|
||||
return false
|
||||
}
|
||||
if (!currentDetail.bankOfDeposit) {
|
||||
if (!String(currentDetail.bankOfDeposit || "").trim()) {
|
||||
this.$message.warning("第" + (i + 1) + "条支付信息请填写开户行")
|
||||
return false
|
||||
}
|
||||
@@ -1531,6 +1552,11 @@ layout("/layouts/platform.html"){
|
||||
this.rebuildInvoiceSummary()
|
||||
this.normalizeFundBalance()
|
||||
const paymentDetails = this.preparePaymentDetailsForSubmit()
|
||||
// 保存草稿也必须填写完整支付信息,避免非法金额和空明细入库。
|
||||
if (!this.validatePaymentDetailsBeforeSubmit(paymentDetails)) {
|
||||
this.$set(this, "activeTabName", "invoice")
|
||||
return
|
||||
}
|
||||
this.$confirm("您确定保存吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
|
||||
+30
-9
@@ -858,9 +858,10 @@ layout("/layouts/platform_h5.html"){
|
||||
></van-field>
|
||||
<van-field
|
||||
label="金额"
|
||||
v-model="item.money"
|
||||
placeholder="请输入金额"
|
||||
type="number"
|
||||
:value="item.money"
|
||||
@input="inputPaymentMoney(item, $event)"
|
||||
placeholder="请输入金额(最多两位小数)"
|
||||
inputmode="decimal"
|
||||
required
|
||||
></van-field>
|
||||
<template v-if="isPrivatePayment(item)">
|
||||
@@ -2003,6 +2004,22 @@ layout("/layouts/platform_h5.html"){
|
||||
this.syncLegacyPaymentFields(submitData.paymentDetails, submitData)
|
||||
return submitData
|
||||
},
|
||||
// 金额输入允许编辑中的空值和末尾小数点;非法字符或超出两位小数时恢复原值,避免改变金额含义。
|
||||
inputPaymentMoney(row, value) {
|
||||
const previous = row.money
|
||||
const text = String(value == null ? "" : value)
|
||||
this.$set(row, "money", text)
|
||||
if (!/^\d*(\.\d{0,2})?$/.test(text)) {
|
||||
this.$nextTick(() => { this.$set(row, "money", previous) })
|
||||
this.$toast.fail("金额只能输入数字,最多两位小数")
|
||||
}
|
||||
},
|
||||
// 保存与提交统一要求完整的正金额,不接受科学计数法、非数字或超过两位小数。
|
||||
isValidPaymentMoney(value) {
|
||||
const text = String(value == null ? "" : value)
|
||||
return /^\d+(\.\d{1,2})?$/.test(text) && Number.isFinite(Number(text)) && Number(text) > 0
|
||||
},
|
||||
// 校验每行支付信息;对私支付还需完整的付款人和银行信息,空白文字不能视为已填写。
|
||||
validatePaymentDetailsBeforeSubmit(paymentDetails) {
|
||||
if (!paymentDetails.length) {
|
||||
this.$toast.fail("请至少维护一条支付信息")
|
||||
@@ -2010,24 +2027,24 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
for (let i = 0; i < paymentDetails.length; i++) {
|
||||
const currentDetail = paymentDetails[i]
|
||||
if (!currentDetail.paymentWay) {
|
||||
if (!["UNION_REIMBURSE_PAYMENT_WAY_1", "UNION_REIMBURSE_PAYMENT_WAY_2"].includes(currentDetail.paymentWay)) {
|
||||
this.$toast.fail("第" + (i + 1) + "条支付信息请选择支付方式")
|
||||
return false
|
||||
}
|
||||
if (!currentDetail.money || Number(currentDetail.money) <= 0) {
|
||||
this.$toast.fail("第" + (i + 1) + "条支付信息请填写正确的金额")
|
||||
if (!this.isValidPaymentMoney(currentDetail.money)) {
|
||||
this.$toast.fail("第" + (i + 1) + "条支付信息金额必须大于0且最多两位小数")
|
||||
return false
|
||||
}
|
||||
if (currentDetail.paymentWay === "UNION_REIMBURSE_PAYMENT_WAY_2") {
|
||||
if (!currentDetail.payer) {
|
||||
if (!String(currentDetail.payer || "").trim()) {
|
||||
this.$toast.fail("第" + (i + 1) + "条支付信息请选择付款人")
|
||||
return false
|
||||
}
|
||||
if (!currentDetail.bankUserName) {
|
||||
if (!String(currentDetail.bankUserName || "").trim()) {
|
||||
this.$toast.fail("第" + (i + 1) + "条支付信息请填写户名")
|
||||
return false
|
||||
}
|
||||
if (!currentDetail.bankOfDeposit) {
|
||||
if (!String(currentDetail.bankOfDeposit || "").trim()) {
|
||||
this.$toast.fail("第" + (i + 1) + "条支付信息请填写开户行")
|
||||
return false
|
||||
}
|
||||
@@ -2071,6 +2088,10 @@ layout("/layouts/platform_h5.html"){
|
||||
this.normalizeCondolenceDateFields()
|
||||
this.rebuildInvoiceSummary()
|
||||
const paymentDetails = this.preparePaymentDetailsForSubmit()
|
||||
// 保存草稿也必须填写完整支付信息,避免非法金额和空明细入库。
|
||||
if (!this.validatePaymentDetailsBeforeSubmit(paymentDetails)) {
|
||||
return
|
||||
}
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post("/platform/unionReimburse/apply/save", { data: JSON.stringify(this.buildSubmitFormData(paymentDetails)) })
|
||||
.then((res) => {
|
||||
|
||||
Reference in New Issue
Block a user