Browse Source

新增客户合同到期查询&去掉竞价配置关联站点

fuyuchuan 6 giờ trước cách đây
mục cha
commit
cb792a609a

+ 12 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/controller/web/ContractController.java

@@ -4,6 +4,7 @@ import com.usky.common.core.bean.ApiResult;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.vpp.service.VppContractService;
 import com.usky.vpp.service.VppContractTemplateService;
+import com.usky.vpp.service.vo.VppContractExpireStatVO;
 import com.usky.vpp.service.vo.VppContractRequestVO;
 import com.usky.vpp.service.vo.VppContractResponseVO;
 import com.usky.vpp.service.vo.VppContractTemplateRequestVO;
@@ -111,7 +112,7 @@ public class ContractController {
      * templateId     模板ID
      * contractType   合同类型
      * customerId     客户ID
-     * contractStatus 合同状态
+     * contractStatus 合同状态 0草稿 1审核中 2待生效 3已生效 4已驳回 5已到期 6已终止 7即将到期(三月内过期)
      * current        页码
      * size           页大小
      */
@@ -130,6 +131,16 @@ public class ContractController {
                 templateId, contractType, customerId, contractStatus, current, size));
     }
 
+    /**
+     * 按客户最新合同统计已到期 / 即将到期数量
+     * <p>租户隔离;每个客户仅取创建时间最新的一份合同,返回已到期、即将到期(近三个月)两类数量。
+     * 列表请调用分页接口:contractStatus=5 查已到期,contractStatus=7 查即将到期。</p>
+     */
+    @GetMapping("/expireStat")
+    public ApiResult<VppContractExpireStatVO> expireStat() {
+        return ApiResult.success(vppContractService.expireStat());
+    }
+
     /**
      * 删除合同(软删除)
      */

+ 0 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppBiddingConfig.java

