package com.by4cloud.platformx.business.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.by4cloud.platformx.admin.api.entity.SysDept; import com.by4cloud.platformx.admin.api.feign.RemoteDeptService; import com.by4cloud.platformx.business.dto.AgingQueryDTO; import com.by4cloud.platformx.business.dto.PaymentConfirmAddDTO; import com.by4cloud.platformx.business.dto.SyncPaymentRecepitDTO; import com.by4cloud.platformx.business.entity.*; import com.by4cloud.platformx.business.mapper.*; import com.by4cloud.platformx.business.service.BusinessCustomerService; import com.by4cloud.platformx.business.service.ContractService; import com.by4cloud.platformx.business.service.PaymentConfirmService; import com.by4cloud.platformx.business.utils.SignHelper; import com.by4cloud.platformx.business.vo.AgingAnalysisVo; import com.by4cloud.platformx.business.vo.AgingCompanyVo; import com.by4cloud.platformx.business.vo.AgingVo; import com.by4cloud.platformx.common.core.util.R; import com.by4cloud.platformx.common.data.datascope.DataScope; import com.by4cloud.platformx.common.security.util.SecurityUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; ; /** * 收款确认 * * @author syt * @date 2026-04-29 11:33:26 */ @Slf4j @Service @RequiredArgsConstructor public class PaymentConfirmServiceImpl extends ServiceImpl implements PaymentConfirmService { private final ContractService contractService; private final BusinessCustomerService businessCustomerService; private final ContractPaymentScheduleMapper contractPaymentScheduleMapper; private final CurrentOverdueMapper currentOverdueMapper; private final HistoryOverdueMapper historyOverdueMapper; private final PaymentSlipMapper paymentSlipMapper; private final BipRequestRecordMapper bipRequestRecordMapper; private final ContractExecDateMapper contractExecDateMapper; private final MeterReadRecordMapper meterReadRecordMapper; private final BankStatementMapper bankStatementMapper; private final RemoteDeptService remoteDeptService; private final StringRedisTemplate redisTemplate; @Value("${bip.url}") private String url; @Value("${bip.codes}") private String codes; @Value("${bip.appKey}") private String appKey; @Value("${bip.appSecret}") private String appSecret; @Override public R add(PaymentConfirmAddDTO addDTO) { if (StrUtil.isNotBlank(addDTO.getContractNo())) { Contract contract = contractService.getOne(Wrappers.lambdaQuery().eq(Contract::getContractNo, addDTO.getContractNo())); if (ObjUtil.isNull(contract)) { return R.failed("合同查询失败,请检查合同编号"); } BusinessCustomer customer = businessCustomerService.getById(contract.getPartyAId()); if (ObjUtil.isNull(customer)) { return R.failed("客商查询失败,请检查客商"); } //转入 if (StrUtil.equals(addDTO.getInOrOut(), "1")) { //保存收款单 savePaymentSlip(addDTO,customer,contract); //保存收款单 savePaymentConfirm(addDTO,contract,customer); //处理收款 handleSlipToSchedule(addDTO,contract); //更新状态 updateContractStatus(addDTO,contract); } //转出 if (StrUtil.equals(addDTO.getInOrOut(), "2")) { return handleBackSlip(addDTO,contract,customer); } } return R.ok(); } private R handleBackSlip(PaymentConfirmAddDTO addDTO,Contract contract,BusinessCustomer customer) { PaymentConfirm lastConfirm = baseMapper.selectOne(Wrappers.lambdaQuery().eq(PaymentConfirm::getContractId, contract.getId()) .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1")); PaymentConfirm entity = new PaymentConfirm(); entity.setContractId(contract.getId()); entity.setContractName(contract.getContractName()); entity.setContractNo(contract.getContractNo()); entity.setBusGuestId(customer.getId()); entity.setBusGuestName(customer.getCompanyName()); entity.setBusinessType("退款"); entity.setTransationAmount(addDTO.getTransationAmount()); BigDecimal newtotal = addDTO.getTransationAmount().multiply(new BigDecimal("-1")).add(lastConfirm.getTotalAmount()); if (newtotal.compareTo(new BigDecimal("0")) > 0) { entity.setAdvanceAmount(newtotal); entity.setTotalAmount(newtotal); } else if (newtotal.compareTo(new BigDecimal("0")) == 0) { entity.setTotalAmount(newtotal); } else { return R.failed("当前客户余额为" + lastConfirm.getTotalAmount() + "不足以满足当前发生金额,无法操作"); } entity.setConfirmTime(new Date()); baseMapper.insert(entity); contract.setPaidAmount(contract.getPaidAmount().subtract(addDTO.getTransationAmount())); contract.setContractStatus(2); contractService.updateById(contract); return R.ok(); } private void updateContractStatus(PaymentConfirmAddDTO addDTO,Contract contract) { //非房屋租赁 水电 if (!StrUtil.equals(contract.getContractCategory(), "water_house")) { //更新合同已付款 BigDecimal paid = new BigDecimal("0"); if (ObjUtil.isNotNull(contract.getPaidAmount())) { paid = contract.getPaidAmount(); } paid = paid.add(addDTO.getTransationAmount()); contract.setPaidAmount(paid); if (paid.compareTo(contract.getAmount().subtract(contract.getDropAmount())) >= 0&&contract.getBillingAmout().compareTo(contract.getAmount())==0) { contract.setContractStatus(3); contract.setPaidAmount(contract.getAmount().subtract(contract.getDropAmount())); } contractService.updateById(contract); }else { List execDateList = contractExecDateMapper.selectList(Wrappers.lambdaQuery().eq(ContractExecDate::getContractId,contract.getId()) .eq(ContractExecDate::getGenFlag,"0")); if (CollUtil.isEmpty(execDateList)){ //更新合同已付款 BigDecimal paid = new BigDecimal("0"); if (ObjUtil.isNotNull(contract.getPaidAmount())) { paid = contract.getPaidAmount(); } paid = paid.add(addDTO.getTransationAmount()); contract.setPaidAmount(paid); if (contract.getBillingAmout().compareTo(contract.getAmount())==0&&paid.compareTo(contract.getAmount().subtract(contract.getDropAmount())) >= 0) { contract.setContractStatus(3); contract.setPaidAmount(contract.getAmount()); } contractService.updateById(contract); } } } private void handleSlipToSchedule(PaymentConfirmAddDTO addDTO,Contract contract) { //更新付款阶段付款 List scheduleList = contractPaymentScheduleMapper.selectList(Wrappers.lambdaQuery() .eq(ContractPaymentSchedule::getContractId, contract.getId()).ne(ContractPaymentSchedule::getPaymentStatus, 2).ne(ContractPaymentSchedule::getPaymentStatus, 3) .orderByAsc(ContractPaymentSchedule::getCreateTime)); if (CollUtil.isNotEmpty(scheduleList)) { BigDecimal remain = addDTO.getTransationAmount(); for (ContractPaymentSchedule schedule : scheduleList) { BigDecimal lastRemain = remain; remain = remain.subtract(StrUtil.equals(schedule.getPaymentStatus() + "", "0") ? schedule.getPlannedAmount() : schedule.getPlannedAmount().subtract(schedule.getActualAmount())); if (remain.compareTo(new BigDecimal("0")) >= 0) { schedule.setActualAmount(schedule.getPlannedAmount()); schedule.setPaymentDate(addDTO.getConfirmTime()); schedule.setPaymentStatus(2); contractPaymentScheduleMapper.updateById(schedule); //查询是否有当前逾期 CurrentOverdue currentOverdue = currentOverdueMapper.selectOne(Wrappers.lambdaQuery().eq(CurrentOverdue::getScheduleId, schedule.getId()) .eq(CurrentOverdue::getContractId, schedule.getContractId()).last("limit 1")); if (ObjUtil.isNotNull(currentOverdue)) { //新增逾期历史 HistoryOverdue historyOverdue = BeanUtil.copyProperties(currentOverdue, HistoryOverdue.class, "id"); historyOverdue.setPaymentTime(addDTO.getConfirmTime()); historyOverdue.setCompId(schedule.getCompId()); historyOverdueMapper.insert(historyOverdue); //删除当前逾期 currentOverdueMapper.deleteById(currentOverdue); } } else { schedule.setActualAmount(StrUtil.equals(schedule.getPaymentStatus() + "", "0") ? schedule.getPlannedAmount().add(remain) : schedule.getActualAmount().add(lastRemain)); schedule.setPaymentDate(addDTO.getConfirmTime()); schedule.setPaymentStatus(1); contractPaymentScheduleMapper.updateById(schedule); //查询是否有当前逾期 CurrentOverdue currentOverdue = currentOverdueMapper.selectOne(Wrappers.lambdaQuery().eq(CurrentOverdue::getScheduleId, schedule.getId()) .eq(CurrentOverdue::getContractId, schedule.getContractId()).last("limit 1")); if (ObjUtil.isNotNull(currentOverdue)) { //新增逾期历史 HistoryOverdue historyOverdue = BeanUtil.copyProperties(currentOverdue, HistoryOverdue.class, "id"); historyOverdue.setReceivableAmount(lastRemain); historyOverdue.setPaymentTime(addDTO.getConfirmTime()); historyOverdue.setCompId(schedule.getCompId()); historyOverdueMapper.insert(historyOverdue); //更新当前逾期 currentOverdue.setReceivableAmount(schedule.getPlannedAmount().subtract(schedule.getActualAmount())); currentOverdueMapper.updateById(currentOverdue); } break; } } } } private void savePaymentConfirm(PaymentConfirmAddDTO addDTO,Contract contract,BusinessCustomer customer) { PaymentConfirm lastConfirm = baseMapper.selectOne(Wrappers.lambdaQuery().eq(PaymentConfirm::getContractId, contract.getId()) .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1")); BigDecimal lastTotal = new BigDecimal("0"); if (ObjUtil.isNotNull(lastConfirm)) { lastTotal = lastConfirm.getTotalAmount(); } BigDecimal newtotal = addDTO.getTransationAmount().add(lastTotal); PaymentConfirm entity = new PaymentConfirm(); entity.setContractId(contract.getId()); entity.setContractName(contract.getContractName()); entity.setContractNo(addDTO.getContractNo()); if (ObjUtil.isNotNull(lastConfirm)) { entity.setScheduleId(lastConfirm.getScheduleId()); entity.setScheduleName(lastConfirm.getScheduleName()); } entity.setBusGuestId(customer.getId()); entity.setBusGuestName(customer.getCompanyName()); entity.setBusinessType("客户付款"); if (newtotal.compareTo(new BigDecimal("0")) > 0) { entity.setTransationAmount(addDTO.getTransationAmount()); entity.setAdvanceAmount(newtotal); entity.setTotalAmount(newtotal); entity.setConfirmTime(new Date()); baseMapper.insert(entity); } else if (newtotal.compareTo(new BigDecimal("0")) == 0) { entity.setTransationAmount(addDTO.getTransationAmount()); entity.setTotalAmount(newtotal); entity.setConfirmTime(new Date()); baseMapper.insert(entity); } else { entity.setTransationAmount(addDTO.getTransationAmount()); entity.setReceivableAmount(newtotal.multiply(new BigDecimal("-1"))); entity.setTotalAmount(newtotal); entity.setConfirmTime(new Date()); baseMapper.insert(entity); } } private void savePaymentSlip(PaymentConfirmAddDTO addDTO,BusinessCustomer customer,Contract contract) { //新增付款单 PaymentSlip slip = new PaymentSlip(); slip.setBusGuestId(customer.getId()); slip.setBusGuestName(customer.getCompanyName()); slip.setPayeeCompId(SecurityUtils.getUser().getCompId()); slip.setPaymentTime(addDTO.getConfirmTime()); slip.setPaymentAmount(addDTO.getTransationAmount()); slip.setPayType(addDTO.getPayType()); slip.setContractId(contract.getId()); paymentSlipMapper.insert(slip); } @Override public R syncPaymentRecepit(SyncPaymentRecepitDTO dto) { String accessToken = ""; if (redisTemplate.hasKey("BIP_TOKEN")) { accessToken = (String) redisTemplate.opsForValue().get("BIP_TOKEN"); } else { accessToken = getAccessToken(accessToken); } if (StrUtil.isEmpty(accessToken)) { return R.failed("获取token失败"); } String[] bipCodeArr = StrUtil.isNotBlank(dto.getBipCode())?dto.getBipCode().split(","):codes.split(","); for (String bipCode : bipCodeArr) { //入参 JSONObject params = new JSONObject(); if (ObjUtil.isNotEmpty(dto.getPageSize())){ params.put("pageSize",dto.getPageSize()); }else { params.put("pageSize",999); } if (ObjUtil.isNotEmpty(dto.getPageNum())){ params.put("pageNum",dto.getPageNum()); }else { params.put("pageNum",1); } if (StrUtil.isNotBlank(dto.getStartDate())){ params.put("begindate",dto.getStartDate()); }else { params.put("begindate", DateUtil.formatDate(DateUtil.offsetDay(new Date(), -1))); } if (StrUtil.isNotBlank(dto.getStartDate())){ params.put("enddate", dto.getEndDate()); }else { params.put("enddate", DateUtil.today()); } params.put("dc_flag","1");//1 借 2 贷 只查收款 //单据状态已审批 params.put("accentity_code", bipCode); String finalAccessToken = accessToken; new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } log.info("银行流水查询 Request:{}", params.toJSONString()); String result = HttpUtil.post(url + "/yonbip/ctm/cmp/bankReconciliationQuery?access_token=" + finalAccessToken, params.toJSONString()); log.info("银行流水查询 Response:{}", result); //保存请求记录 saveBipRequestRecord(params, "银行流水查询接口", result); JSONObject resultJson = JSONObject.parseObject(result); if (resultJson.containsKey("code") && resultJson.getString("code").equals("200")) { handleAndSave(bipCode, resultJson); } }).start(); } return R.ok(); } @Override public String getAccessToken(String accessToken) { long timestamp = System.currentTimeMillis(); Map paramMap = new HashMap<>(); paramMap.put("appKey", appKey); paramMap.put("timestamp", timestamp); String signature = null; try { signature = SignHelper.sign(paramMap, appSecret); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } String getTokenResult = HttpUtil.get(url + "/open-auth/selfAppAuth/getAccessToken?appKey=" + appKey + "×tamp=" + timestamp + "&signature=" + signature); log.info("Get access_token Response:{}", getTokenResult); JSONObject tokenJson = JSONObject.parseObject(getTokenResult); if (!tokenJson.containsKey("code") || !tokenJson.getString("code").equals("00000")) { log.error("Get access_token ERROR"); return ""; } if (ObjUtil.isNull(tokenJson.get("data")) || ObjUtil.isNull(JSONObject.parseObject(tokenJson.getString("data")))) { log.error("access_token value exception"); return ""; } accessToken = JSONObject.parseObject(tokenJson.getString("data")).getString("access_token"); log.info("access_token value:{}", accessToken); redisTemplate.opsForValue().set("BIP_TOKEN", accessToken, JSONObject.parseObject(tokenJson.getString("data")).getIntValue("expire") - 10, TimeUnit.SECONDS); return accessToken; } @Override public void saveBipRequestRecord(JSONObject params, String methodName, String result) { BipRequestRecord record = new BipRequestRecord(); record.setInterfaceName(methodName); record.setRequestParams(params.toJSONString()); record.setResponseParams(result); record.setResponseCode(JSONObject.parseObject(result).getString("code")); bipRequestRecordMapper.insert(record); } private void handleAndSave(String bipCode, JSONObject resultJson) { R r = remoteDeptService.selectDeptByBipCode(bipCode); if (!r.isOk()) { log.error("bipcode查询单位失败"); return; } SysDept dept = r.getData(); JSONObject dataJson = resultJson.getJSONObject("data"); if (ObjUtil.isNull(dataJson)) { log.error("返回结果中data解析失败"); return; } JSONArray recordList = dataJson.getJSONArray("recordList"); if (CollUtil.isEmpty(recordList)) { log.error("返回结果中recordList为空"); return; } log.info("==================开始处理银行流水信息==================="); recordList.stream().forEach(obj -> { JSONObject record = (JSONObject) obj; BusinessCustomer customer = businessCustomerService.getOne(Wrappers.lambdaQuery() .eq(BusinessCustomer::getCompanyName, record.getString("to_acct_name")) .eq(BusinessCustomer::getCompId,dept.getDeptId()) ); //保存付款单 Boolean codeFlag = saveBankStatement(customer, record,dept); if (!codeFlag) { log.error("付款单id:{},付款单已存在"); } }); log.info("==================结束处理银行流水信息==================="); } private Boolean saveBankStatement(BusinessCustomer customer, JSONObject record,SysDept dept) { BankStatement bankStatement = new BankStatement(); bankStatement.setBankSeqNo(record.getString("bank_seq_no")); bankStatement.setReceiveBankAcc(record.getString("bank_seq_no")); bankStatement.setReceiveBankAccAcc(record.getString("bankaccount_account")); bankStatement.setReceiveBankAccName(record.getString("bankaccount_acctName")); bankStatement.setPayBankNo(record.getString("to_acct_no")); bankStatement.setPayBankAccName(record.getString("to_acct_name")); bankStatement.setTranDate(DateUtil.parseDateTime(record.getString("tran_date"))); bankStatement.setTranAmount(record.getBigDecimal("tran_amt")); bankStatement.setDcFlag(record.getString("dc_flag")); bankStatement.setDataId(record.getLong("id")); bankStatement.setDataJson(record.toJSONString()); if (ObjUtil.isNotNull(customer)) { bankStatement.setBusGuestId(customer.getId()); } BankStatement statement = bankStatementMapper.selectOne(Wrappers.lambdaQuery().eq(BankStatement::getDataId,record.getLong("id"))); if (ObjUtil.isNotNull(statement)){ return false; } bankStatement.setRemark(record.getString("remark")); bankStatement.setCompId(dept.getDeptId()); bankStatementMapper.insert(bankStatement); return true; } @Override public R queryAllBusinessType() { return R.ok(baseMapper.selectList(Wrappers.lambdaQuery().select(PaymentConfirm::getBusinessType).isNotNull(PaymentConfirm::getBusinessType) .groupBy(PaymentConfirm::getBusinessType)).stream().map(PaymentConfirm::getBusinessType).collect(Collectors.toList())); } @Override public R queryAging() { List companys = contractPaymentScheduleMapper.selectAgingCompany(null, DataScope.of("comp_id")); if (CollUtil.isEmpty(companys)) { return R.failed("暂无账龄"); } List result = new ArrayList<>(); AgingQueryDTO queryDTO = new AgingQueryDTO(); List agingVoList = contractPaymentScheduleMapper.selectAgingTotal(null); companys.stream().forEach(company -> { BusinessCustomer businessCustomer = businessCustomerService.getById(company.getCompanyId()); AgingAnalysisVo agingAnalysisVo = new AgingAnalysisVo(); agingAnalysisVo.setCompanyName(businessCustomer.getCompanyName()); queryDTO.setCompanyId(company.getCompanyId()); List companyAgingVo = contractPaymentScheduleMapper.selectAgingTotal(queryDTO); //补全数据 if (CollUtil.isEmpty(companyAgingVo)) { companyAgingVo = new ArrayList<>(); AgingVo agingVo1 = new AgingVo(1L,"0-30天", new BigDecimal("0"), null); AgingVo agingVo2 = new AgingVo(2L,"31-60天", new BigDecimal("0"), null); AgingVo agingVo3 = new AgingVo(3L,"61-90天", new BigDecimal("0"), null); AgingVo agingVo4 = new AgingVo(4L,"91-180天", new BigDecimal("0"), null); AgingVo agingVo5 = new AgingVo(5L,"181-365天", new BigDecimal("0"), null); AgingVo agingVo6 = new AgingVo(6L,"1年以上", new BigDecimal("0"), null); companyAgingVo.add(agingVo1); companyAgingVo.add(agingVo2); companyAgingVo.add(agingVo3); companyAgingVo.add(agingVo4); companyAgingVo.add(agingVo5); companyAgingVo.add(agingVo6); } else { List overdueRanges = companyAgingVo.stream().map(AgingVo::getOverdueRange).collect(Collectors.toList()); if (!CollUtil.contains(overdueRanges, "0-30天")) { AgingVo agingVo1 = new AgingVo(1L,"0-30天", new BigDecimal("0"), null); companyAgingVo.add(agingVo1); } if (!CollUtil.contains(overdueRanges, "31-60天")) { AgingVo agingVo2 = new AgingVo(2L,"31-60天", new BigDecimal("0"), null); companyAgingVo.add(agingVo2); } if (!CollUtil.contains(overdueRanges, "61-90天")) { AgingVo agingVo3 = new AgingVo(3L,"61-90天", new BigDecimal("0"), null); companyAgingVo.add(agingVo3); } if (!CollUtil.contains(overdueRanges, "91-180天")) { AgingVo agingVo4 = new AgingVo(4L,"91-180天", new BigDecimal("0"), null); companyAgingVo.add(agingVo4); } if (!CollUtil.contains(overdueRanges, "181-365天")) { AgingVo agingVo5 = new AgingVo(5L,"181-365天", new BigDecimal("0"), null); companyAgingVo.add(agingVo5); } if (!CollUtil.contains(overdueRanges, "1年以上")) { AgingVo agingVo6 = new AgingVo(6L,"1年以上", new BigDecimal("0"), null); companyAgingVo.add(agingVo6); } } agingAnalysisVo.setAmount(companyAgingVo.stream().map(AgingVo::getTotalAmount).collect(Collectors.toList())); List finalCompanyAgingVo = companyAgingVo; agingAnalysisVo.setPercent(IntStream.range(0, agingVoList.size()) .mapToObj(i -> { BigDecimal part = finalCompanyAgingVo.get(i).getTotalAmount(); BigDecimal total = agingVoList.get(i).getTotalAmount(); if (total == null || total.compareTo(BigDecimal.ZERO) == 0) { return null; } return part.divide(total, 4, RoundingMode.HALF_UP) .multiply(new BigDecimal("100")) .setScale(2, RoundingMode.HALF_UP); }) .collect(Collectors.toList())); result.add(agingAnalysisVo); }); //合计 AgingAnalysisVo agingAnalysisVo = new AgingAnalysisVo(); agingAnalysisVo.setCompanyName("合计"); agingAnalysisVo.setAmount(agingVoList.stream().map(AgingVo::getTotalAmount).collect(Collectors.toList())); List percent = IntStream.range(0, agingAnalysisVo.getAmount().size()) .mapToObj(i -> { if (agingAnalysisVo.getAmount().get(i) == null || agingAnalysisVo.getAmount().get(i).compareTo(BigDecimal.ZERO) == 0) { return null; } else { return new BigDecimal("100"); } }) .collect(Collectors.toList()); agingAnalysisVo.setPercent(percent); result.add(agingAnalysisVo); return R.ok(result); } }