| | |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.core.domain.model.LoginUser; |
| | | import com.ruoyi.common.enums.ApplyTypeEnum; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.exception.base.BaseException; |
| | | import com.ruoyi.common.utils.HttpClientKit; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.bean.DtoConversionUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.project.common.FundNotifyMessageBuilder; |
| | | import com.ruoyi.project.common.IdGeneratorUtils; |
| | | import com.ruoyi.project.domain.*; |
| | | import com.ruoyi.project.domain.vo.*; |
| | |
| | | import com.ruoyi.project.service.*; |
| | | import com.ruoyi.system.mapper.SysUserMapper; |
| | | import com.ruoyi.system.mapper.SysUserRoleMapper; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.ObjectUtils; |
| | |
| | | public class ServiceFundServiceImpl extends ServiceImpl<ServiceFundMapper, ServiceFund> implements IServiceFundService { |
| | | @Autowired |
| | | ServiceFundMapper serviceFundMapper; |
| | | |
| | | /** 专家劳务费 9 条费用控制规则校验(同一案例累计) */ |
| | | @Autowired |
| | | private ExpertFeeValidateService expertFeeValidateService; |
| | | |
| | | @Autowired |
| | | private DingTalkService dingTalkService; |
| | |
| | | @Value("${spring.profiles.active}") |
| | | private String active; |
| | | |
| | | @Autowired |
| | | private ISysConfigService sysConfigService; |
| | | |
| | | /** |
| | | * 审批单是否上传医院协友(致远)系统:0=不上传(测试环境),1=上传(正式环境)。 |
| | | * |
| | | * <p>2026-10-09 新增:原来用 {@code spring.profiles.active == druid} 判断是否推送, |
| | | * 测试环境与正式环境无法通过参数区分。改为系统参数控制。 |
| | | */ |
| | | private static final String CFG_UPLOAD_OA = "sys.approval.upload.oa"; |
| | | |
| | | /** 参数缺省值:上传(保持正式环境原有行为) */ |
| | | private static final boolean DEFAULT_UPLOAD_OA = true; |
| | | |
| | | /** 审批单是否需要上传医院协友(致远)系统 */ |
| | | private boolean isUploadOaEnabled() { |
| | | String value = null; |
| | | try { |
| | | value = sysConfigService.selectConfigByKey(CFG_UPLOAD_OA); |
| | | } catch (Exception e) { |
| | | log.warn("读取参数 {} 失败,按默认值 {} 处理", CFG_UPLOAD_OA, DEFAULT_UPLOAD_OA, e); |
| | | } |
| | | if (value == null || value.trim().isEmpty()) { |
| | | return DEFAULT_UPLOAD_OA; |
| | | } |
| | | String v = value.trim(); |
| | | return "1".equals(v) || "true".equalsIgnoreCase(v) || "y".equalsIgnoreCase(v) || "on".equalsIgnoreCase(v); |
| | | } |
| | | |
| | | /** |
| | | * 致远系统流程退回 |
| | | * |
| | |
| | | String phone = null; |
| | | String applytype = null; |
| | | Long flowlevel = null; |
| | | //钉钉通知用的单据信息 |
| | | Double amount = null; |
| | | String donorName = null; |
| | | String billNo = null; |
| | | String travelers = null; |
| | | String reason = null; |
| | | if (serviceProcessVO.getApplytype().equals("0")) { |
| | | ServiceReimbursement serviceReimbursement = new ServiceReimbursement(); |
| | | serviceReimbursement.setId(StringUtils.isNotEmpty(serviceProcessVO.getBackCode()) ? Long.valueOf(serviceProcessVO.getBackCode()) : null); |
| | |
| | | phone = serviceReimbursementList.get(0).getPhone(); |
| | | flowlevel = serviceReimbursementList.get(0).getFlowlevel(); |
| | | applytype = "0"; |
| | | amount = serviceReimbursementList.get(0).getAmountrequested(); |
| | | billNo = FundNotifyMessageBuilder.billNo(serviceReimbursementList.get(0).getBh(), |
| | | serviceReimbursementList.get(0).getId()); |
| | | travelers = serviceReimbursementList.get(0).getTravelers(); |
| | | reason = serviceReimbursementList.get(0).getReason(); |
| | | } else { |
| | | ServiceFund serviceFund = new ServiceFund(); |
| | | serviceFund.setId(StringUtils.isNotEmpty(serviceProcessVO.getBackCode()) ? Long.valueOf(serviceProcessVO.getBackCode()) : null); |
| | |
| | | phone = serviceFunds.get(0).getPhone(); |
| | | flowlevel = serviceFunds.get(0).getFlowlevel(); |
| | | applytype = serviceFunds.get(0).getApplytype(); |
| | | amount = serviceFunds.get(0).getPretaxcost(); |
| | | donorName = serviceFunds.get(0).getDonorname(); |
| | | billNo = FundNotifyMessageBuilder.billNo(serviceFunds.get(0).getBh(), |
| | | serviceFunds.get(0).getId()); |
| | | } |
| | | |
| | | // //将操作保存到fundflow表中 |
| | |
| | | |
| | | |
| | | SysUser sysUser = sysUserService.selectUserByUserName(userNo); |
| | | sendMeg(serviceProcessVO, phone); |
| | | |
| | | //钉钉通知内容:单据信息(类型/报销人/金额/案例/编号)+ 审批人/审批时间/意见 |
| | | List<ConcurrentHashMap<String, Object>> notifyRows = FundNotifyMessageBuilder.newRows(); |
| | | FundNotifyMessageBuilder.addBillInfo(notifyRows, applytype, userName, amount, donorName, billNo); |
| | | FundNotifyMessageBuilder.addTravelInfo(notifyRows, travelers, reason); |
| | | FundNotifyMessageBuilder.addAuditResult(notifyRows, serviceProcessVO.getCheckusername(), |
| | | serviceProcessVO.getCheckTime(), |
| | | FundNotifyMessageBuilder.opinion(serviceProcessVO.getFlowconclusion(), |
| | | serviceProcessVO.getFlowcontent())); |
| | | sendMeg(serviceProcessVO, phone, notifyRows); |
| | | //发送信息保存 |
| | | ServiceSystemmessage serviceSystemmessage = new ServiceSystemmessage(); |
| | | serviceSystemmessage.setFundtype(1); |
| | |
| | | serviceSystemmessage.setUpdateBy(sysUser.getNickName()); |
| | | serviceSystemmessage.setIsread(0); |
| | | serviceSystemmessage.setMessagetype(1); |
| | | serviceSystemmessage.setMessagetitle("财务系统信息"); |
| | | serviceSystemmessage.setMessagetitle("OPO系统审批"); |
| | | serviceSystemmessage.setMessagecontent(serviceProcessVO.getFlowcontent()); |
| | | iServiceSystemmessageService.save(serviceSystemmessage); |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, String> checkFund(CheckFundVO checkFundVO, LoginUser loginUser) { |
| | | ServiceFund serviceFund = getById(checkFundVO.getFundid()); |
| | | Map<String, String> map = new HashMap<>(); |
| | |
| | | if (serviceFund == null) { |
| | | map.put(HttpStatus.NO_CONTENT + "", "费用编号不正确"); |
| | | return map; |
| | | } |
| | | |
| | | // 专家劳务费:审核“通过”前校验 9 条费用控制规则(同一案例累计);不通过则不阻断 |
| | | if (checkFundVO.getFlowconclusion() != null && checkFundVO.getFlowconclusion() == 1) { |
| | | ExpertFeeCheckResult expertFeeResult = expertFeeValidateService.validateBeforeCheck(checkFundVO.getFundid()); |
| | | if (expertFeeResult.hasError()) { |
| | | map.put(HttpStatus.ERROR + "", expertFeeResult.errorMessage()); |
| | | return map; |
| | | } |
| | | if (expertFeeResult.hasWarning()) { |
| | | // 规则 5、6 仅提示,不阻断审核 |
| | | log.warn("专家劳务费审核校验提示:fundId={},{}", checkFundVO.getFundid(), expertFeeResult.warningMessage()); |
| | | } |
| | | } |
| | | |
| | | Integer TotalLevel = 0; |
| | |
| | | Integer RecordStatus = serviceFund.getRecordstatus(); |
| | | Integer FlowLevel = serviceFund.getFlowlevel().intValue(); |
| | | Integer OriginalFlowLevel = FlowLevel; |
| | | //审批结论必填:原实现直接 == 1 拆箱,接口被直调传 null 会 NPE 500 |
| | | if (checkFundVO.getFlowconclusion() == null) { |
| | | map.put(HttpStatus.ERROR + "", "审批结论不能为空"); |
| | | return map; |
| | | } |
| | | if (checkFundVO.getFlowconclusion() == 1) { |
| | | CheckFlag = 1; |
| | | if (TotalLevel == 0) { |
| | |
| | | if (newSwitch == 1) { |
| | | aBoolean1 = sendData(serviceFund); |
| | | if (!aBoolean1) { |
| | | map.put("500", "发送失败"); |
| | | return map; |
| | | // 推送医院协友系统失败:整个审批不生效(事务回滚),允许下次重新审批 |
| | | log.error("费用审批单上传医院协友系统失败,审批不生效,fundId={},请重新审批", serviceFund.getId()); |
| | | throw new ServiceException("医院协友系统接收失败,本次审批未生效,请稍后重新审批"); |
| | | } |
| | | } |
| | | //保存审批流程表 |
| | |
| | | } |
| | | |
| | | SysUser sysUser = sysUserService.selectUserByUserName(serviceFund.getUserno()); |
| | | ArrayList<ConcurrentHashMap<String, Object>> contentList = new ArrayList<>(); |
| | | ConcurrentHashMap cmap = new ConcurrentHashMap(); |
| | | cmap.put("审批人:", loginUser.getUser().getUserName()); |
| | | cmap.put("审批时间:", new Date()); |
| | | cmap.put("意见:", "不通过"); |
| | | if (StringUtils.isNotBlank(checkFundVO.getFlowcontent())) cmap.put("意见:", checkFundVO.getFlowcontent()); |
| | | contentList.add(cmap); |
| | | try{ |
| | | DingTalkReqVo dingTalkReqVo = new DingTalkReqVo(); |
| | | dingTalkReqVo.setTitle("财务系统驳回信息"); |
| | | dingTalkReqVo.setNumber(sysUser.getPhonenumber()); |
| | | dingTalkReqVo.setContents(contentList); |
| | | dingTalkService.sendNotification(dingTalkReqVo); |
| | | }catch (Exception e){ |
| | | log.error("费用---财务系统驳回信息报错了", e); |
| | | //审批未通过:单据信息 + 审批人/审批时间/意见(意见 = "审批未通过。" + 驳回意见) |
| | | List<ConcurrentHashMap<String, Object>> contentList = FundNotifyMessageBuilder.newRows(); |
| | | FundNotifyMessageBuilder.addBillInfo(contentList, serviceFund.getApplytype(), |
| | | serviceFund.getUsername(), serviceFund.getPretaxcost(), serviceFund.getDonorname(), |
| | | FundNotifyMessageBuilder.billNo(serviceFund.getBh(), serviceFund.getId())); |
| | | FundNotifyMessageBuilder.addAuditResult(contentList, loginUser.getUser().getNickName(), new Date(), |
| | | FundNotifyMessageBuilder.opinion(FundNotifyMessageBuilder.CONCLUSION_REJECT, |
| | | checkFundVO.getFlowcontent())); |
| | | try { |
| | | DingTalkReqVo dingTalkReqVo = new DingTalkReqVo(); |
| | | dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE); |
| | | dingTalkReqVo.setNumber(sysUser.getPhonenumber()); |
| | | dingTalkReqVo.setContents(contentList); |
| | | dingTalkService.sendNotification(dingTalkReqVo); |
| | | } catch (Exception e) { |
| | | log.error("费用---审批未通过钉钉通知失败,fundId={}", serviceFund.getId(), e); |
| | | } |
| | | } |
| | | |
| | |
| | | //搞一个开关,别影响老流程 |
| | | aBoolean1 = sendData(serviceFund); |
| | | if (!aBoolean1) { |
| | | map.put("500", "发送失败"); |
| | | return map; |
| | | // 推送医院协友系统失败:整个审批不生效(事务回滚),允许下次重新审批 |
| | | log.error("费用审批单上传医院协友系统失败,审批不生效,fundId={},请重新审批", serviceFund.getId()); |
| | | throw new ServiceException("医院协友系统接收失败,本次审批未生效,请稍后重新审批"); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 审批单上传医院协友(致远)系统。 |
| | | * |
| | | * <p>2026-10-09:外层包一层兜底,保证**任何异常都不会向外抛**(原来 strRes 为 null 时 |
| | | * 会在 parseObject 后取 code 触发 NPE,把整个审批请求打成 500)。 |
| | | * 返回 false 表示推送失败,调用方按"审批不生效、允许下次重新审批"处理。 |
| | | */ |
| | | public Boolean sendData(ServiceFund serviceFund) { |
| | | if (!isUploadOaEnabled()) { |
| | | // 上传开关关闭(如测试环境):跳过上传并视为成功,绝不能因此阻断审批 |
| | | log.info("审批单上传医院协友系统已关闭(sys.approval.upload.oa != 1),跳过上传并视为成功,fundId={}", |
| | | serviceFund == null ? null : serviceFund.getId()); |
| | | return true; |
| | | } |
| | | try { |
| | | return doSendData(serviceFund); |
| | | } catch (Throwable t) { |
| | | log.error("费用审批单上传医院协友系统异常,审批不生效,fundId={}", |
| | | serviceFund == null ? null : serviceFund.getId(), t); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private Boolean doSendData(ServiceFund serviceFund) { |
| | | // fund和funddetail的数据获取 |
| | | addFundSharedInfo(serviceFund.getId()); |
| | | List<ServiceFundShared> fundShareInfos = serviceFundSharedMapper.getFundShareInfoById(serviceFund.getId()); |
| | |
| | | |
| | | map.put("data", data); |
| | | String strRes = null; |
| | | if (!active.equals("druid")) { |
| | | if (isUploadOaEnabled()) { |
| | | //如果是本地环境,就不调这个了 |
| | | String token = getToken(); |
| | | JSONObject json1 = JSONObject.parseObject(token); |
| | |
| | | } |
| | | } |
| | | log.error("------------------strRes的值为:{}", strRes); |
| | | JSONObject jsonObject = JSONObject.parseObject(strRes); |
| | | if (strRes == null || strRes.trim().isEmpty()) { |
| | | // 医院协友系统无返回(超时/异常/未配置):推送失败,由调用方回滚整个审批 |
| | | log.error("费用审批单上传医院协友系统失败:接口无返回内容,fundId={}", serviceFund.getId()); |
| | | return false; |
| | | } |
| | | JSONObject jsonObject = null; |
| | | try { |
| | | jsonObject = JSONObject.parseObject(strRes); |
| | | } catch (Exception e) { |
| | | log.error("费用审批单上传医院协友系统失败:返回内容无法解析,fundId={},strRes={}", |
| | | serviceFund.getId(), strRes, e); |
| | | return false; |
| | | } |
| | | if (jsonObject == null) { |
| | | log.error("费用审批单上传医院协友系统失败:返回内容为空,fundId={},strRes={}", |
| | | serviceFund.getId(), strRes); |
| | | return false; |
| | | } |
| | | String code = jsonObject.getString("code"); |
| | | if (!code.equals("0")) { |
| | | if (code == null || !"0".equals(code)) { |
| | | log.error("费用审批单上传医院协友系统失败:code={},fundId={},strRes={}", |
| | | code, serviceFund.getId(), strRes); |
| | | return false; |
| | | } |
| | | |
| | |
| | | serviceFunddetailShareds.get(i).setId(null); |
| | | //数据发送给财务 |
| | | try { |
| | | if (!active.equals("druid")) uploadOAFileAndUpdateDb(serviceFunddetailShareds.get(i)); |
| | | if (isUploadOaEnabled()) uploadOAFileAndUpdateDb(serviceFunddetailShareds.get(i)); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | log.error("Exception中入参的ID为:{},异常信息为 : {}", id, e.getMessage()); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public ExpertFeeCheckResult validateExpertFee(ServiceFundVO serviceFundVO) { |
| | | return expertFeeValidateService.validateBeforeSave(serviceFundVO); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long addOrUpdateNew(ServiceFundVO serviceFundVO) { |
| | | // 专家劳务费:保存前校验 9 条费用控制规则(同一案例累计) |
| | | ExpertFeeCheckResult expertFeeResult = expertFeeValidateService.validateBeforeSave(serviceFundVO); |
| | | if (expertFeeResult.hasError()) { |
| | | throw new BaseException(expertFeeResult.errorMessage()); |
| | | } |
| | | if (expertFeeResult.hasWarning()) { |
| | | // 规则 5、6 仅提示,不阻断保存;前端单独取用提示文案 |
| | | log.warn("专家劳务费保存校验提示:{}", expertFeeResult.warningMessage()); |
| | | } |
| | | |
| | | Long id = null; |
| | | //将数据保存到fund表 |
| | | serviceFundVO.setUploadStates(0); |
| | |
| | | return null; |
| | | } |
| | | |
| | | //总税前 |
| | | Double pretaxcosts = 0.00; |
| | | //总税后 |
| | | Double taxedcosts = 0.00; |
| | | //总税前、总税后(金额累加用 BigDecimal,避免浮点尾差) |
| | | BigDecimal pretaxcosts = BigDecimal.ZERO; |
| | | BigDecimal taxedcosts = BigDecimal.ZERO; |
| | | for (ServiceFunddetailVO serviceFunddetailVO : serviceFunddetails) { |
| | | serviceFunddetailVO.setFundid(id); |
| | | |
| | |
| | | if (!performance.isEmpty()) { |
| | | Double amount = performance.get("amount"); |
| | | Double taxedamount = performance.get("taxedamount"); |
| | | pretaxcosts = pretaxcosts + amount; |
| | | taxedcosts = taxedcosts + taxedamount; |
| | | if (amount != null) { |
| | | pretaxcosts = pretaxcosts.add(BigDecimal.valueOf(amount)); |
| | | } |
| | | if (taxedamount != null) { |
| | | taxedcosts = taxedcosts.add(BigDecimal.valueOf(taxedamount)); |
| | | } |
| | | } |
| | | serviceFundVO.setPretaxcost(pretaxcosts); |
| | | serviceFundVO.setTaxedcost(taxedcosts); |
| | | updateById(serviceFundVO); |
| | | } |
| | | // 合计在循环结束后统一回写,原来写在循环内会每行都 UPDATE 一次,且中间值被反复覆盖 |
| | | serviceFundVO.setPretaxcost(pretaxcosts.doubleValue()); |
| | | serviceFundVO.setTaxedcost(taxedcosts.doubleValue()); |
| | | updateById(serviceFundVO); |
| | | Map map = new HashMap(); |
| | | map.put("fundId", id); |
| | | return map; |
| | |
| | | Map<String, Object> map = new HashMap<String, Object>(); |
| | | map.put("userName", "opo"); |
| | | map.put("password", "4126407a-9821-4874-be41-6568abd6dbe5"); |
| | | map.put("loginName", "OPO系统"); |
| | | map.put("loginName", "OPO系统审批"); |
| | | |
| | | JSONObject jsonObj = new JSONObject(map); |
| | | |
| | |
| | | } |
| | | |
| | | |
| | | public void sendMeg(ServiceProcessVO serviceProcessVO, String phone) { |
| | | public void sendMeg(ServiceProcessVO serviceProcessVO, String phone, |
| | | List<ConcurrentHashMap<String, Object>> contentList) { |
| | | //记录操作记录 |
| | | ServiceFundflow serviceFundflow = new ServiceFundflow(); |
| | | |
| | |
| | | serviceFundflow.setFundid(StringUtils.isNotEmpty(serviceProcessVO.getBackCode()) ? Long.valueOf(serviceProcessVO.getBackCode()) : null); |
| | | fundflowService.save(serviceFundflow); |
| | | try { |
| | | ArrayList<ConcurrentHashMap<String, Object>> contentList = new ArrayList<>(); |
| | | ConcurrentHashMap map = new ConcurrentHashMap(); |
| | | map.put("审批人:", serviceProcessVO.getCheckusername()); |
| | | map.put("审批时间:", serviceProcessVO.getCheckTime()); |
| | | map.put("意见:", serviceProcessVO.getFlowcontent()); |
| | | contentList.add(map); |
| | | DingTalkReqVo dingTalkReqVo = new DingTalkReqVo(); |
| | | dingTalkReqVo.setTitle("财务系统信息"); |
| | | dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE); |
| | | dingTalkReqVo.setNumber(phone); |
| | | dingTalkReqVo.setContents(contentList); |
| | | log.info("钉钉发送消息:{}", dingTalkReqVo); |
| | | dingTalkService.sendNotification(dingTalkReqVo); |
| | | } catch (Exception e) { |
| | | log.error("财务系统信息---钉钉消息发送失败,不影响主流程", e); |
| | | log.error("报销审批钉钉消息发送失败,不影响主流程", e); |
| | | } |
| | | } |
| | | } |