diff --git a/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java b/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java index b54162f6..5708655a 100644 --- a/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java +++ b/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java @@ -8,11 +8,9 @@ import com.budwk.app.base.service.BaseService; import com.budwk.app.bpm.enums.BpmProcessInstanceStatusEnum; import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum; import com.budwk.app.flow.enums.ProcessInstanceStateEnum; -import com.budwk.app.sys.models.Sys_role; -import com.budwk.app.sys.models.Sys_user_role; -import com.budwk.app.sys.services.SysRoleService; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.club.model.ClubUser; import com.budwk.app.zhgh.club.model.SysClub; import com.budwk.app.zhgh.club.service.SysClubService; import org.nutz.dao.Cnd; @@ -38,8 +36,6 @@ public class ClubCommonController { @Inject private SysClubService sysClubService; - @Inject - private SysRoleService sysRoleService; @At @SaCheckLogin @@ -62,9 +58,9 @@ public class ClubCommonController { public Result listClubByRole() { Cnd cnd = Cnd.NEW(); if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { - Sys_role sysRole = sysRoleService.getByCode(RoleConstant.CLUB_PRESIDENT); - List userRoles = sysClubService.dao().query(Sys_user_role.class, Cnd.where("userId", "=", SecurityUtil.getUserId()).and("roleId", "=", sysRole.getId())); - List myClubId = userRoles.stream().map(Sys_user_role::getClubId).toList(); + // 报销申请只允许选择当前用户已加入的协会,避免看到未加入的协会数据。 + List clubUsers = sysClubService.dao().query(ClubUser.class, Cnd.where("userId", "=", SecurityUtil.getUserId())); + List myClubId = clubUsers.stream().map(ClubUser::getClubId).distinct().toList(); cnd.and("c.id", "in", myClubId); } cnd.and("inst.state","=",ProcessInstanceStateEnum.FINISHED.getCode()); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayUseDetailServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayUseDetailServiceImpl.java index 9ff8382d..bc5e46d2 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayUseDetailServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayUseDetailServiceImpl.java @@ -1,6 +1,7 @@ package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub; import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.model.OutlayUseDetail; import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.school.model.OutlayManageSchool; import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayUseDetailService; @@ -21,49 +22,65 @@ public class OutlayUseDetailServiceImpl extends BaseServiceImpl @Override public void doDeleteDetail(String id, String outlayType) { - //更新预算表的金额 - updateOutlayManage(id, null,outlayType,false); - //更新完删除 + // 先回滚对应预算的已使用额度,再删除详情记录。 + updateOutlayManage(id, null, outlayType, false); delete(id); } @Override - public void doEditDetail(OutlayUseDetail outlayUseDetail,String outlayType) { - updateOutlayManage(outlayUseDetail.getId(), outlayUseDetail,outlayType,true); + public void doEditDetail(OutlayUseDetail outlayUseDetail, String outlayType) { + // 编辑详情时需要先回退旧金额,再叠加新金额,保证预算台账准确。 + updateOutlayManage(outlayUseDetail.getId(), outlayUseDetail, outlayType, true); update(outlayUseDetail); - } - /** - * 更新预算表 + * 同步预算主表的已使用额度,保证校工会、分工会、协会三类台账口径一致。 * - * @param id - * @param outlayType + * @param id 详情主键 + * @param outlayUseDetail 编辑后的详情对象,删除场景可为空 + * @param outlayType 预算类型:school/union/club + * @param isEdit 是否为编辑场景 */ - private void updateOutlayManage(String id, OutlayUseDetail outlayUseDetail,String outlayType,Boolean isEdit) { - //找到是哪一条的预算详情 + private void updateOutlayManage(String id, OutlayUseDetail outlayUseDetail, String outlayType, Boolean isEdit) { OutlayUseDetail detail = fetch(id); - //拿到预算详情的调整金额 - if (outlayType.equals("school")) { - //找到预算分配的记录 + if (detail == null) { + return; + } + if ("school".equals(outlayType)) { OutlayManageSchool manageSchool = dao().fetch(OutlayManageSchool.class, detail.getOutlayManageId()); + if (manageSchool == null) { + return; + } manageSchool.setUsedQuota(manageSchool.getUsedQuota().subtract(detail.getAdjustMoney())); - if (isEdit){ - //如果是修改,就先减去原来的值在加上现在新的值 + if (Boolean.TRUE.equals(isEdit) && outlayUseDetail != null) { manageSchool.setUsedQuota(manageSchool.getUsedQuota().add(outlayUseDetail.getAdjustMoney())); } update(manageSchool); - } else if (outlayType.equals("union")) { + return; + } + if ("union".equals(outlayType)) { OutlayManageUnion manageUnion = dao().fetch(OutlayManageUnion.class, detail.getOutlayManageId()); + if (manageUnion == null) { + return; + } manageUnion.setUsedQuota(manageUnion.getUsedQuota().subtract(detail.getAdjustMoney())); - if (isEdit){ - //如果是修改,就先减去原来的值在加上现在新的值 + if (Boolean.TRUE.equals(isEdit) && outlayUseDetail != null) { manageUnion.setUsedQuota(manageUnion.getUsedQuota().add(outlayUseDetail.getAdjustMoney())); } update(manageUnion); + return; + } + if ("club".equals(outlayType)) { + OutlayManageClub manageClub = dao().fetch(OutlayManageClub.class, detail.getOutlayManageId()); + if (manageClub == null) { + return; + } + manageClub.setUsedQuota(manageClub.getUsedQuota().subtract(detail.getAdjustMoney())); + if (Boolean.TRUE.equals(isEdit) && outlayUseDetail != null) { + manageClub.setUsedQuota(manageClub.getUsedQuota().add(outlayUseDetail.getAdjustMoney())); + } + update(manageClub); } } - - } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java index b53b50b6..7a799ff1 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java @@ -259,7 +259,14 @@ public class UnionReimburseApplyController { return Result.success(value); } - // 如果没有找到该社团的经费记录,返回0 - return Result.success(0.0); + // 协会在年度预算分配之后才创建时,不会自动生成当年预算记录,这里给出明确提示。 + return Result.error("该协会本年度未生成预算记录,请先在协会预算分配中补分配"); + } + + @At + @ApiOperation("统一查询经费余额") + @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR) + public Result getBudgetBalance(String reimburseFundSource, String clubId) { + return unionReimburseService.getBudgetBalance(reimburseFundSource, clubId); } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java index 5f3fa5f9..a6d6de41 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java @@ -15,7 +15,6 @@ import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; -import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.cri.SqlExpressionGroup; @@ -27,18 +26,15 @@ import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; -import java.util.Date; import java.util.List; @IocBean @At("/platform/unionReimburse/review") @Ok("json:full") -@Api("审核报销") +@Api("工会报销审核") @Slf4j public class UnionReimburseReviewController { - @Inject - private Dao dao; @Inject private UnionReimburseService unionReimburseService; @@ -98,42 +94,34 @@ public class UnionReimburseReviewController { } @At - @ApiOperation("审核") + @ApiOperation("审核报销") @Aop(TransAop.READ_COMMITTED) @SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR) - @SLog(tag = "审核工会报销", msg = "审核工会报销") + @SLog(tag = "工会报销审核", msg = "审核工会报销") public Result reviewTask(@Param("data") UnionReimburse unionReimburse, String submitType) { if (submitType == null || !List.of("3", "4", "5").contains(submitType)) { return Result.error("无效的审核操作类型"); } - UnionReimburse dbRecord = dao.fetch(UnionReimburse.class, unionReimburse.getId()); - if (dbRecord == null) { - return Result.error("记录不存在"); - } - dbRecord.setReviewTime(new Date()); - dbRecord.setStateId(Integer.parseInt(submitType)); - dbRecord.setReviewOpinion(unionReimburse.getReviewOpinion()); - dao.update(dbRecord); - return Result.success(); + return unionReimburseService.reviewApply(unionReimburse.getId(), unionReimburse.getReviewOpinion(), Integer.parseInt(submitType)); } @At @ApiOperation("一键审核") @Aop(TransAop.READ_COMMITTED) @SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR) - @SLog(tag = "一键审核", msg = "一键审核") + @SLog(tag = "工会报销审核", msg = "一键审核工会报销") public Result allReview() { try { List reimbursements = unionReimburseService.query(Cnd.where("stateId", "=", 2)); for (UnionReimburse reimbursement : reimbursements) { - reimbursement.setStateId(3); - reimbursement.setReviewTime(new Date()); - reimbursement.setReviewOpinion("通过"); - dao.update(reimbursement); + Result result = unionReimburseService.reviewApply(reimbursement.getId(), "通过", 3); + if (result.getCode() != 0) { + return result; + } } - return Result.success("一键审核完成,共处理 " + reimbursements.size() + " 条记录"); + return Result.success("一键审核完成,共处理" + reimbursements.size() + "条记录"); } catch (Exception e) { - log.error("一键审核失败"); + log.error("一键审核失败", e); return Result.error("一键审核失败: " + e.getMessage()); } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java index 546506e9..db5efe78 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java @@ -1,7 +1,7 @@ package com.budwk.app.zhgh.dayofficework.unionReimburse.service; -import com.budwk.app.base.service.BaseService; import com.budwk.app.base.result.Result; +import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse; import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm; import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO; @@ -9,7 +9,7 @@ import org.nutz.dao.sql.Sql; import java.util.List; -public interface UnionReimburseService extends BaseService { +public interface UnionReimburseService extends BaseService { Sql getSql(UnionReimbursePageForm pageForm); @@ -19,7 +19,7 @@ public interface UnionReimburseService extends BaseService { Result saveApply(UnionReimburse unionReimburse, int stateId); /** - * 查询申请表单详情,补齐发票明细回显数据。 + * 查询申请表单详情,并补齐发票明细回显数据。 */ UnionReimburse getApplyForm(String id); @@ -34,8 +34,17 @@ public interface UnionReimburseService extends BaseService { Result checkInvoiceDuplicate(String invoiceNo, String reimburseId); /** - * 识别单张发票文件,并在需要时调用腾讯云验真接口补齐票面信息。 + * 识别单张发票文件,并在需要时调用验真接口补齐票面信息。 */ Result recognizeInvoice(String fileId, Boolean verifySwitch); + /** + * 查询报销可用经费余额,同时校验是否已分配以及协会归属权限。 + */ + Result getBudgetBalance(String reimburseFundSource, String clubId); + + /** + * 审核报销,审核通过时扣减对应预算并新增预算使用详情。 + */ + Result reviewApply(String reimburseId, String reviewOpinion, int submitType); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java index 32f2c050..6d42fe24 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java @@ -8,6 +8,11 @@ import com.budwk.app.base.result.Result; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.sys.models.Sys_file; import com.budwk.app.sys.services.SysFileService; +import com.budwk.app.zhgh.club.model.ClubUser; +import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub; +import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.model.OutlayUseDetail; +import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.school.model.OutlayManageSchool; +import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.model.OutlayManageUnion; import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse; import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburseInvoiceDetail; import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm; @@ -123,6 +128,10 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i if (validateResult != null) { return validateResult; } + Result fundValidateResult = validateFundSourceBeforeSubmit(unionReimburse); + if (fundValidateResult != null) { + return fundValidateResult; + } Result duplicateResult = validateInvoiceDuplicate(unionReimburse); if (duplicateResult != null) { return duplicateResult; @@ -232,8 +241,80 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i } /** - * 统一规整发票明细中的文本和金额,避免空格、空数组等脏数据进入库表。 + * 按经费来源统一查询当前可用余额,未分配或权限不匹配时直接拦截。 */ + @Override + public Result getBudgetBalance(String reimburseFundSource, String clubId) { + if (StrUtil.isBlank(reimburseFundSource)) { + return Result.error("请选择经费来源"); + } + if ("UNION_REIMBURSE_FUND_SOURCE_1".equals(reimburseFundSource)) { + OutlayManageSchool outlayManageSchool = dao().fetch(OutlayManageSchool.class, + Cnd.where("year", "=", DateUtil.thisYear())); + if (outlayManageSchool == null) { + return Result.error("校工会经费未分配"); + } + return Result.success(formatMoney(outlayManageSchool.getTotalQuota().subtract(outlayManageSchool.getUsedQuota()))); + } + if ("UNION_REIMBURSE_FUND_SOURCE_2".equals(reimburseFundSource)) { + OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class, + Cnd.where("unionId", "=", SecurityUtil.getUnionId()).and("year", "=", DateUtil.thisYear())); + if (outlayManageUnion == null) { + return Result.error("分工会经费未分配"); + } + return Result.success(formatMoney(outlayManageUnion.getTotalQuota().subtract(outlayManageUnion.getUsedQuota()))); + } + if ("UNION_REIMBURSE_FUND_SOURCE_3".equals(reimburseFundSource)) { + if (StrUtil.isBlank(clubId)) { + return Result.error("请选择协会"); + } + List myClubIds = getCurrentUserClubIds(); + if (!isAdmin() && !myClubIds.contains(clubId)) { + return Result.error("当前用户无权使用该协会经费"); + } + OutlayManageClub outlayManageClub = dao().fetch(OutlayManageClub.class, + Cnd.where("clubId", "=", clubId).and("year", "=", DateUtil.thisYear())); + if (outlayManageClub == null) { + return Result.error("该协会本年度未生成预算记录,请先在协会预算分配中补分配"); + } + return Result.success(formatMoney(outlayManageClub.getTotalQuota().subtract(outlayManageClub.getUsedQuota()))); + } + return Result.error("不支持的经费来源"); + } + + /** + * 审核通过时统一校验余额、扣减预算并写入预算使用详情,避免控制器分散业务逻辑。 + */ + @Override + public Result reviewApply(String reimburseId, String reviewOpinion, int submitType) { + if (!List.of(3, 4, 5).contains(submitType)) { + return Result.error("无效的审核操作类型"); + } + UnionReimburse dbRecord = this.fetch(reimburseId); + if (dbRecord == null) { + return Result.error("记录不存在"); + } + if (submitType == 3) { + // 已通过的单据再次触发通过时直接返回,避免重复扣减预算。 + if (Integer.valueOf(3).equals(dbRecord.getStateId())) { + return Result.success(); + } + Result validateResult = validateFundSourceBeforeSubmit(dbRecord); + if (validateResult != null) { + return validateResult; + } + Result deductResult = deductBudgetAndSaveUseDetail(dbRecord); + if (deductResult != null && deductResult.getCode() != 0) { + return deductResult; + } + } + dbRecord.setReviewTime(new Date()); + dbRecord.setStateId(submitType); + dbRecord.setReviewOpinion(reviewOpinion); + dao().update(dbRecord); + return Result.success(); + } + private void normalizeInvoiceDetails(UnionReimburse unionReimburse) { if (unionReimburse.getInvoiceDetails() == null) { unionReimburse.setInvoiceDetails(new ArrayList<>()); @@ -289,15 +370,15 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i if (!isValidContact(unionReimburse.getMobile())) { return Result.error("请输入正确的联系方式"); } - if (StrUtil.isBlank(unionReimburse.getBankUserName())) { - return Result.error("请填写户名"); - } - if (StrUtil.isBlank(unionReimburse.getBankCardNumber())) { - return Result.error("请填写银行账号"); - } - if (StrUtil.isBlank(unionReimburse.getBankOfDeposit())) { - return Result.error("请填写开户行"); - } +// if (StrUtil.isBlank(unionReimburse.getBankUserName())) { +// return Result.error("请填写户名"); +// } +// if (StrUtil.isBlank(unionReimburse.getBankCardNumber())) { +// return Result.error("请填写银行账号"); +// } +// if (StrUtil.isBlank(unionReimburse.getBankOfDeposit())) { +// return Result.error("请填写开户行"); +// } if (StrUtil.isBlank(unionReimburse.getPaymentNotes())) { return Result.error("请填写支付内容"); } @@ -342,8 +423,159 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i } /** - * 同次提交和历史记录都要拦截重复发票,避免重复报销。 + * 提交和审核前统一校验预算是否已分配、余额是否充足。 */ + private Result validateFundSourceBeforeSubmit(UnionReimburse unionReimburse) { + Result balanceResult = getBudgetBalance(unionReimburse.getReimburseFundSource(), unionReimburse.getClubId()); + if (balanceResult == null || balanceResult.getCode() != 0) { + return balanceResult == null ? Result.error("经费余额校验失败") : balanceResult; + } + BigDecimal applyMoney = getApplyMoney(unionReimburse); + if (applyMoney.compareTo(BigDecimal.ZERO) <= 0) { + return Result.error("报销金额必须大于0"); + } + BigDecimal balance = new BigDecimal(String.valueOf(balanceResult.getData())); + if (balance.compareTo(applyMoney) < 0) { + return Result.error("经费余额不足,当前余额:" + formatMoney(balance)); + } + unionReimburse.setFundBalance(balance.doubleValue()); + return null; + } + + /** + * 审核通过后按经费来源扣减预算,并补写预算使用详情,便于后续台账追踪。 + */ + private Result deductBudgetAndSaveUseDetail(UnionReimburse unionReimburse) { + BigDecimal applyMoney = getApplyMoney(unionReimburse); + String reimburseFundSource = unionReimburse.getReimburseFundSource(); + if ("UNION_REIMBURSE_FUND_SOURCE_1".equals(reimburseFundSource)) { + OutlayManageSchool outlayManageSchool = dao().fetch(OutlayManageSchool.class, + Cnd.where("year", "=", DateUtil.thisYear())); + if (outlayManageSchool == null) { + return Result.error("校工会经费未分配"); + } + if (outlayManageSchool.getTotalQuota().subtract(outlayManageSchool.getUsedQuota()).compareTo(applyMoney) < 0) { + return Result.error("校工会经费余额不足"); + } + outlayManageSchool.setUsedQuota(outlayManageSchool.getUsedQuota().add(applyMoney)); + dao().updateIgnoreNull(outlayManageSchool); + insertUseDetail(unionReimburse, outlayManageSchool.getId(), applyMoney); + return Result.success(); + } + if ("UNION_REIMBURSE_FUND_SOURCE_2".equals(reimburseFundSource)) { + OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class, + Cnd.where("unionId", "=", unionReimburse.getUnionId()).and("year", "=", DateUtil.thisYear())); + if (outlayManageUnion == null) { + return Result.error("分工会经费未分配"); + } + if (outlayManageUnion.getTotalQuota().subtract(outlayManageUnion.getUsedQuota()).compareTo(applyMoney) < 0) { + return Result.error("分工会经费余额不足"); + } + outlayManageUnion.setUsedQuota(outlayManageUnion.getUsedQuota().add(applyMoney)); + dao().updateIgnoreNull(outlayManageUnion); + insertUseDetail(unionReimburse, outlayManageUnion.getId(), applyMoney); + return Result.success(); + } + if ("UNION_REIMBURSE_FUND_SOURCE_3".equals(reimburseFundSource)) { + OutlayManageClub outlayManageClub = dao().fetch(OutlayManageClub.class, + Cnd.where("clubId", "=", unionReimburse.getClubId()).and("year", "=", DateUtil.thisYear())); + if (outlayManageClub == null) { + return Result.error("该协会本年度未生成预算记录,请先在协会预算分配中补分配"); + } + if (outlayManageClub.getTotalQuota().subtract(outlayManageClub.getUsedQuota()).compareTo(applyMoney) < 0) { + return Result.error("协会经费余额不足"); + } + outlayManageClub.setUsedQuota(outlayManageClub.getUsedQuota().add(applyMoney)); + dao().updateIgnoreNull(outlayManageClub); + insertUseDetail(unionReimburse, outlayManageClub.getId(), applyMoney); + return Result.success(); + } + return Result.error("不支持的经费来源"); + } + + /** + * 一张报销单只写入一条预算使用详情,避免重复通过或重复点击造成重复明细。 + */ + private void insertUseDetail(UnionReimburse unionReimburse, String outlayManageId, BigDecimal applyMoney) { + OutlayUseDetail oldDetail = dao().fetch(OutlayUseDetail.class, Cnd.where("outlayReimburseId", "=", unionReimburse.getId())); + if (oldDetail != null) { + return; + } + OutlayUseDetail detail = new OutlayUseDetail(); + detail.setOutlayManageId(outlayManageId); + detail.setOutlayReimburseId(unionReimburse.getId()); + detail.setAdjustMoney(applyMoney); + detail.setAdjustReason(StrUtil.blankToDefault(unionReimburse.getPaymentNotes(), getReimburseProjectName(unionReimburse.getReimburseProject()))); + detail.setAdjustUserId(SecurityUtil.getUserId()); + detail.setAdjustUserName(SecurityUtil.getUserUsername()); + detail.setAdjustLoginName(SecurityUtil.getUserLoginname()); + detail.setProjectName(buildUseDetailProjectName(unionReimburse)); + detail.setActivityNumber(unionReimburse.getActivityNumber()); + detail.setActivityTime(buildUseDetailTime(unionReimburse)); + dao().insert(detail); + } + + private BigDecimal getApplyMoney(UnionReimburse unionReimburse) { + Double money = "UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject()) + ? unionReimburse.getCondolenceMoney() : unionReimburse.getMoney(); + return BigDecimal.valueOf(money == null ? 0D : money).setScale(2, RoundingMode.HALF_UP); + } + + private String buildUseDetailProjectName(UnionReimburse unionReimburse) { + if (StrUtil.isNotBlank(unionReimburse.getActivityName())) { + return unionReimburse.getActivityName(); + } + if (StrUtil.isNotBlank(unionReimburse.getCondolenceUserName())) { + return getReimburseProjectName(unionReimburse.getReimburseProject()) + "-" + unionReimburse.getCondolenceUserName(); + } + return StrUtil.blankToDefault(unionReimburse.getDocumentNo(), getReimburseProjectName(unionReimburse.getReimburseProject())); + } + + private String buildUseDetailTime(UnionReimburse unionReimburse) { + if (unionReimburse.getActivityTime() != null) { + return DateUtil.formatDate(unionReimburse.getActivityTime()); + } + if (unionReimburse.getCondolenceTime() != null) { + return DateUtil.formatDate(unionReimburse.getCondolenceTime()); + } + if (unionReimburse.getCreateTime() != null) { + return DateUtil.formatDate(unionReimburse.getCreateTime()); + } + return ""; + } + + private String getReimburseProjectName(String reimburseProject) { + if ("UNION_REIMBURSE_PROJECT_1".equals(reimburseProject)) { + return "慰问申请"; + } + if ("UNION_REIMBURSE_PROJECT_2".equals(reimburseProject)) { + return "文体活动申请"; + } + if ("UNION_REIMBURSE_PROJECT_3".equals(reimburseProject)) { + return "日常活动申请"; + } + if ("UNION_REIMBURSE_PROJECT_4".equals(reimburseProject)) { + return "专项活动申请"; + } + return "报销申请"; + } + + private Double formatMoney(BigDecimal money) { + return money == null ? 0D : money.setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + private List getCurrentUserClubIds() { + if (isAdmin()) { + return new ArrayList<>(); + } + List clubUsers = dao().query(ClubUser.class, Cnd.where("userId", "=", SecurityUtil.getUserId())); + return clubUsers.stream() + .map(ClubUser::getClubId) + .filter(StrUtil::isNotBlank) + .distinct() + .collect(Collectors.toList()); + } + private Result validateInvoiceDuplicate(UnionReimburse unionReimburse) { Map> currentInvoiceMap = new LinkedHashMap<>(); List details = unionReimburse.getInvoiceDetails(); diff --git a/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html b/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html index 40ddabc5..b1a3e1a3 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/unionReimburse/apply/index.html @@ -51,8 +51,66 @@ layout("/layouts/platform.html"){ - {{formData.userName}} + + + + + 校工会经费 + 分工会经费 + 协会经费 + + + + + + + + + + {{item.name}} + + + + + + + + + {{item.name}} + + + + {{formData.loginName}} + {{formData.userName}} + + + + + + + + + + + + + + @@ -62,24 +120,8 @@ layout("/layouts/platform.html"){ - - - - - {{item.name}} - - - - - - - - - {{item.name}} - - - - + + - - {{ viewData.condolenceUserName }}({{ viewData.condolenceLoginName }})({{ viewData.condolenceUnitName }}) - + + {{ viewData.mobile }} + - - {{ viewData.condolenceSex }} - + + {{ viewData.payerName }} + - - {{ viewData.condolenceBirthday && $moment(viewData.condolenceBirthday).isValid() ? $moment(viewData.condolenceBirthday).format('YYYY-MM-DD') : '' }} - + + {{ viewData.bankUserName }} + - - {{ viewData.condolenceIdCard }} - + + {{ viewData.bankOfDeposit }} + - - {{ viewData.condolenceMobile }} - + + {{ viewData.bankCardNumber }} + - - {{ viewData.typeName }} - + + {{ viewData.condolenceUserName }}({{ viewData.condolenceLoginName }})({{ viewData.condolenceUnitName }}) + - - {{ viewData.way }} - + + {{ viewData.condolenceSex }} + - - {{ viewData.condolenceMoney }} - - - - {{ viewData.realMoney }} - + + {{ viewData.condolenceBirthday && $moment(viewData.condolenceBirthday).isValid() ? $moment(viewData.condolenceBirthday).format('YYYY-MM-DD') : '' }} + - - - {{ viewData.activityName }} - + + {{ viewData.condolenceIdCard }} + - - {{ viewData.activityType }} - + + {{ viewData.condolenceMobile }} + - - {{ viewData.activityNumber }} - + + {{ viewData.typeName }} + - - {{ viewData.activityPlace }} - + + {{ viewData.way }} + - - {{ viewData.money }} - + + {{ formatMoney(viewData.condolenceMoney) }} + - - {{ viewData.realMoney }} - + + {{ formatMoney(viewData.realMoney) }} + - - {{ viewData.activityTime | dateFormat }} - + + {{ viewData.activityName }} + - - {{ viewData.invoiceNumber }} - + + {{ viewData.activityNumber }} + - - {{ viewData.condolenceTime | dateFormat }} - + + {{ viewData.activityPlace }} + - - {{ viewData.marryTime | dateFormat }} - + + {{ formatMoney(viewData.money) }} + - - {{ viewData.fertilityTime | dateFormat }} - + + {{ formatMoney(viewData.realMoney) }} + - - {{ viewData.hospitalCausation }} - + + {{ viewData.activityTime | dateFormat }} + - - - {{ $moment(viewData.hospitalizationStartTime).format('YYYY-MM-DD') }} 至 {{ $moment(viewData.hospitalizationEndTime).format('YYYY-MM-DD') }} - - 暂无住院时间 - + + {{ viewData.condolenceTime | dateFormat }} + - - {{ viewData.hospital }} - + + {{ viewData.marryTime | dateFormat }} + - - {{ viewData.hospitalCount }} - + + {{ viewData.fertilityTime | dateFormat }} + - - {{ viewData.deathTime | dateFormat }} - + + {{ viewData.hospitalCausation }} + - - {{ viewData.condolenceRelationship }} - + + + {{ $moment(viewData.hospitalizationStartTime).format('YYYY-MM-DD') }} 至 {{ $moment(viewData.hospitalizationEndTime).format('YYYY-MM-DD') }} + + 暂无住院时间 + - - {{ viewData.participants }} - + + {{ viewData.hospital }} + - - {{ viewData.paymentNotes }} - + + {{ viewData.hospitalCount }} + - - {{ viewData.notes }} - + + {{ viewData.deathTime | dateFormat }} + - - - 暂无附件 - - + + {{ viewData.condolenceRelationship }} + + + + {{ viewData.participants }} + + + + {{ viewData.paymentNotes }} + + + + {{ viewData.notes }} + + + + + 暂无附件 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
发票明细列表
+
+ 查看页按申请页结构展示发票明细,仅用于查看,不提供编辑操作。 +
+
+
+ + + + + + + + + + + + + + +
+
+
+
- + `, @@ -201,42 +281,69 @@ const unionReimburseInfo = { }, data() { return { + activeTabName: "basic", visible: false, - viewData: {}, + viewData: { + files: [], + invoiceDetails: [] + }, typeName: '', row: null } }, + computed: { + showInvoiceTab() { + return this.viewData.reimburseProject !== "UNION_REIMBURSE_PROJECT_1" + } + }, methods: { // 打开 onOpen(row) { this.row = row this.visible = true + this.activeTabName = "basic" this.getInfo() }, // 获取申请信息 getInfo() { this.$axios.post('/platform/unionReimburse/apply/info', {id: this.row.id}).then((res) => { if (res.code === 0) { - this.viewData = res.data + this.viewData = this.buildViewData(res.data) this.typeName = res.data.typeName || '' } }) }, - - // // 获取已办任务审批记录 - // getDoneTasks() { - // this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => { - // if (res.code === 0) { - // this.doneTasks = res.data - // } - // }) - // }, - // - // // 查看流程图 - // openChart(){ - // this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId,this.row.instanceId) - // } - + // 查看页面需要给附件和发票明细补默认值,避免空数据时组件渲染异常。 + buildViewData(data) { + const viewData = Object.assign({ + files: [], + invoiceDetails: [] + }, data || {}) + if (!Array.isArray(viewData.files)) { + viewData.files = [] + } + if (!Array.isArray(viewData.invoiceDetails)) { + viewData.invoiceDetails = [] + } + return viewData + }, + // 查看页金额统一保留两位小数,和申请页展示口径保持一致。 + formatMoney(value) { + if (value === null || value === undefined || value === "") { + return "" + } + const numberValue = Number(value) + if (Number.isNaN(numberValue)) { + return value + } + return numberValue.toFixed(2) + }, + // 查看流程图 + openChart() { + if (!this.row || !this.$refs.snakerChartRef) { + return + } + this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId) + } } }