@@ -44,8 +44,6 @@ public class VppBiddingConfig implements Serializable {
     private BigDecimal maxPrice;
     @TableField("min_price")
     private BigDecimal minPrice;
-    @TableField("site_ids")
-    private String siteIds;
     @TableField("tenant_id")
     private Integer tenantId;
 

+ 1 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/domain/VppDrEvent.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;

+ 8 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/VppContractService.java

@@ -1,6 +1,7 @@
 package com.usky.vpp.service;
 
 import com.usky.common.core.bean.CommonPage;
+import com.usky.vpp.service.vo.VppContractExpireStatVO;
 import com.usky.vpp.service.vo.VppContractRequestVO;
 import com.usky.vpp.service.vo.VppContractResponseVO;
 
@@ -52,4 +53,11 @@ public interface VppContractService {
      * 下载合同文件
      */
     void download(Long id, HttpServletResponse response);
+
+    /**
+     * 按客户最新合同统计已到期 / 即将到期(近三个月)数量
+     * <p>租户隔离;每个客户仅取创建时间最新的一份合同,再按到期日分类统计数量。
+     * 列表请走分页接口,按 contractStatus=5(已到期)/ 7(即将到期)查询。</p>
+     */
+    VppContractExpireStatVO expireStat();
 }

+ 6 - 67
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppBiddingConfigServiceImpl.java

@@ -6,28 +6,22 @@ import com.usky.common.core.bean.CommonPage;
 import com.usky.common.core.exception.BusinessException;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.vpp.domain.VppBiddingConfig;
-import com.usky.vpp.domain.VppSite;
 import com.usky.vpp.mapper.VppBiddingConfigMapper;
-import com.usky.vpp.mapper.VppSiteMapper;
 import com.usky.vpp.service.VppBiddingConfigService;
 import com.usky.vpp.service.vo.BiddingConfigRequest;
-import com.usky.vpp.service.vo.BiddingConfigSiteVO;
 import com.usky.vpp.service.vo.BiddingConfigVO;
 import com.usky.vpp.util.VppAuditHelper;
 import com.usky.vpp.util.VppBiddingConfigHelper;
 import com.usky.vpp.util.VppPageHelper;
-import com.usky.vpp.util.VppReportHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 
 import java.time.LocalDateTime;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.stream.Collectors;
 
 @Service
@@ -35,8 +29,6 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
 
     @Autowired
     private VppBiddingConfigMapper biddingConfigMapper;
-    @Autowired
-    private VppSiteMapper siteMapper;
 
     @Override
     public CommonPage<BiddingConfigVO> page(String configName,
@@ -65,24 +57,23 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
 
         Page<VppBiddingConfig> result = biddingConfigMapper.selectPage(page, wrapper);
         List<BiddingConfigVO> records = result.getRecords().stream()
-                .map(config -> toVo(config, false))
+                .map(this::toVo)
                 .collect(Collectors.toList());
         return new CommonPage<>(records, result.getTotal(), result.getCurrent(), result.getSize());
     }
 
     @Override
     public BiddingConfigVO get(Long id) {
-        return toVo(requireConfig(id), true);
+        return toVo(requireConfig(id));
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long create(BiddingConfigRequest request) {
         validateRequest(request);
-        List<Long> siteIds = validateAndNormalizeSiteIds(request.getSiteIds(), resolveCurrentTenantId());
 
         VppBiddingConfig config = new VppBiddingConfig();
-        fillConfigFromRequest(config, request, siteIds);
+        fillConfigFromRequest(config, request);
         VppAuditHelper.fillCreate(config);
         biddingConfigMapper.insert(config);
         return config.getId();
@@ -93,9 +84,8 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
     public void update(Long id, BiddingConfigRequest request) {
         VppBiddingConfig config = requireConfig(id);
         validateRequest(request);
-        List<Long> siteIds = validateAndNormalizeSiteIds(request.getSiteIds(), resolveCurrentTenantId());
 
-        fillConfigFromRequest(config, request, siteIds);
+        fillConfigFromRequest(config, request);
         VppAuditHelper.fillUpdate(config);
         biddingConfigMapper.updateById(config);
     }
@@ -117,8 +107,7 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
     }
 
     private void fillConfigFromRequest(VppBiddingConfig config,
-                                       BiddingConfigRequest request,
-                                       List<Long> siteIds) {
+                                       BiddingConfigRequest request) {
         config.setConfigName(request.getConfigName().trim());
         config.setEffectiveStart(request.getEffectiveStart());
         config.setEffectiveEnd(request.getEffectiveEnd());
@@ -129,10 +118,9 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
         config.setAdvanceCloseHours(request.getAdvanceCloseHours());
         config.setMaxPrice(request.getMaxPrice());
         config.setMinPrice(request.getMinPrice());
-        config.setSiteIds(VppReportHelper.toJson(siteIds));
     }
 
-    private BiddingConfigVO toVo(VppBiddingConfig config, boolean withSites) {
+    private BiddingConfigVO toVo(VppBiddingConfig config) {
         BiddingConfigVO vo = new BiddingConfigVO();
         vo.setId(config.getId());
         vo.setConfigName(config.getConfigName());
@@ -150,30 +138,10 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
                 VppBiddingConfigHelper.resolveAdvanceCloseHoursLabel(config.getAdvanceCloseHours()));
         vo.setMaxPrice(config.getMaxPrice());
         vo.setMinPrice(config.getMinPrice());
-        List<Long> siteIds = VppReportHelper.parseSiteIds(config.getSiteIds());
-        vo.setSiteIds(siteIds);
         vo.setUpdateTime(config.getUpdateTime());
-        if (withSites) {
-            vo.setSites(loadSiteBriefs(siteIds));
-        }
         return vo;
     }
 
-    private List<BiddingConfigSiteVO> loadSiteBriefs(List<Long> siteIds) {
-        if (siteIds == null || siteIds.isEmpty()) {
-            return Collections.emptyList();
-        }
-        Map<Long, String> siteNameMap = siteMapper.selectBatchIds(siteIds).stream()
-                .filter(site -> !VppAuditHelper.isDeleted(site.getDeleteFlag()))
-                .collect(Collectors.toMap(VppSite::getId, VppSite::getSiteName, (left, right) -> left));
-        return siteIds.stream().map(siteId -> {
-            BiddingConfigSiteVO item = new BiddingConfigSiteVO();
-            item.setSiteId(siteId);
-            item.setSiteName(siteNameMap.get(siteId));
-            return item;
-        }).collect(Collectors.toList());
-    }
-
     private void validateRequest(BiddingConfigRequest request) {
         if (request == null) {
             throw new BusinessException("请求体不能为空");
@@ -205,35 +173,6 @@ public class VppBiddingConfigServiceImpl implements VppBiddingConfigService {
             throw new BusinessException("最低价格不能为空");
         }
         VppBiddingConfigHelper.validatePriceRange(request.getMinPrice(), request.getMaxPrice());
-        if (request.getSiteIds() == null || request.getSiteIds().isEmpty()) {
-            throw new BusinessException("请至少选择一个关联站点");
-        }
-    }
-
-    private List<Long> validateAndNormalizeSiteIds(List<Long> siteIds, Integer tenantId) {
-        if (siteIds == null || siteIds.isEmpty()) {
-            throw new BusinessException("请至少选择一个关联站点");
-        }
-        List<Long> distinct = siteIds.stream()
-                .filter(Objects::nonNull)
-                .distinct()
-                .collect(Collectors.toList());
-        if (distinct.isEmpty()) {
-            throw new BusinessException("请至少选择一个关联站点");
-        }
-        Map<Long, VppSite> siteMap = siteMapper.selectBatchIds(distinct).stream()
-                .filter(site -> !VppAuditHelper.isDeleted(site.getDeleteFlag()))
-                .collect(Collectors.toMap(VppSite::getId, site -> site, (left, right) -> left));
-        for (Long siteId : distinct) {
-            VppSite site = siteMap.get(siteId);
-            if (site == null) {
-                throw new BusinessException("站点不存在或已删除: " + siteId);
-            }
-            if (tenantId != null && site.getTenantId() != null && !tenantId.equals(site.getTenantId())) {
-                throw new BusinessException("站点不属于当前租户: " + siteId);
-            }
-        }
-        return distinct;
     }
 
     private Integer resolveCurrentTenantId() {

+ 44 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/impl/VppContractServiceImpl.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
 import com.usky.vpp.domain.VppContract;
 import com.usky.vpp.domain.VppContractTemplate;
 import com.usky.vpp.domain.VppCustomer;
@@ -12,6 +13,7 @@ import com.usky.vpp.mapper.VppContractMapper;
 import com.usky.vpp.mapper.VppContractTemplateMapper;
 import com.usky.vpp.mapper.VppCustomerMapper;
 import com.usky.vpp.service.VppContractService;
+import com.usky.vpp.service.vo.VppContractExpireStatVO;
 import com.usky.vpp.service.vo.VppContractRequestVO;
 import com.usky.vpp.service.vo.VppContractResponseVO;
 import com.usky.vpp.util.VppAuditHelper;
@@ -32,13 +34,12 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
-import java.time.LocalDateTime;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -244,6 +245,47 @@ public class VppContractServiceImpl implements VppContractService {
         return i > 0;
     }
 
+    @Override
+    public VppContractExpireStatVO expireStat() {
+        VppContractExpireStatVO result = new VppContractExpireStatVO();
+        Integer tenantId = SecurityUtils.getTenantId();
+
+        LambdaQueryWrapper<VppContract> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(VppContract::getDeleteFlag, 0)
+                .eq(tenantId != null, VppContract::getTenantId, tenantId)
+                .isNotNull(VppContract::getCustomerId)
+                .isNotNull(VppContract::getExpireDate)
+                .orderByDesc(VppContract::getCreateTime);
+        List<VppContract> allContracts = vppContractMapper.selectList(queryWrapper);
+        if (allContracts == null || allContracts.isEmpty()) {
+            return result;
+        }
+
+        // 每个客户仅保留创建时间最新的一份合同
+        Map<Long, VppContract> latestByCustomer = new LinkedHashMap<>();
+        for (VppContract contract : allContracts) {
+            latestByCustomer.putIfAbsent(contract.getCustomerId(), contract);
+        }
+
+        LocalDate today = LocalDate.now();
+        LocalDate upcomingDeadline = today.plusMonths(3);
+        long expiredCount = 0L;
+        long upcomingExpireCount = 0L;
+        for (VppContract contract : latestByCustomer.values()) {
+            LocalDate expireDate = contract.getExpireDate();
+            if (today.isAfter(expireDate)) {
+                expiredCount++;
+            } else if (!expireDate.isAfter(upcomingDeadline)) {
+                // today <= expireDate <= today+3个月
+                upcomingExpireCount++;
+            }
+        }
+
+        result.setExpiredCount(expiredCount);
+        result.setUpcomingExpireCount(upcomingExpireCount);
+        return result;
+    }
+
     @Override
     public void download(Long id, HttpServletResponse response) {
         LambdaQueryWrapper<VppContract> queryWrapper = new LambdaQueryWrapper<>();

+ 0 - 2
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/BiddingConfigRequest.java

@@ -5,7 +5,6 @@ import lombok.Data;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.List;
 
 /**
  * 竞价配置创建/更新请求
@@ -27,5 +26,4 @@ public class BiddingConfigRequest {
     private Integer advanceCloseHours;
     private BigDecimal maxPrice;
     private BigDecimal minPrice;
-    private List<Long> siteIds;
 }

+ 0 - 3
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/BiddingConfigVO.java

@@ -5,7 +5,6 @@ import lombok.Data;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.List;
 
 /**
  * 竞价配置详情/列表项
@@ -31,8 +30,6 @@ public class BiddingConfigVO {
     private String advanceCloseHoursLabel;
     private BigDecimal maxPrice;
     private BigDecimal minPrice;
-    private List<Long> siteIds;
-    private List<BiddingConfigSiteVO> sites;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
 }

+ 20 - 0
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractExpireStatVO.java

@@ -0,0 +1,20 @@
+package com.usky.vpp.service.vo;
+
+import lombok.Data;
+
+/**
+ * 合同到期统计:按客户最新合同的到期时间统计已到期 / 即将到期数量
+ */
+@Data
+public class VppContractExpireStatVO {
+
+    /**
+     * 已到期数量
+     */
+    private Long expiredCount = 0L;
+
+    /**
+     * 即将到期数量(近三个月)
+     */
+    private Long upcomingExpireCount = 0L;
+}

+ 1 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/service/vo/VppContractResponseVO.java

@@ -53,7 +53,7 @@ public class VppContractResponseVO {
     private String contractName;
 
     /**
-     * 合同状态
+     * 合同状态(0草稿 1审核中 2待生效 3已生效 4已驳回 5已到期 6已终止)
      */
     private Integer contractStatus;
 

+ 11 - 1
service-vpp/service-vpp-biz/src/main/java/com/usky/vpp/util/VppContractStatusHelper.java

@@ -20,6 +20,8 @@ public final class VppContractStatusHelper {
     public static final int REJECTED = 4;
     public static final int EXPIRED = 5;
     public static final int TERMINATED = 6;
+    /** 查询专用:即将到期(近三个月内过期,非落库状态) */
+    public static final int UPCOMING_EXPIRE = 7;
 
     private static final Set<Integer> MANUAL_STATUSES = new HashSet<>(Arrays.asList(
             DRAFT, AUDITING, REJECTED, TERMINATED
@@ -61,7 +63,8 @@ public final class VppContractStatusHelper {
     }
 
     /**
-     * 分页筛选:将生命周期状态(待生效/已生效/已到期)转换为日期条件。
+     * 分页筛选:将生命周期状态(待生效/已生效/已到期/即将到期)转换为日期条件。
+     * <p>其中 {@link #UPCOMING_EXPIRE} 仅用于查询,不会写入数据库。</p>
      */
     public static void applyStatusFilter(LambdaQueryWrapper<VppContract> wrapper, Integer contractStatus) {
         if (contractStatus == null) {
@@ -87,6 +90,13 @@ public final class VppContractStatusHelper {
                     .lt(VppContract::getExpireDate, today));
             return;
         }
+        if (contractStatus == UPCOMING_EXPIRE) {
+            LocalDate upcomingDeadline = today.plusMonths(3);
+            wrapper.and(w -> w.in(VppContract::getContractStatus, PENDING, EFFECTIVE, EXPIRED)
+                    .ge(VppContract::getExpireDate, today)
+                    .le(VppContract::getExpireDate, upcomingDeadline));
+            return;
+        }
         wrapper.eq(VppContract::getContractStatus, contractStatus);
     }
 }