shiyunteng
6 天以前 7ffef0059ddf3d4a82de4a4a8999b4b2429fcda6
feat:erp石工泵合同推送
6个文件已修改
5个文件已添加
658 ■■■■■ 已修改文件
platformx-boot/src/main/resources/application-dev.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractAddDTO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ErpRequestRecord.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/ErpRequestRecordMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractServiceImpl.java 471 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/OutBoundServiceImpl.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/utils/AESCFBUtils.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/utils/ItemGeneratorUtil.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/resources/mapper/ErpRequestRecordMapper.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-boot/src/main/resources/application-dev.yml
@@ -55,4 +55,12 @@
  sgb: 2056556196124250113
  jxc: 2056556317461270529
  tfgs: 2056556421857497090
  ymj: 2056556535640576002
  ymj: 2056556535640576002
#erp推送
erp:
  ymj:
  smj:
  sgb:
  jxc:
  tfgs:
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractAddDTO.java
@@ -15,6 +15,9 @@
@Data
public class ContractAddDTO {
    @Schema(description = "合同编号")
    private String contractNo;
    @Schema(description = "合同名称")
    private String contractName;
@@ -152,6 +155,9 @@
    @Schema(description = "经济事项 bip推送应收用")
    private String economicMatters;
    @Schema(description = "客户erp编码")
    private String partyACode;
    private List<ContractPaymentScheduleAddDTO> contractPaymentSchedule;
    private List<ContractSubjectMatterAddDTO> contractSubjectMatter;
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ErpRequestRecord.java
New file
@@ -0,0 +1,32 @@
package com.by4cloud.platformx.business.entity;
import com.by4cloud.platformx.common.data.mybatis.BaseModel;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.Data;
import org.hibernate.annotations.Table;
/**
 * 时间
 * syt
 */
@Data
@Entity//加了才能自动生成表
@Table(appliesTo="erp_request_record",comment = "erp请求记录")//给表加注释
@jakarta.persistence.Table(name = "erp_request_record")//数据库创建的表明
public class ErpRequestRecord extends BaseModel<ErpRequestRecord> {
    @Schema(description = "erp公司")
    @Column(columnDefinition="VARCHAR(128) comment '接口名称'")
    private String erpName;
    @Schema(description = "请求唯一标识")
    @Column(columnDefinition="VARCHAR(64) comment '请求唯一标识'")
    private String requestParams;
    @Schema(description = "回参")
    @Column(columnDefinition="text comment '回参'")
    private String responseParams;
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractController.java
@@ -262,4 +262,25 @@
        return contractService.delayOutApproval(dto);
    }
    /**
     * 新增合同-石工泵
     * @return R
     */
    @Operation(summary = "新增合同-石工泵" , description = "新增合同-石工泵" )
    @PostMapping("/addContractSgb")
    @SysLog("新增合同-石工泵" )
    public R addContractSgb(@RequestBody ContractAddDTO addDTO) {
        return contractService.addContractSgb(addDTO);
    }
    /**
     * 修改合同-石工泵
     * @return R
     */
    @Operation(summary = "修改合同-石工泵" , description = "修改合同-石工泵" )
    @PostMapping("/updateContractSgb")
    @SysLog("修改合同-石工泵" )
    public R updateContractSgb(@RequestBody ContractUpdateDTO updateDTO) {
        return contractService.updateContractSgb(updateDTO);
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/ErpRequestRecordMapper.java
New file
@@ -0,0 +1,9 @@
package com.by4cloud.platformx.business.mapper;
import com.by4cloud.platformx.business.entity.ErpRequestRecord;
import com.by4cloud.platformx.common.data.datascope.PlatformxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ErpRequestRecordMapper extends PlatformxBaseMapper<ErpRequestRecord> {
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractService.java
@@ -53,4 +53,8 @@
    Page pageScope(Page page, ContracQueryDTO queryDTO);
    void exportContractTFGSGYPMMWord(Long id, HttpServletResponse response);
    R addContractSgb(ContractAddDTO addDTO);
    R updateContractSgb(ContractUpdateDTO addDTO);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractServiceImpl.java
@@ -7,6 +7,9 @@
import cn.hutool.core.util.ArrayUtil;
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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -14,14 +17,12 @@
import com.by4cloud.platformx.admin.api.feign.RemoteDeptService;
import com.by4cloud.platformx.business.api.feign.RemoteFlowProcessService;
import com.by4cloud.platformx.business.constant.FlowNameEnum;
import com.by4cloud.platformx.business.dto.ContracQueryDTO;
import com.by4cloud.platformx.business.dto.ContractAddDTO;
import com.by4cloud.platformx.business.dto.ContractUpdateDTO;
import com.by4cloud.platformx.business.dto.DelayOutApprovalDTO;
import com.by4cloud.platformx.business.dto.*;
import com.by4cloud.platformx.business.entity.*;
import com.by4cloud.platformx.business.mapper.*;
import com.by4cloud.platformx.business.service.ContractService;
import com.by4cloud.platformx.business.utils.ContractNumberGenerator;
import com.by4cloud.platformx.business.utils.ItemGeneratorUtil;
import com.by4cloud.platformx.business.vo.ContractDetailVo;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.datascope.DataScope;
@@ -33,6 +34,8 @@
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
@@ -43,12 +46,14 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
 * @author cd
 * @description
 * @date 2026/4/29 14:07
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class ContractServiceImpl extends ServiceImpl<ContractMapper, Contract> implements ContractService {
@@ -65,6 +70,37 @@
    private final ProductMapper productMapper;
    private final RemoteDeptService remoteDeptService;
    private final ContractDelayOutMapper contractDelayOutMapper;
    private final ErpRequestRecordMapper erpRequestRecordMapper;
    @Value("${erp.ymj}")
    private String ymjErp;
    @Value("${erp.smj}")
    private String smjErp;
    @Value("${erp.sgb}")
    private String sgbErp;
    @Value("${erp.jxc}")
    private String jxcErp;
    @Value("${erp.tfgs}")
    private String tfgsErp;
    @Value("${dept.smj}")
    private String smj;
    @Value("${dept.sgb}")
    private String sgb;
    @Value("${dept.jxc}")
    private String jxc;
    @Value("${dept.tfgs}")
    private String tfgs;
    @Value("${dept.ymj}")
    private String ymj;
    @Override
    public R add(ContractAddDTO addDTO) {
@@ -76,9 +112,20 @@
        contract.setBillingStatus("0");
        contract.setErpPushFlag("0");
        if (StrUtil.isNotEmpty(addDTO.getContractCategory()) && (
                StrUtil.equals(addDTO.getContractCategory(), "ymjgkcpmm")||StrUtil.equals(addDTO.getContractCategory(), "ymjgypmm"))) {
            contract.setExpirationDate(DateUtil.offsetDay(contract.getSignDate(),contract.getDeliveryCycle()));
        if (ObjUtil.isNotNull(addDTO.getDeliveryCycle())){
            contract.setExpirationDate(DateUtil.offsetDay(addDTO.getSignDate(),addDTO.getDeliveryCycle()));
        }
        if (!StrUtil.equals(addDTO.getContractCategory(), "water_house")){
            // 假设 list 是你的 List<ContractSubjectMatterAddDTO>
            Optional<ContractSubjectMatterAddDTO> maxDateObj = addDTO.getContractSubjectMatter().stream()
                    .filter(item -> item.getPlannedDeliveryDate() != null) // 过滤掉日期为空的项,避免 NullPointerException
                    .max(Comparator.comparing(ContractSubjectMatterAddDTO::getPlannedDeliveryDate));
            if (maxDateObj.isPresent()) {
                contract.setExpirationDate(maxDateObj.get().getPlannedDeliveryDate());
            }
        }
        List<Contract> contracts = baseMapper.selectList(Wrappers.<Contract>lambdaQuery().eq(Contract::getContractNo, contract.getContractNo()));
        while (ArrayUtil.isNotEmpty(contracts.toArray())) {
@@ -176,6 +223,21 @@
            contract.setContractNo(ContractNumberGenerator.generateContractNumber());
            contracts = baseMapper.selectList(Wrappers.<Contract>lambdaQuery().eq(Contract::getContractNo, contract.getContractNo()));
        }
        if (ObjUtil.isNotNull(updateDTO.getDeliveryCycle())){
            contract.setExpirationDate(DateUtil.offsetDay(updateDTO.getSignDate(),updateDTO.getDeliveryCycle()));
        }
        if (!StrUtil.equals(updateDTO.getContractCategory(), "water_house")){
            // 假设 list 是你的 List<ContractSubjectMatterAddDTO>
            Optional<ContractSubjectMatterAddDTO> maxDateObj = updateDTO.getContractSubjectMatter().stream()
                    .filter(item -> item.getPlannedDeliveryDate() != null) // 过滤掉日期为空的项,避免 NullPointerException
                    .max(Comparator.comparing(ContractSubjectMatterAddDTO::getPlannedDeliveryDate));
            if (maxDateObj.isPresent()) {
                contract.setExpirationDate(maxDateObj.get().getPlannedDeliveryDate());
            }
        }
        baseMapper.updateById(contract);
        if (ArrayUtil.isNotEmpty(updateDTO.getContractSubjectMatter())) {
@@ -272,10 +334,10 @@
        Contract contract = baseMapper.selectById(id);
        contract.setContractStatus(2);
        baseMapper.updateById(contract);
        if (StrUtil.isNotEmpty(contract.getContractCategory()) && StrUtil.equals(contract.getContractCategory(), "water_house")) {
            List<ContractSubjectMatter> subjectMatterList = contractSubjectMatterMapper.selectList(Wrappers.<ContractSubjectMatter>lambdaQuery()
                    .eq(ContractSubjectMatter::getContractId,id));
        List<ContractSubjectMatter> subjectMatterList = contractSubjectMatterMapper.selectList(Wrappers.<ContractSubjectMatter>lambdaQuery()
                .eq(ContractSubjectMatter::getContractId,id));
        if (StrUtil.isNotEmpty(contract.getContractCategory()) && StrUtil.equals(contract.getContractCategory(), "water_house")) {
            if (ArrayUtil.isNotEmpty(subjectMatterList.toArray())) {
                for (ContractSubjectMatter contractSubjectMatter: subjectMatterList)  {
                    if (StrUtil.equals(contract.getExecFrequency(), "1")) {
@@ -474,6 +536,85 @@
//            contractPaymentScheduleMapper.updateById(fitstSchedule);
//        }
        }
        //erp推送
        JSONObject request = new JSONObject();
        BusinessCustomer customer = businessCustomerMapper.selectById(contract.getPartyAId());
        request.put("contractNo",contract.getContractNo());
        request.put("partyA",contract.getPartyA());
        request.put("partyACode",customer.getErpCompanyCode());
        if (StrUtil.equals(contract.getPartyBId()+"",ymj)&&StrUtil.isNotEmpty(ymjErp)){
            request.put("customerCode",customer.getErpCompanyCode());
            request.put("currencyId","RMB");
            request.put("signDate",DateUtil.format(contract.getSignDate(),DatePattern.NORM_DATETIME_PATTERN));
            request.put("dateDeliver",DateUtil.format(contract.getExpirationDate(),DatePattern.NORM_DATETIME_PATTERN));
            request.put("moneySum",contract.getAmount());
            request.put("signManner","面签");
            request.put("salesman",contract.getCreateBy());
            if (ArrayUtil.isNotEmpty(subjectMatterList.toArray())){
                JSONArray subjectMatter = new JSONArray();
                subjectMatterList.stream().forEach(contractSubjectMatter -> {
                    JSONObject subjectMatterItem = new JSONObject();
                    subjectMatterItem.put("itemNo",ItemGeneratorUtil.nextId());
                    subjectMatterItem.put("salePrice",contractSubjectMatter.getUnitPrice());
                    subjectMatterItem.put("qtyRequired",contractSubjectMatter.getQuantity());
                    subjectMatterItem.put("money",contractSubjectMatter.getQuantity().multiply(contractSubjectMatter.getUnitPrice()));
                    Product product = productMapper.selectOne(Wrappers.<Product>lambdaQuery().eq(Product::getCompId,contract.getCompId())
                            .eq(Product::getErpCode,contractSubjectMatter.getMaterialCode()));
                    if (ObjUtil.isNotNull(product)){
                        subjectMatterItem.put("taxRate",new BigDecimal(product.getTaxRate()).divide(new BigDecimal("100")));
                    }
                    subjectMatterItem.put("dateDeliver",ObjUtil.isNotNull(contractSubjectMatter.getPlannedDeliveryDate())?
                            DateUtil.format(contractSubjectMatter.getPlannedDeliveryDate(),DatePattern.NORM_DATETIME_PATTERN):
                            DateUtil.format(contract.getExpirationDate(),DatePattern.NORM_DATETIME_PATTERN));
                    subjectMatter.add(subjectMatterItem);
                });
                request.put("details",subjectMatter);
            }
            log.info("一煤机合同推送erp入参:{}",request.toJSONString());
            String result = HttpUtil.post(ymjErp,request.toJSONString());
            log.info("一煤机合同推送erp回参:{}",result);
            //保存请求记录
            saveErpRequestRecord("ymj",request.toJSONString(),result);
            //更新合同erp推送标识
            contract.setErpPushFlag("1");
            baseMapper.updateById(contract);
        }
        if (StrUtil.equals(contract.getPartyBId()+"",smj)&&StrUtil.isNotEmpty(smjErp)){
            log.info("石煤机合同推送erp入参:{}",request.toJSONString());
            String result = HttpUtil.post(ymjErp,request.toJSONString());
            log.info("石煤机合同推送erp回参:{}",result);
            //保存请求记录
            saveErpRequestRecord("smj",request.toJSONString(),result);
            //更新合同erp推送标识
            contract.setErpPushFlag("1");
            baseMapper.updateById(contract);
        }
        if (StrUtil.equals(contract.getPartyBId()+"",jxc)&&StrUtil.isNotEmpty(jxcErp)){
            log.info("机械厂合同推送erp入参:{}",request.toJSONString());
            String result = HttpUtil.post(ymjErp,request.toJSONString());
            log.info("机械厂合同推送erp回参:{}",result);
            //保存请求记录
            saveErpRequestRecord("jxc",request.toJSONString(),result);
            //更新合同erp推送标识
            contract.setErpPushFlag("1");
            baseMapper.updateById(contract);
        }
        if (StrUtil.equals(contract.getPartyBId()+"",tfgs)&&StrUtil.isNotEmpty(tfgsErp)){
            //通方公司通过系统录入
        }
        if (StrUtil.equals(contract.getPartyBId()+"",sgb)&&StrUtil.isNotEmpty(sgbErp)){
            //石工泵主动推送系统
        }
    }
    private void saveErpRequestRecord(String ymj, String toJSONString, String result) {
        ErpRequestRecord record = new ErpRequestRecord();
        record.setErpName(ymj);
        record.setRequestParams(toJSONString);
        record.setResponseParams(result);
        erpRequestRecordMapper.insert(record);
    }
    private void savePaymentConfirm(Contract contract, ContractPaymentSchedule schedule) {
@@ -574,41 +715,43 @@
                overdueConfirm.setScheduleName(contractPaymentSchedule.getStageName());
                PaymentConfirm newLastConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId, contract.getId())
                        .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
                overdueConfirm.setTransationAmount(StrUtil.equals(contractPaymentSchedule.getPaymentStatus() + "", "0") ?
                        contractPaymentSchedule.getPlannedAmount() :
                        contractPaymentSchedule.getPlannedAmount().subtract(contractPaymentSchedule.getActualAmount()));
                overdueConfirm.setReceivableAmount(newLastConfirm.getTotalAmount().multiply(new BigDecimal("-1")));
                overdueConfirm.setOverdueAmount(overdueConfirm.getTransationAmount());
                overdueConfirm.setTotalAmount(newLastConfirm.getTotalAmount());
                overdueConfirm.setCompId(contract.getCompId());
                //判断当前阶段是否已生成上阶段应收超期
                PaymentConfirm oiverdueConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getScheduleId, contractPaymentSchedule.getId())
                        .eq(PaymentConfirm::getBusinessType, "应收超期"));
                if (ObjUtil.isNull(oiverdueConfirm) && overdueConfirm.getTransationAmount().compareTo(new BigDecimal("0")) > 0) {
                    paymentConfirmMapper.insert(overdueConfirm);
                }
                //当前逾期
                CurrentOverdue currentOverdue = new CurrentOverdue();
                currentOverdue.setBusGuestId(contract.getPartyAId());
                currentOverdue.setBusGuestName(contract.getPartyA());
                currentOverdue.setContractId(contractPaymentSchedule.getContractId());
                currentOverdue.setContractName(contractPaymentSchedule.getContractName());
                currentOverdue.setScheduleId(contractPaymentSchedule.getId());
                currentOverdue.setScheduleName(contractPaymentSchedule.getStageName());
                currentOverdue.setCompId(contract.getCompId());
                currentOverdue.setContractExpirTime(contractPaymentSchedule.getEffectiveEndDate());
                currentOverdue.setReceivableAmount(StrUtil.equals(contractPaymentSchedule.getPaymentStatus() + "", "0") ?
                        contractPaymentSchedule.getPlannedAmount() : contractPaymentSchedule.getPlannedAmount().subtract(contractPaymentSchedule.getActualAmount()));
                currentOverdue.setOverdueDuration(BigDecimal.valueOf(DateUtil.betweenDay(contractPaymentSchedule.getEffectiveEndDate(), new Date(), true)));
                CurrentOverdue overdue = currentOverdueMapper.selectOne(Wrappers.<CurrentOverdue>lambdaQuery().eq(CurrentOverdue::getContractId, currentOverdue.getContractId())
                        .eq(CurrentOverdue::getScheduleId, currentOverdue.getScheduleId()).last("limit 1"));
                if (ObjUtil.isNull(overdue)) {
                    if (currentOverdue.getReceivableAmount().compareTo(new BigDecimal("0")) > 0) {
                        currentOverdueMapper.insert(currentOverdue);
                if(ObjUtil.isNotNull(newLastConfirm)){
                    overdueConfirm.setTransationAmount(StrUtil.equals(contractPaymentSchedule.getPaymentStatus() + "", "0") ?
                            contractPaymentSchedule.getPlannedAmount() :
                            contractPaymentSchedule.getPlannedAmount().subtract(contractPaymentSchedule.getActualAmount()));
                    overdueConfirm.setReceivableAmount(newLastConfirm.getTotalAmount().multiply(new BigDecimal("-1")));
                    overdueConfirm.setOverdueAmount(overdueConfirm.getTransationAmount());
                    overdueConfirm.setTotalAmount(newLastConfirm.getTotalAmount());
                    overdueConfirm.setCompId(contract.getCompId());
                    //判断当前阶段是否已生成上阶段应收超期
                    PaymentConfirm oiverdueConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getScheduleId, contractPaymentSchedule.getId())
                            .eq(PaymentConfirm::getBusinessType, "应收超期"));
                    if (ObjUtil.isNull(oiverdueConfirm) && overdueConfirm.getTransationAmount().compareTo(new BigDecimal("0")) > 0) {
                        paymentConfirmMapper.insert(overdueConfirm);
                    }
                } else {
                    overdue.setOverdueDuration(BigDecimal.valueOf(DateUtil.betweenDay(contractPaymentSchedule.getEffectiveEndDate(), new Date(), true)));
                    currentOverdueMapper.updateById(overdue);
                    //当前逾期
                    CurrentOverdue currentOverdue = new CurrentOverdue();
                    currentOverdue.setBusGuestId(contract.getPartyAId());
                    currentOverdue.setBusGuestName(contract.getPartyA());
                    currentOverdue.setContractId(contractPaymentSchedule.getContractId());
                    currentOverdue.setContractName(contractPaymentSchedule.getContractName());
                    currentOverdue.setScheduleId(contractPaymentSchedule.getId());
                    currentOverdue.setScheduleName(contractPaymentSchedule.getStageName());
                    currentOverdue.setCompId(contract.getCompId());
                    currentOverdue.setContractExpirTime(contractPaymentSchedule.getEffectiveEndDate());
                    currentOverdue.setReceivableAmount(StrUtil.equals(contractPaymentSchedule.getPaymentStatus() + "", "0") ?
                            contractPaymentSchedule.getPlannedAmount() : contractPaymentSchedule.getPlannedAmount().subtract(contractPaymentSchedule.getActualAmount()));
                    currentOverdue.setOverdueDuration(BigDecimal.valueOf(DateUtil.betweenDay(contractPaymentSchedule.getEffectiveEndDate(), new Date(), true)));
                    CurrentOverdue overdue = currentOverdueMapper.selectOne(Wrappers.<CurrentOverdue>lambdaQuery().eq(CurrentOverdue::getContractId, currentOverdue.getContractId())
                            .eq(CurrentOverdue::getScheduleId, currentOverdue.getScheduleId()).last("limit 1"));
                    if (ObjUtil.isNull(overdue)) {
                        if (currentOverdue.getReceivableAmount().compareTo(new BigDecimal("0")) > 0) {
                            currentOverdueMapper.insert(currentOverdue);
                        }
                    } else {
                        overdue.setOverdueDuration(BigDecimal.valueOf(DateUtil.betweenDay(contractPaymentSchedule.getEffectiveEndDate(), new Date(), true)));
                        currentOverdueMapper.updateById(overdue);
                    }
                }
            });
        }
@@ -1253,4 +1396,244 @@
        }
    }
    @Override
    public R addContractSgb(ContractAddDTO addDTO) {
        if (ArrayUtil.isEmpty(addDTO.getContractSubjectMatter().toArray())){
            return R.failed("请添加标的物");
        }
        if (ArrayUtil.isEmpty(addDTO.getContractPaymentSchedule().toArray())){
            return R.failed("请添加付款阶段或付款阶段缺少");
        }
        if (!isValidPaymentStages(addDTO.getContractPaymentSchedule())){
            return R.failed("付款阶段信息异常");
        }
        if (addDTO.getContractPaymentSchedule().stream().map(item->item.getPaymentRatio()).reduce(BigDecimal.ZERO,BigDecimal::add)
                .compareTo(new BigDecimal("100.00"))!=0){
            return R.failed("付款阶段比例异常");
        }
        Contract contract = BeanUtil.copyProperties(addDTO, Contract.class);
        contract.setPartyBId(SecurityUtils.getUser().getCompId());
        contract.setPartyB(SecurityUtils.getUser().getCompName());
        contract.setContractStatus(2);
        contract.setPaidAmount(new BigDecimal("0"));
        contract.setBillingStatus("0");
        contract.setErpPushFlag("1");
        if (ObjUtil.isNotNull(addDTO.getDeliveryCycle())){
            contract.setExpirationDate(DateUtil.offsetDay(addDTO.getSignDate(),addDTO.getDeliveryCycle()));
        }
        if (!StrUtil.equals(addDTO.getContractCategory(), "water_house")){
            // 假设 list 是你的 List<ContractSubjectMatterAddDTO>
            Optional<ContractSubjectMatterAddDTO> maxDateObj = addDTO.getContractSubjectMatter().stream()
                    .filter(item -> item.getPlannedDeliveryDate() != null) // 过滤掉日期为空的项,避免 NullPointerException
                    .max(Comparator.comparing(ContractSubjectMatterAddDTO::getPlannedDeliveryDate));
            if (maxDateObj.isPresent()) {
                contract.setExpirationDate(maxDateObj.get().getPlannedDeliveryDate());
            }
        }
        baseMapper.insert(contract);
        if (ArrayUtil.isNotEmpty(addDTO.getContractSubjectMatter())) {
            addDTO.getContractSubjectMatter().stream().forEach(contractSubjectMatterAddDTO -> {
                ContractSubjectMatter subjectMatter = BeanUtil.copyProperties(contractSubjectMatterAddDTO, ContractSubjectMatter.class);
                subjectMatter.setContractId(contract.getId());
                subjectMatter.setContractName(contract.getContractName());
                subjectMatter.setDeliveredQuantity(new BigDecimal("0"));
                subjectMatter.setDeliveryStatus(0);
                subjectMatter.setTotalAmount(contractSubjectMatterAddDTO.getQuantity().multiply(contractSubjectMatterAddDTO.getUnitPrice()));
                contractSubjectMatterMapper.insert(subjectMatter);
                //水电类必须有初次表号
                if (StrUtil.isNotEmpty(contractSubjectMatterAddDTO.getMeterReadCode()) &&
                        ObjUtil.isNotNull(contractSubjectMatterAddDTO.getMeterReadNum())) {
                    MeterReadRecord record = BeanUtil.copyProperties(contractSubjectMatterAddDTO, MeterReadRecord.class,"id");
                    record.setContractId(contract.getId());
                    record.setMatterId(subjectMatter.getId());
                    record.setContractName(contract.getContractName());
                    record.setBusGuestId(contract.getPartyAId());
                    record.setBusGuestName(contract.getPartyA());
                    record.setMeterReadTime(new Date());
                    meterReadRecordMapper.insert(record);
                }
                if (StrUtil.isNotEmpty(addDTO.getContractCategory()) && StrUtil.equals(addDTO.getContractCategory(), "ymjcg")
                        &&ObjUtil.isNotNull(subjectMatter.getPlannedDeliveryDate())&&ObjUtil.isNotNull(contract.getExpirationDate())
                        &&DateUtil.compare(contract.getExpirationDate(),subjectMatter.getPlannedDeliveryDate())>0) {
                    contract.setExpirationDate(subjectMatter.getPlannedDeliveryDate());
                    baseMapper.updateById(contract);
                }
            });
        }
        if (ArrayUtil.isNotEmpty(addDTO.getContractPaymentSchedule())) {
            AtomicInteger index = new AtomicInteger(1);
            addDTO.getContractPaymentSchedule().stream().forEach(contractPaymentScheduleAddDTO -> {
                int currentIndex = index.getAndIncrement();
                ContractPaymentSchedule schedule = BeanUtil.copyProperties(contractPaymentScheduleAddDTO, ContractPaymentSchedule.class);
                if (contractPaymentScheduleAddDTO.getStageName().equals("合同签订")) {
                    schedule.setEffectiveEndDate(DateUtil.offsetDay(contract.getSignDate(), contractPaymentScheduleAddDTO.getAgreedDays()));
                }
                if (ObjUtil.isNotNull(schedule.getEffectiveDate())) {
                    if (schedule.getEffectiveDate().before(DateUtil.date())) {
                        schedule.setFulfillmentStatus(1);
                    } else {
                        schedule.setFulfillmentStatus(0);
                    }
                }
                if (contractPaymentScheduleAddDTO.getPaymentRatio().compareTo(new BigDecimal("0")) > 0) {
                    schedule.setPaymentStatus(0);
                } else {
                    schedule.setPaymentStatus(3);
                }
                schedule.setContractId(contract.getId());
                schedule.setContractName(contract.getContractName());
                schedule.setPlannedAmount(contract.getAmount().multiply(schedule.getPaymentRatio().divide(new BigDecimal("100"))));
                schedule.setStageOrder(currentIndex);
                contractPaymentScheduleMapper.insert(schedule);
                if (contractPaymentScheduleAddDTO.getStageName().equals("货到签收")) {
                    contract.setArrivalScheduleId(schedule.getId());
                    baseMapper.updateById(contract);
                }
                if (contractPaymentScheduleAddDTO.getStageName().equals("调试完成或验收")) {
                    contract.setAcceptScheduleId(schedule.getId());
                    baseMapper.updateById(contract);
                }
            });
        }
        return R.ok(contract.getId());
    }
    @Override
    public R updateContractSgb(ContractUpdateDTO updateDTO) {
        if (ArrayUtil.isEmpty(updateDTO.getContractSubjectMatter().toArray())){
            return R.failed("请添加标的物");
        }
        if (ArrayUtil.isEmpty(updateDTO.getContractPaymentSchedule().toArray())){
            return R.failed("请添加付款阶段或付款阶段缺少");
        }
        if (!isValidPaymentStages(updateDTO.getContractPaymentSchedule())){
            return R.failed("付款阶段信息异常");
        }
        if (updateDTO.getContractPaymentSchedule().stream().map(item->item.getPaymentRatio()).reduce(BigDecimal.ZERO,BigDecimal::add)
                .compareTo(new BigDecimal("100.00"))!=0){
            return R.failed("付款阶段比例异常");
        }
        Contract contract = BeanUtil.copyProperties(updateDTO, Contract.class);
        if (ObjUtil.isNotNull(updateDTO.getDeliveryCycle())){
            contract.setExpirationDate(DateUtil.offsetDay(updateDTO.getSignDate(),updateDTO.getDeliveryCycle()));
        }
        if (!StrUtil.equals(updateDTO.getContractCategory(), "water_house")){
            // 假设 list 是你的 List<ContractSubjectMatterAddDTO>
            Optional<ContractSubjectMatterAddDTO> maxDateObj = updateDTO.getContractSubjectMatter().stream()
                    .filter(item -> item.getPlannedDeliveryDate() != null) // 过滤掉日期为空的项,避免 NullPointerException
                    .max(Comparator.comparing(ContractSubjectMatterAddDTO::getPlannedDeliveryDate));
            if (maxDateObj.isPresent()) {
                contract.setExpirationDate(maxDateObj.get().getPlannedDeliveryDate());
            }
        }
        baseMapper.updateById(contract);
        if (ArrayUtil.isNotEmpty(updateDTO.getContractSubjectMatter())) {
            contractSubjectMatterMapper.delete(Wrappers.<ContractSubjectMatter>lambdaQuery().eq(ContractSubjectMatter::getContractId, contract.getId()));
            updateDTO.getContractSubjectMatter().stream().forEach(contractSubjectMatterAddDTO -> {
                ContractSubjectMatter subjectMatter = BeanUtil.copyProperties(contractSubjectMatterAddDTO, ContractSubjectMatter.class);
                subjectMatter.setContractId(contract.getId());
                subjectMatter.setContractName(contract.getContractName());
                subjectMatter.setDeliveredQuantity(new BigDecimal("0"));
                subjectMatter.setDeliveryStatus(0);
                contractSubjectMatterMapper.insert(subjectMatter);
                //水电类必须有初次表号
                if (StrUtil.isNotEmpty(contractSubjectMatterAddDTO.getMeterReadCode()) &&
                        ObjUtil.isNotNull(contractSubjectMatterAddDTO.getMeterReadNum())) {
                    meterReadRecordMapper.delete(Wrappers.<MeterReadRecord>lambdaQuery().eq(MeterReadRecord::getContractId, contract.getId()));
                    MeterReadRecord record = BeanUtil.copyProperties(contractSubjectMatterAddDTO, MeterReadRecord.class,"id");
                    record.setContractId(contract.getId());
                    record.setMatterId(subjectMatter.getId());
                    record.setContractName(contract.getContractName());
                    record.setBusGuestId(contract.getPartyAId());
                    record.setBusGuestName(contract.getPartyA());
                    record.setMeterReadTime(new Date());
                    meterReadRecordMapper.insert(record);
                }
            });
        }
        if (ArrayUtil.isNotEmpty(updateDTO.getContractPaymentSchedule())) {
            contractPaymentScheduleMapper.delete(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId, contract.getId()));
            AtomicInteger index = new AtomicInteger(1);
            updateDTO.getContractPaymentSchedule().stream().forEach(contractPaymentScheduleAddDTO -> {
                int currentIndex = index.getAndIncrement();
                ContractPaymentSchedule schedule = BeanUtil.copyProperties(contractPaymentScheduleAddDTO, ContractPaymentSchedule.class);
                schedule.setContractId(contract.getId());
                schedule.setContractName(contract.getContractName());
                schedule.setPlannedAmount(contract.getAmount().multiply(schedule.getPaymentRatio().divide(new BigDecimal("100"))));
                schedule.setStageOrder(currentIndex);
                if (contractPaymentScheduleAddDTO.getPaymentRatio().compareTo(new BigDecimal("0")) > 0) {
                    schedule.setPaymentStatus(0);
                } else {
                    schedule.setPaymentStatus(3);
                }
                contractPaymentScheduleMapper.insert(schedule);
                if (contractPaymentScheduleAddDTO.getStageName().equals("货到签收")) {
                    contract.setArrivalScheduleId(schedule.getId());
                    baseMapper.updateById(contract);
                }
                if (contractPaymentScheduleAddDTO.getStageName().equals("调试完成或验收")) {
                    contract.setAcceptScheduleId(schedule.getId());
                    baseMapper.updateById(contract);
                }
            });
        }
        return R.ok();
    }
    // 定义标准的五个阶段
    private static final Set<String> VALID_STAGES = new HashSet<>(Arrays.asList(
            "合同签订",
            "发货前",
            "货到签收",
            "调试完成或验收",
            "质保金"
    ));
    /**
     * 判断付款阶段列表是否符合标准
     * @param scheduleList 付款阶段列表
     * @return true 如果包含且仅包含标准的五个阶段,false 否则
     */
    public static boolean isValidPaymentStages(List<ContractPaymentScheduleAddDTO> scheduleList) {
        if (scheduleList == null || scheduleList.isEmpty()) {
            return false;
        }
        // 1. 提取所有非空的 stageName
        List<String> actualStages = scheduleList.stream()
                .map(ContractPaymentScheduleAddDTO::getStageName)
                .filter(stage -> stage != null && !stage.trim().isEmpty())
                .map(String::trim)
                .collect(Collectors.toList());
        // 2. 基本数量检查:必须正好是5个
        if (actualStages.size() != VALID_STAGES.size()) {
            return false;
        }
        // 3. 内容检查:所有实际阶段都必须在标准集合中
        // 使用 Set 去重后比较,防止列表中有重复阶段但总数凑够5个的情况
        Set<String> actualStageSet = new HashSet<>(actualStages);
        // 检查实际集合大小是否也为5(确保无重复)且包含于标准集合
        return VALID_STAGES.equals(actualStageSet);
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/OutBoundServiceImpl.java
@@ -388,10 +388,19 @@
        contractOutBound.setInvoiceStatus("0");
        contractOutBound.setInvoiceNum(new BigDecimal("0"));
        contractOutBoundMapper.insert(contractOutBound);
        if (StrUtil.isEmpty(contract.getContractCategory())) {
            if (DateUtil.compare(addDTO.getOutBoundTime(), contract.getExpirationDate()) > 0) {
        if (!StrUtil.equals(contract.getContractCategory(),"water_house")) {
            // 1. 确定用于比较的基准日期(优先级:计划交货日期 > 合同到期日期)
            Date deadline = subjectMatter.getPlannedDeliveryDate();
            if (ObjUtil.isNull(deadline)) {
                deadline = contract.getExpirationDate();
            }
            // 2. 统一判断是否逾期
            // 注意:需确保 deadline 不为 null,防止空指针异常
            if (deadline != null && DateUtil.compare(addDTO.getOutBoundTime(), deadline) > 0) {
                saveOverdueOutBound(contract, addDTO, subjectMatter);
            }
        }
    }
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/utils/AESCFBUtils.java
New file
@@ -0,0 +1,52 @@
package com.by4cloud.platformx.business.utils;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AESCFBUtils {
    /**
     * AES CFB NoPadding 加密
     * @param src 明文
     * @param keyWord 密钥字符串
     * @return Base64 密文
     */
    public static String encrypt(String src, String keyWord) throws Exception {
        // 1. 处理 Key 和 IV (UTF-8 字节数组)
        byte[] keyBytes = keyWord.getBytes("UTF-8");
        // AES 要求 Key/IV 长度为 16 字节 (128位)。
        // CryptoJS 行为:若不足可能补零,若超过可能截断或使用全部(取决于版本/模式内部处理)。
        // 安全做法:统一处理为 16 字节
        byte[] fixedKey = new byte[16];
        System.arraycopy(keyBytes, 0, fixedKey, 0, Math.min(keyBytes.length, 16));
        SecretKeySpec keySpec = new SecretKeySpec(fixedKey, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(fixedKey); // IV 等于 Key
        // 2. 初始化 Cipher
        Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        // 3. 加密
        byte[] encryptedBytes = cipher.doFinal(src.getBytes("UTF-8"));
        // 4. 返回 Base64 字符串
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    public static void main(String[] args) throws Exception {
//        System.out.println(encrypt("123456","pigxpigxpigxpigx"));
        // 或者自定义 WorkerId 和 DataCenterId
        Snowflake snowflake = IdUtil.createSnowflake(1, 1);
        for (int i = 0; i < 1150; i++) {
            System.out.println(snowflake.nextId());
        }
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/utils/ItemGeneratorUtil.java
New file
@@ -0,0 +1,22 @@
package com.by4cloud.platformx.business.utils;
import java.util.concurrent.atomic.AtomicLong;
import java.text.DecimalFormat;
public class ItemGeneratorUtil {
    private static final AtomicLong counter = new AtomicLong(0);
    private static final String PREFIX = "ITEM";
    // 定义格式:至少3位,不足补0
    private static final DecimalFormat formatter = new DecimalFormat("000");
    public static String nextId() {
        long num = counter.incrementAndGet();
        return PREFIX + formatter.format(num);
    }
    public static void main(String[] args) {
        System.out.println(nextId()); // ITEM001
        System.out.println(nextId()); // ITEM002
        System.out.println(nextId()); // ITEM003
    }
}
platformx-business-finance-biz/src/main/resources/mapper/ErpRequestRecordMapper.xml
New file
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.by4cloud.platformx.business.mapper.ErpRequestRecordMapper">
  <resultMap id="bipRequestRecordMap" type="com.by4cloud.platformx.business.entity.ErpRequestRecord">
        <id property="id" column="id"/>
        <result property="compId" column="comp_id"/>
        <result property="erpName" column="erp_name"/>
        <result property="requestParams" column="request_params"/>
        <result property="responseParams" column="response_params"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="delFlag" column="del_flag"/>
  </resultMap>
</mapper>