Kaynağa Gözat

Merge branch 'mode-min-unit' into mode-min-unit-连续调度5天

sucheng 2 ay önce
ebeveyn
işleme
8d7f8c8e9a
21 değiştirilmiş dosya ile 891 ekleme ve 7 silme
  1. 16 0
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/dao/ApsDeliveryOffsetDao.java
  2. 21 1
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/dao/ApsProcessOperationProcessEquDao.java
  3. 11 0
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/ApsDeliveryOffsetService.java
  4. 2 0
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/ApsProcessOperationProcessEquService.java
  5. 16 3
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/impl/ApsBlankOrderServiceImpl.java
  6. 20 0
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/impl/ApsDeliveryOffsetServiceImpl.java
  7. 295 0
      cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/impl/ApsProcessOperationProcessEquServiceImpl.java
  8. 6 2
      cx-aps/cx-aps-common/src/main/resources/mybatis/ApsBlankOrderDao.xml
  9. 272 1
      cx-aps/cx-aps-common/src/main/resources/mybatis/ApsProcessOperationProcessEquDao.xml
  10. 5 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsBlankOrderDo.java
  11. 5 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsBlankOrderHistoryDo.java
  12. 84 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsDeliveryOffsetDo.java
  13. 5 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsProcessOperationProcessEquDo.java
  14. 13 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ApsDeliveryOffsetVo.java
  15. 16 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductCardAndLatestScheduleEndDateVo.java
  16. 50 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductCardVo.java
  17. 3 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductPlanReportParamVo.java
  18. 5 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductPlanReportVo.java
  19. 14 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/TenantIdAndUserIdsVo.java
  20. 12 0
      cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/WorkDeliveryOffsetReq.java
  21. 20 0
      cx-aps/cx-aps-server/src/main/java/com/rongwei/bsserver/controller/ApsProcessOperationProcessEquController.java

+ 16 - 0
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/dao/ApsDeliveryOffsetDao.java

@@ -0,0 +1,16 @@
+package com.rongwei.bscommon.sys.dao;
+
+import com.rongwei.bsentity.domain.ApsDeliveryOffsetDo;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Update;
+
+/**
+ * @Entity generator.domain.ApsDeliveryOffset
+ */
+public interface ApsDeliveryOffsetDao extends BaseMapper<ApsDeliveryOffsetDo> {
+
+}
+
+
+
+

+ 21 - 1
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/dao/ApsProcessOperationProcessEquDao.java

@@ -2,8 +2,8 @@ package com.rongwei.bscommon.sys.dao;
 
 
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateTime;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.rongwei.bsentity.domain.ApsDeliveryOffsetDo;
 import com.rongwei.bsentity.domain.ApsProcessOperationProcessEquDo;
 import com.rongwei.bsentity.domain.ApsProcessOperationProcessEquDo;
-import com.rongwei.bsentity.domain.ApsProductDetailDo;
 import com.rongwei.bsentity.vo.*;
 import com.rongwei.bsentity.vo.*;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Select;
@@ -149,6 +149,26 @@ public interface ApsProcessOperationProcessEquDao extends BaseMapper<ApsProcessO
     List<RollerTypeDownVo> selectAllRollerType(@Param("tenantId") String tenantId);
     List<RollerTypeDownVo> selectAllRollerType(@Param("tenantId") String tenantId);
 
 
     List<RollerTypeExcelLookVo> selectAllRollerTypeAndWorkShopAndDevice(@Param("tenantId") String tenantId);
     List<RollerTypeExcelLookVo> selectAllRollerTypeAndWorkShopAndDevice(@Param("tenantId") String tenantId);
+
+    List<ApsProcessOperationProcessEquDo> selectDeliveryOffestList(@Param("cardIdList") List<String> cardIdList);
+
+    List<ProductCardVo> selectProductionCards(@Param("tenantId") String tenantId);
+
+    List<ProductCardAndLatestScheduleEndDateVo> selectAllLatestEndDateByCarIds(@Param("cardIdList") List<String> cardIdList);
+
+    List<ProductCardAndLatestScheduleEndDateVo> selectAllLatestEndDateByCarIds2(@Param("cardIdList") List<String> cardIdList);
+
+    List<ProductCardAndLatestScheduleEndDateVo> selectAllLatestEndDateByCarIds3(@Param("cardIdList") List<String> cardIdList);
+
+    List<String> selectAllNeedDelIds();
+
+    void myDeleteByIds(@Param("needDelIdList") List<String> needDelIdList);
+
+    List<TenantIdAndUserIdsVo> selectNeedPushMessageUsers();
+
+    void updateProcessEquBatchById(@Param("list") List<ApsProcessOperationProcessEquDo> list);
+
+    void saveOrUpdateDeliverOffsetBatch(@Param("list") List<ApsDeliveryOffsetDo> needUpdateOrInsertList);
 }
 }
 
 
 
 

+ 11 - 0
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/ApsDeliveryOffsetService.java

@@ -0,0 +1,11 @@
+package com.rongwei.bscommon.sys.service;
+
+import com.rongwei.bsentity.domain.ApsDeliveryOffsetDo;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ *
+ */
+public interface ApsDeliveryOffsetService extends IService<ApsDeliveryOffsetDo> {
+
+}

+ 2 - 0
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/ApsProcessOperationProcessEquService.java

@@ -58,4 +58,6 @@ public interface ApsProcessOperationProcessEquService extends IService<ApsProces
     void workShopExport(CriteriaQuery query, HttpServletResponse response) throws IOException;
     void workShopExport(CriteriaQuery query, HttpServletResponse response) throws IOException;
 
 
     R workShopImport(MultipartFile multipartFile, HttpServletResponse response) throws IOException;
     R workShopImport(MultipartFile multipartFile, HttpServletResponse response) throws IOException;
+
+    R workDeliveryOffset(WorkDeliveryOffsetReq req);
 }
 }

+ 16 - 3
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/impl/ApsBlankOrderServiceImpl.java

@@ -2449,6 +2449,7 @@ public class ApsBlankOrderServiceImpl extends ServiceImpl<ApsBlankOrderDao, ApsB
             blankUpdateWrapper.set(ApsBlankOrderDo::getCustomname, apsBlankOrderDo.getCustomname());
             blankUpdateWrapper.set(ApsBlankOrderDo::getCustomname, apsBlankOrderDo.getCustomname());
             blankUpdateWrapper.set(ApsBlankOrderDo::getCardid, apsBlankOrderDo.getCardid());
             blankUpdateWrapper.set(ApsBlankOrderDo::getCardid, apsBlankOrderDo.getCardid());
             blankUpdateWrapper.set(ApsBlankOrderDo::getCustomeasyname, apsBlankOrderDo.getCustomeasyname());
             blankUpdateWrapper.set(ApsBlankOrderDo::getCustomeasyname, apsBlankOrderDo.getCustomeasyname());
+            blankUpdateWrapper.set(ApsBlankOrderDo::getRoutetag, apsBlankOrderDo.getRoutetag());
 
 
             this.update(blankUpdateWrapper);
             this.update(blankUpdateWrapper);
         } else {
         } else {
@@ -3427,6 +3428,9 @@ public class ApsBlankOrderServiceImpl extends ServiceImpl<ApsBlankOrderDao, ApsB
                     if (column.getField().equals("productionOrderStatus")) {
                     if (column.getField().equals("productionOrderStatus")) {
                         param.setProductionOrderStatus(column.getValue());
                         param.setProductionOrderStatus(column.getValue());
                     }
                     }
+                    if (column.getField().equals("isOutPromiseDate")) {
+                        param.setIsOutPromiseDate(column.getValue());
+                    }
                 }
                 }
             }
             }
             if (columnQueryType.getColumnInnerRelation().equals("OR")) {
             if (columnQueryType.getColumnInnerRelation().equals("OR")) {
@@ -3904,6 +3908,11 @@ public class ApsBlankOrderServiceImpl extends ServiceImpl<ApsBlankOrderDao, ApsB
             });
             });
             if (ObjectUtil.isNotEmpty(clearBachMaterialProcessEquList)) {
             if (ObjectUtil.isNotEmpty(clearBachMaterialProcessEquList)) {
                 apsProcessOperationProcessEquService.updateBatchById(clearBachMaterialProcessEquList);
                 apsProcessOperationProcessEquService.updateBatchById(clearBachMaterialProcessEquList);
+                //删除所有的坯料批次号
+                List<String> needDelIdList = clearBachMaterialProcessEquList.stream().map(ApsProcessOperationProcessEquDo::getId).collect(Collectors.toList());
+                apsProcessOperationProcessEquService.update(new LambdaUpdateWrapper<ApsProcessOperationProcessEquDo>()
+                        .set(ApsProcessOperationProcessEquDo::getBlankbatchnumber, "")
+                        .in(ApsProcessOperationProcessEquDo::getRoottaskid, needDelIdList));
             }
             }
             updateOperationProcessEquList.forEach(equDo -> {
             updateOperationProcessEquList.forEach(equDo -> {
                 //剩余待开工卷数 =计划加工卷数-已开工卷数-已取消卷数
                 //剩余待开工卷数 =计划加工卷数-已开工卷数-已取消卷数
@@ -3918,6 +3927,12 @@ public class ApsBlankOrderServiceImpl extends ServiceImpl<ApsBlankOrderDao, ApsB
             });
             });
             if (ObjectUtil.isNotEmpty(needUpdateProcessEquList)) {
             if (ObjectUtil.isNotEmpty(needUpdateProcessEquList)) {
                 apsProcessOperationProcessEquService.updateBatchById(needUpdateProcessEquList);
                 apsProcessOperationProcessEquService.updateBatchById(needUpdateProcessEquList);
+                //更新所有的坯料批次号
+                for (ApsProcessOperationProcessEquDo equDo : needUpdateProcessEquList) {
+                    apsProcessOperationProcessEquService.update(new LambdaUpdateWrapper<ApsProcessOperationProcessEquDo>()
+                            .set(ApsProcessOperationProcessEquDo::getBlankbatchnumber, equDo.getBachmaterialprocess())
+                            .eq(ApsProcessOperationProcessEquDo::getRoottaskid, equDo.getId()));
+                }
             }
             }
 
 
 
 
@@ -4836,9 +4851,7 @@ public class ApsBlankOrderServiceImpl extends ServiceImpl<ApsBlankOrderDao, ApsB
         }
         }
 
 
         try {
         try {
-            //冷轧1天
-            String lenzhaEndDate = DateUtil.format(DateUtil.offsetDay(DateUtil.parse(needScheduleStartDate), 1), "yyyy-MM-dd HH:mm:ss");
-            apsScheduleRestart(null, needScheduleStartDate, lenzhaEndDate, currentUser, null, true, false);
+            apsScheduleRestart(null, needScheduleStartDate, needScheduleEndDate, currentUser, null, true, false);
             //退火5天
             //退火5天
             apsScheduleRestart(null, needScheduleStartDate, needScheduleEndDate, currentUser, null, false, true);
             apsScheduleRestart(null, needScheduleStartDate, needScheduleEndDate, currentUser, null, false, true);
         } catch (Exception e) {
         } catch (Exception e) {

+ 20 - 0
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/impl/ApsDeliveryOffsetServiceImpl.java

@@ -0,0 +1,20 @@
+package com.rongwei.bscommon.sys.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.rongwei.bscommon.sys.dao.ApsDeliveryOffsetDao;
+import com.rongwei.bscommon.sys.service.ApsDeliveryOffsetService;
+import com.rongwei.bsentity.domain.ApsDeliveryOffsetDo;
+import org.springframework.stereotype.Service;
+
+/**
+ *
+ */
+@Service
+public class ApsDeliveryOffsetServiceImpl extends ServiceImpl<ApsDeliveryOffsetDao, ApsDeliveryOffsetDo>
+    implements ApsDeliveryOffsetService {
+
+}
+
+
+
+

+ 295 - 0
cx-aps/cx-aps-common/src/main/java/com/rongwei/bscommon/sys/service/impl/ApsProcessOperationProcessEquServiceImpl.java

@@ -18,6 +18,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
+import com.rongwei.bscommon.sys.dao.ApsDeliveryOffsetDao;
 import com.rongwei.bscommon.sys.dao.ApsProcessOperationProcessEquDao;
 import com.rongwei.bscommon.sys.dao.ApsProcessOperationProcessEquDao;
 import com.rongwei.bscommon.sys.listener.SpecificRowDropDownHandler;
 import com.rongwei.bscommon.sys.listener.SpecificRowDropDownHandler;
 import com.rongwei.bscommon.sys.listener.WorkShopImportListener;
 import com.rongwei.bscommon.sys.listener.WorkShopImportListener;
@@ -30,7 +32,9 @@ import com.rongwei.rwcommon.base.exception.CustomException;
 import com.rongwei.rwcommon.utils.SecurityUtil;
 import com.rongwei.rwcommon.utils.SecurityUtil;
 import com.rongwei.rwcommon.utils.StringUtils;
 import com.rongwei.rwcommon.utils.StringUtils;
 import com.rongwei.rwcommon.vo.CriteriaQuery;
 import com.rongwei.rwcommon.vo.CriteriaQuery;
+import com.rongwei.rwcommon.vo.MailDo;
 import com.rongwei.safecommon.fegin.CXAdminFeginClient;
 import com.rongwei.safecommon.fegin.CXAdminFeginClient;
+import com.rongwei.safecommon.fegin.CXCommonFeginClient;
 import com.rongwei.safecommon.utils.CXCommonUtils;
 import com.rongwei.safecommon.utils.CXCommonUtils;
 import com.rongwei.wfserver.wfcommon.sys.service.impl.ProcessServiceImpl;
 import com.rongwei.wfserver.wfcommon.sys.service.impl.ProcessServiceImpl;
 import lombok.extern.log4j.Log4j2;
 import lombok.extern.log4j.Log4j2;
@@ -44,10 +48,12 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.DefaultTransactionDefinition;
 import org.springframework.transaction.support.DefaultTransactionDefinition;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
+import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.net.URLEncoder;
 import java.net.URLEncoder;
 import java.util.*;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -59,6 +65,9 @@ import static com.rongwei.safecommon.utils.SaveConstans.ForcedConflictsDescripti
 import static com.rongwei.safecommon.utils.SaveConstans.JobStatus.*;
 import static com.rongwei.safecommon.utils.SaveConstans.JobStatus.*;
 import static com.rongwei.safecommon.utils.SaveConstans.LockmarkType.LOCKMARK_N;
 import static com.rongwei.safecommon.utils.SaveConstans.LockmarkType.LOCKMARK_N;
 import static com.rongwei.safecommon.utils.SaveConstans.LockmarkType.LOCKMARK_Y;
 import static com.rongwei.safecommon.utils.SaveConstans.LockmarkType.LOCKMARK_Y;
+import static com.rongwei.safecommon.utils.SaveConstans.NotifyContent.PROCESSDELAY_CONTENT;
+import static com.rongwei.safecommon.utils.SaveConstans.NotifyTitle.PROCESSDELAY_TITLE;
+import static com.rongwei.safecommon.utils.SaveConstans.NotifyType.PROCESSDELAY_REMIND;
 import static com.rongwei.safecommon.utils.SaveConstans.ProcessWay.PROCESS_WAY_MERGE;
 import static com.rongwei.safecommon.utils.SaveConstans.ProcessWay.PROCESS_WAY_MERGE;
 import static com.rongwei.safecommon.utils.SaveConstans.ProductionStatus.*;
 import static com.rongwei.safecommon.utils.SaveConstans.ProductionStatus.*;
 import static com.rongwei.safecommon.utils.SaveConstans.UNForcedConflictsDescription.*;
 import static com.rongwei.safecommon.utils.SaveConstans.UNForcedConflictsDescription.*;
@@ -115,6 +124,12 @@ public class ApsProcessOperationProcessEquServiceImpl extends ServiceImpl<ApsPro
     private CXAdminFeginClient cxAdminFeginClient;
     private CXAdminFeginClient cxAdminFeginClient;
     @Autowired
     @Autowired
     private ApsProductionProcessesService apsProductionProcessesService;
     private ApsProductionProcessesService apsProductionProcessesService;
+    @Autowired
+    private ApsDeliveryOffsetService apsDeliveryOffsetService;
+    @Autowired
+    private ApsDeliveryOffsetDao apsDeliveryOffsetDao;
+    @Resource
+    private CXCommonFeginClient cxCommonFeginClient;
 
 
 //    /**
 //    /**
 //     * 更新工序的待加工批次号信息
 //     * 更新工序的待加工批次号信息
@@ -3005,6 +3020,286 @@ public class ApsProcessOperationProcessEquServiceImpl extends ServiceImpl<ApsPro
             return R.ok(res);
             return R.ok(res);
         }
         }
     }
     }
+
+    @Override
+    public R workDeliveryOffset(WorkDeliveryOffsetReq req) {
+        SysUserVo currentUser = CXCommonUtils.getCurrentUser();
+        String tenantId = "";
+        if (currentUser == null) {
+            currentUser = new SysUserVo();
+            currentUser.setId("0");
+            currentUser.setName("定时任务操作");
+        }else {
+            tenantId = CXCommonUtils.getCurrentUserFactoryId(currentUser);
+        }
+        //查询所有没有交期偏差的生产卡片
+        List<ProductCardVo> list = this.baseMapper.selectProductionCards(tenantId);
+        if (list.isEmpty()) {
+            log.info("没有查询到需要计算的卡片信息");
+            return R.ok();
+        }
+        //卡片ID集合
+        List<String> cardIdList = list.stream().map(ProductCardVo::getId).distinct().collect(Collectors.toList());
+        //查询卡片对应的 计划成品卷数>0的作业明细的最晚的排程完工时间
+        List<ProductCardAndLatestScheduleEndDateVo> cardIdAndScheduleEndDateList = this.baseMapper.selectAllLatestEndDateByCarIds(cardIdList);
+        //查询卡片对应的 计划成品卷数>0并且作业状态=待开工或加工中的作业明细的最晚的排程完工时间
+        List<ProductCardAndLatestScheduleEndDateVo> cardIdAndScheduleEndDateList2 = this.baseMapper.selectAllLatestEndDateByCarIds2(cardIdList);
+        //查询卡片对应的 计划成品卷数>0的作业明细最晚的实际完工时间
+        List<ProductCardAndLatestScheduleEndDateVo> cardIdAndScheduleEndDateList3 = this.baseMapper.selectAllLatestEndDateByCarIds3(cardIdList);
+
+        /**
+         * 计算每个料卷(卡片ID区分料卷)正在加工(作业状态=加工中)和准备开工(作业状态=待开工,并且待加工料卷批次号不为空)的作业明细,与原计划(预排程计划)完工时间偏差(整小时数,四舍五入),计算逻辑如下:
+         * 如果作业状态=加工中,则完工时间偏差=计划完工时间-排程完工时间
+         * 如果作业状态=待开工,则完工时间偏差=取其大(计划完工时间,当前时间+作业时长)-排程完工时间
+         */
+        List<ApsProcessOperationProcessEquDo> apsProcessOperationProcessEquDos = this.baseMapper.selectDeliveryOffestList(cardIdList);
+        //聚合<卡片ID,最大的时间偏差>
+        Map<String, Integer> maxOffsetTimeMap = apsProcessOperationProcessEquDos.stream()
+                .collect(
+                        Collectors.groupingBy(
+                                ApsProcessOperationProcessEquDo::getRoottaskid, Collectors.reducing(
+                                        0, // 默认值
+                                        ApsProcessOperationProcessEquDo::getTimeoffset,
+                                        Integer::max
+                                )));
+
+
+        DateTime now = DateUtil.date();
+        //需要修改或新增的集合
+        List<ApsDeliveryOffsetDo> needUpdateOrInsertList = new LinkedList<>();
+        for (ProductCardVo productCardVo : list) {
+            ApsDeliveryOffsetDo apsDeliveryOffsetDo = new ApsDeliveryOffsetDo();
+            apsDeliveryOffsetDo.setId(productCardVo.getId());
+            apsDeliveryOffsetDo.setOrderid(productCardVo.getOrderId());
+            apsDeliveryOffsetDo.setBlankid(productCardVo.getBlankId());
+            apsDeliveryOffsetDo.setStatus(productCardVo.getStatus());
+            apsDeliveryOffsetDo.setTenantid(productCardVo.getTenantId());
+            apsDeliveryOffsetDo.setCreatedate(now);
+            apsDeliveryOffsetDo.setModifydate(now);
+            apsDeliveryOffsetDo.setCreateuserid(currentUser.getId());
+            apsDeliveryOffsetDo.setCreateusername(currentUser.getName());
+            apsDeliveryOffsetDo.setModifyuserid(currentUser.getId());
+            apsDeliveryOffsetDo.setModifyusername(currentUser.getName());
+            apsDeliveryOffsetDo.setDeleted("0");
+
+            //计算每个料卷(卡片ID区分料卷)计划完工时间=计划成品卷数>0的作业明细的最晚的排程完工时间+订单的送货时长
+            //订单的送货时长(小时)
+            Integer deliverTime = productCardVo.getDeliverTime();
+            if (deliverTime == null) {
+                deliverTime = 0;
+            }
+            //计划成品卷数>0的作业明细的最晚的排程完工时间
+            ProductCardAndLatestScheduleEndDateVo productCardAndLatestScheduleEndDateVo = cardIdAndScheduleEndDateList.stream().filter(item -> item.getCardId().equals(productCardVo.getId())).findFirst().orElse(null);
+            if (productCardAndLatestScheduleEndDateVo != null) {
+                Date latestScheduleEndDate = productCardAndLatestScheduleEndDateVo.getLatestScheduleEndDate();
+                //料卷计划完工时间
+                DateTime planEndDate = DateUtil.offsetHour(latestScheduleEndDate, deliverTime);
+                apsDeliveryOffsetDo.setPlanenddate(planEndDate);
+            }
+
+            /**
+             * 计算每个料卷预计完工时间:
+             * 如果卡片的生产状态=待开工或加工中,
+             *  则=(计划成品卷数>0,并且作业状态=待开工或加工中的作业明细的最晚的排程完工时间)+订单的送货时长+料卷交货期偏差,
+             *      料卷交货期偏差=该料卷准备开工和正在加工的作业计划完工时间与原计划(预排程计划)完工时间偏差的最大偏差(大于零表示延期,小于零表示提前,零表示没有偏差)
+             * 如果卡片的生产状态=全部取消或部分取消或已完工,则=空(表示不需要显示)
+             */
+            if (Arrays.asList("待开工", "加工中").contains(productCardVo.getStatus())) {
+                ProductCardAndLatestScheduleEndDateVo latestScheduleEndDateVo = cardIdAndScheduleEndDateList2.stream().filter(item -> item.getCardId().equals(productCardVo.getId())).findFirst().orElse(null);
+                if (latestScheduleEndDateVo != null) {
+                    Date latestScheduleEndDate = latestScheduleEndDateVo.getLatestScheduleEndDate();
+                    Integer maxOffsetTime = maxOffsetTimeMap.getOrDefault(productCardVo.getId(), 0);
+                    int time = deliverTime + maxOffsetTime;
+                    DateTime guessEndDate = DateUtil.offsetHour(latestScheduleEndDate, time);
+                    apsDeliveryOffsetDo.setGuessenddate(guessEndDate);
+                }
+            } else {
+                apsDeliveryOffsetDo.setGuessenddate(null);
+            }
+
+            /**
+             * 计算每个料卷实际完工时间:
+             * 如果卡片的生产状态=部分取消或已完工,则=计划成品卷数>0的作业明细最晚的实际完工时间;
+             * 如果卡片的生产状态=待开工或加工中或全部取消,则为空(表示未完工,不需要显示)
+             */
+            if (Arrays.asList("部分取消", "已完工").contains(productCardVo.getStatus())) {
+                ProductCardAndLatestScheduleEndDateVo latestScheduleEndDateVo = cardIdAndScheduleEndDateList3.stream().filter(item -> item.getCardId().equals(productCardVo.getId())).findFirst().orElse(null);
+                if (latestScheduleEndDateVo != null) {
+                    Date latestScheduleEndDate = latestScheduleEndDateVo.getLatestScheduleEndDate();
+                    apsDeliveryOffsetDo.setActualenddate(latestScheduleEndDate);
+                }
+            } else {
+                apsDeliveryOffsetDo.setActualenddate(null);
+            }
+            /**
+             * 计算每个料卷交货期偏差(天):
+             * 如果卡片的生产状态=待开工或加工中,
+             * 如果料卷计划完工时间的日期在坯料计划承诺交货期起止范围内,则=空
+             * 如果料卷计划完工时间的日期<坯料计划承诺交货期起,则=料卷计划完工时间的日期-坯料计划承诺交货期起
+             * 如果料卷计划完工时间的日期>坯料计划承诺交货期止,则=料卷计划完工时间的日期-坯料计划承诺交货期止
+             * 如果卡片的生产状态=部分取消或已完工,
+             * 如果料卷实际完工时间的日期在坯料计划承诺交货期起止范围内,则=空
+             * 如果料卷实际完工时间的日期<坯料计划承诺交货期起,则=料卷实际完工时间的日期-坯料计划承诺交货期起
+             * 如果料卷实际完工时间的日期>坯料计划承诺交货期止,则=料卷实际完工时间的日期-坯料计划承诺交货期止
+             * 如果卡片的生产状态=全部取消,则=空(表示不显示偏差)
+             */
+            //坯料计划承诺交货期
+            String[] split = productCardVo.getPromiseDate().split("~");
+            DateTime promiseStartDate = DateUtil.parse(split[0] + " 00:00:00");
+            DateTime promiseEndDate = DateUtil.parse(split[1] + " 23:59:59");
+            if (Arrays.asList("待开工", "加工中", "部分取消", "已完工").contains(productCardVo.getStatus())) {
+                DateTime endDate = null;
+                if (Arrays.asList("待开工", "加工中").contains(productCardVo.getStatus())) {
+                    endDate = DateTime.of(apsDeliveryOffsetDo.getGuessenddate());
+                } else {
+                    endDate = DateTime.of(apsDeliveryOffsetDo.getActualenddate());
+                }
+                if (endDate != null) {
+                    //如果料卷计划完工时间的日期<坯料计划承诺交货期起,则=料卷预计完工时间的日期-坯料计划承诺交货期起
+                    if (endDate.before(promiseStartDate)) {
+                        long between = DateUtil.between(promiseStartDate, endDate, DateUnit.HOUR, false);
+                        //取整
+                        BigDecimal divide = new BigDecimal(between).divide(new BigDecimal(24), RoundingMode.UP);
+                        int offsetDay = divide.intValue();
+                        apsDeliveryOffsetDo.setDeliveryoffset(offsetDay);
+                        // 如果料卷计划完工时间的日期>坯料计划承诺交货期止,则=料卷预计完工时间的日期-坯料计划承诺交货期止
+                    } else if (endDate.after(promiseEndDate)) {
+                        long between = DateUtil.between(promiseEndDate, endDate, DateUnit.HOUR, false);
+                        //取整
+                        BigDecimal divide = new BigDecimal(between).divide(new BigDecimal(24), RoundingMode.UP);
+                        int offsetDay = divide.intValue();
+                        apsDeliveryOffsetDo.setDeliveryoffset(offsetDay);
+                    } else {
+                        apsDeliveryOffsetDo.setDeliveryoffset(null);
+                    }
+                } else {
+                    apsDeliveryOffsetDo.setDeliveryoffset(null);
+                }
+            } else {
+                apsDeliveryOffsetDo.setDeliveryoffset(null);
+            }
+            needUpdateOrInsertList.add(apsDeliveryOffsetDo);
+        }
+
+
+        if (!apsProcessOperationProcessEquDos.isEmpty()) {
+            Lists.partition(apsProcessOperationProcessEquDos, 1000).forEach(apsProcessOperationProcessEquDoList -> {
+                this.baseMapper.updateProcessEquBatchById(apsProcessOperationProcessEquDoList);
+            });
+        }
+        if (!needUpdateOrInsertList.isEmpty()) {
+            Lists.partition(needUpdateOrInsertList, 50).forEach(needUpdateOrInsertListVo -> {
+                this.baseMapper.saveOrUpdateDeliverOffsetBatch(needUpdateOrInsertListVo);
+            });
+        }
+        //反查需要删除的数据
+        List<String> needDelIdList = this.baseMapper.selectAllNeedDelIds();
+        if (!needDelIdList.isEmpty()) {
+            this.baseMapper.myDeleteByIds(needDelIdList);
+        }
+        if (req.getNeedSendMessage()) {
+            //发送消息和邮件
+            sendMessageAndEmail(needUpdateOrInsertList, list);
+        }
+        return R.ok();
+    }
+
+    private void sendMessageAndEmail(List<ApsDeliveryOffsetDo> needUpdateOrInsertList, List<ProductCardVo> list) {
+        //过滤需要发送消息的数据
+        List<ApsDeliveryOffsetDo> needSendMessageList = needUpdateOrInsertList.stream().filter(item ->
+                Arrays.asList("待开工", "加工中").contains(item.getStatus())
+                        && item.getDeliveryoffset() != null
+                        && item.getDeliveryoffset() > 0).collect(Collectors.toList());
+        if (!needSendMessageList.isEmpty()) {
+            //查询每个工厂的 生产计划主任(role071)、车间调度组长(cjddzz)
+            List<TenantIdAndUserIdsVo> tenantIdAndUserIdsVoList = this.baseMapper.selectNeedPushMessageUsers();
+            Map<String, List<String>> tenantIdAndUserIdListMap = new HashMap<>();
+            Map<String, String> tenantIdAndEmailsListMap = new HashMap<>();
+            for (TenantIdAndUserIdsVo tenantIdAndUserIdsVo : tenantIdAndUserIdsVoList) {
+                List<String> userList = new LinkedList<>();
+                if (StringUtils.isNotBlank(tenantIdAndUserIdsVo.getUserIds())) {
+                    userList = new LinkedList<>(Arrays.asList(tenantIdAndUserIdsVo.getUserIds().split(",")));
+                }
+                tenantIdAndUserIdListMap.putIfAbsent(tenantIdAndUserIdsVo.getTenantId(), userList);
+                tenantIdAndEmailsListMap.putIfAbsent(tenantIdAndUserIdsVo.getTenantId(), tenantIdAndUserIdsVo.getEmails());
+            }
+            //<工厂ID,<订单号,批次号集合>>
+            Map<String, Map<String, List<String>>> resMap = new HashMap<>();
+            for (ApsDeliveryOffsetDo apsDeliveryOffsetDo : needSendMessageList) {
+                ProductCardVo productCardVo = list.stream().filter(item -> item.getId().equals(apsDeliveryOffsetDo.getId())).findFirst().orElse(null);
+                if (productCardVo == null) {
+                    continue;
+                }
+                //订单号 优先使用客户订单号
+                String orderNo = productCardVo.getCustomOrderNo();
+                if (StringUtils.isBlank(orderNo)) {
+                    orderNo = productCardVo.getOrderNo();
+                    if (StringUtils.isBlank(orderNo)) {
+                        continue;
+                    }
+                }
+                //批次号
+                String blankBatchNumber = productCardVo.getBlankBatchNumber();
+                if (StringUtils.isBlank(blankBatchNumber)) {
+                    continue;
+                }
+                String tenantId = productCardVo.getTenantId();
+                if (resMap.containsKey(tenantId)) {
+                    Map<String, List<String>> orderMap = resMap.get(tenantId);
+                    if (orderMap.containsKey(orderNo)) {
+                        List<String> blankNumberList = orderMap.get(orderNo);
+                        blankNumberList.add(blankBatchNumber);
+                    } else {
+                        orderMap.put(orderNo, new LinkedList<>(Collections.singletonList(blankBatchNumber)));
+                    }
+                } else {
+                    Map<String, List<String>> orderMap = new HashMap<>();
+                    orderMap.put(orderNo, new LinkedList<>(Collections.singletonList(blankBatchNumber)));
+                    resMap.put(tenantId, orderMap);
+                }
+            }
+            //按照工厂发送消息
+            for (String key : resMap.keySet()) {
+                Map<String, List<String>> orderMap = resMap.get(key);
+                List<String> messageList = new LinkedList<>();
+                for (String orderNo : orderMap.keySet()) {
+                    List<String> numberList = orderMap.get(orderNo);
+                    String message = "订单号:【" + orderNo + "】,料卷批次号:【" + String.join(",", numberList) + "】";
+                    messageList.add(message);
+                }
+                if (messageList.isEmpty()) {
+                    continue;
+                }
+                //获取此工厂需要发送通知的人的ID
+                List<String> userIdList = tenantIdAndUserIdListMap.getOrDefault(key, null);
+                if (userIdList.isEmpty()) {
+                    continue;
+                }
+                //系统通知(移动端和PC端个人工作台)
+                CXCommonUtils.sendNotify("料卷延期",
+                        "以下料卷预计延期,请及时调整计划:\r\n" + String.join("\r\n", messageList),
+                        null,
+                        userIdList,
+                        null,
+                        PROCESSDELAY_REMIND,
+                        false);
+
+                //获取需要邮件的邮箱
+                String emails = tenantIdAndEmailsListMap.getOrDefault(key, null);
+                if (StringUtils.isBlank(emails)) {
+                    continue;
+                }
+                //邮件提醒
+                MailDo mailDo = new MailDo();
+                mailDo.setReceiveEmail(emails.split(","));
+                mailDo.setNeedTransReceive(false);
+                mailDo.setCcEmail(new String[]{});
+                mailDo.setSubject("料卷延期");
+                mailDo.setContent("以下料卷预计延期,请及时调整计划:<br>" + String.join("<br>", messageList));
+                cxCommonFeginClient.sendHtmlMail(mailDo);
+            }
+        }
+    }
 }
 }
 
 
 
 

+ 6 - 2
cx-aps/cx-aps-common/src/main/resources/mybatis/ApsBlankOrderDao.xml

@@ -493,7 +493,7 @@
         where a.rn_asc=1 or a.rn_desc=1 order by a.PLANENDDATE
         where a.rn_asc=1 or a.rn_desc=1 order by a.PLANENDDATE
     </select>
     </select>
     <select id="selectPageReport" resultType="com.rongwei.bsentity.vo.ProductPlanReportVo">
     <select id="selectPageReport" resultType="com.rongwei.bsentity.vo.ProductPlanReportVo">
-        SELECT a.* from (SELECT
+        SELECT a.*,ado.DELIVERYOFFSET AS 'deliveryOffset',IF(ado.DELIVERYOFFSET > 0,'是','否') AS 'isOutPromiseDate' from (SELECT
             apo.*,
             apo.*,
             abo.PROMISEDATESTART AS 'promiseDateStart',
             abo.PROMISEDATESTART AS 'promiseDateStart',
             abo.PROMISEDATEEND AS 'promiseDateEnd',
             abo.PROMISEDATEEND AS 'promiseDateEnd',
@@ -577,7 +577,7 @@
             <if test="param.tenantId != ''">
             <if test="param.tenantId != ''">
                 AND apo.TENANTID = #{param.tenantId}
                 AND apo.TENANTID = #{param.tenantId}
             </if>
             </if>
-            )a
+            )a left join (select BLANKID,MAX(DELIVERYOFFSET) AS 'DELIVERYOFFSET' from aps_delivery_offset where DELETED = 0 GROUP BY BLANKID) ado ON a.blankId = ado.BLANKID
         <where>
         <where>
             <if test="param.productionOrderStatus != ''">
             <if test="param.productionOrderStatus != ''">
                 AND a.productionOrderStatus = #{param.productionOrderStatus}
                 AND a.productionOrderStatus = #{param.productionOrderStatus}
@@ -588,10 +588,14 @@
             <if test="param.customOrderNo != ''">
             <if test="param.customOrderNo != ''">
                 AND a.newCustomOrderNo like concat('%',#{param.customOrderNo},'%')
                 AND a.newCustomOrderNo like concat('%',#{param.customOrderNo},'%')
             </if>
             </if>
+            <if test="param.isOutPromiseDate != ''">
+                AND IF(ado.DELIVERYOFFSET > 0,'是','否') = #{param.isOutPromiseDate}
+            </if>
             <if test="param.likeSearchAll != ''">
             <if test="param.likeSearchAll != ''">
                 AND (a.newCustomName like concat('%',#{param.likeSearchAll},'%')
                 AND (a.newCustomName like concat('%',#{param.likeSearchAll},'%')
                 OR a.newCustomOrderNo like concat('%',#{param.likeSearchAll},'%')
                 OR a.newCustomOrderNo like concat('%',#{param.likeSearchAll},'%')
                 OR a.blankNumber like concat('%',#{param.likeSearchAll},'%')
                 OR a.blankNumber like concat('%',#{param.likeSearchAll},'%')
+                OR IF(ado.DELIVERYOFFSET > 0,'是','否') like concat('%',#{param.likeSearchAll},'%')
                 )
                 )
             </if>
             </if>
         </where>
         </where>

+ 272 - 1
cx-aps/cx-aps-common/src/main/resources/mybatis/ApsProcessOperationProcessEquDao.xml

@@ -45,7 +45,17 @@
         REPORTROLL,CHECKOUTROLL,UNFINISHROLL,
         REPORTROLL,CHECKOUTROLL,UNFINISHROLL,
         WORKSTATUS,BACHMATERIALPROCESS,WAITREPORTID
         WORKSTATUS,BACHMATERIALPROCESS,WAITREPORTID
     </sql>
     </sql>
-<!--    <update id="updateBachmaterialprocessByIds">-->
+    <delete id="myDeleteByIds">
+        DELETE
+        FROM
+            aps_delivery_offset
+        WHERE
+            ID IN
+        <foreach collection="needDelIdList" open="(" close=")" item="item" separator=",">
+            #{item}
+        </foreach>
+    </delete>
+    <!--    <update id="updateBachmaterialprocessByIds">-->
 <!--        update aps_process_operation_process_equ set BACHMATERIALPROCESS =-->
 <!--        update aps_process_operation_process_equ set BACHMATERIALPROCESS =-->
 <!--        CONCAT_WS(',',BACHMATERIALPROCESS,#{batchNum})-->
 <!--        CONCAT_WS(',',BACHMATERIALPROCESS,#{batchNum})-->
 <!--        <where>-->
 <!--        <where>-->
@@ -462,6 +472,47 @@
         WHERE
         WHERE
             apo.DELETED = 0
             apo.DELETED = 0
     </update>
     </update>
+    <update id="updateProcessEquBatchById">
+        <foreach collection="list" item="item">
+            update aps_process_operation_process_equ set TIMEOFFSET = #{item.timeoffset} where ID = #{item.id};
+        </foreach>
+    </update>
+    <update id="saveOrUpdateDeliverOffsetBatch">
+        <foreach collection="list" item="item">
+            INSERT INTO aps_delivery_offset (ID,TENANTID,ROPTION,DELETED,REMARK,CREATEDATE,CREATEUSERID,MODIFYDATE,MODIFYUSERID,CREATEUSERNAME,MODIFYUSERNAME,DELIVERYOFFSET,PLANENDDATE,GUESSENDDATE,ACTUALENDDATE,`STATUS`,ORDERID,BLANKID)
+            VALUES (#{item.id},
+                    #{item.tenantid},
+                    #{item.roption},
+                    #{item.deleted},
+                    #{item.remark},
+                    #{item.createdate},
+                    #{item.createuserid},
+                    #{item.modifydate},
+                    #{item.modifyuserid},
+                    #{item.createusername},
+                    #{item.modifyusername},
+                    #{item.deliveryoffset},
+                    #{item.planenddate},
+                    #{item.guessenddate},
+                    #{item.actualenddate},
+                    #{item.status},
+                    #{item.orderid},
+                    #{item.blankid})
+            ON DUPLICATE KEY
+                UPDATE
+            DELETED=#{item.deleted},
+            MODIFYDATE=#{item.modifydate},
+            MODIFYUSERID=#{item.modifyuserid},
+            MODIFYUSERNAME=#{item.modifyusername},
+            DELIVERYOFFSET=#{item.deliveryoffset},
+            PLANENDDATE=#{item.planenddate},
+            GUESSENDDATE=#{item.guessenddate},
+            ACTUALENDDATE=#{item.actualenddate},
+            STATUS=#{item.status},
+            ORDERID=#{item.orderid},
+            BLANKID=#{item.blankid};
+        </foreach>
+    </update>
     <select id="getOptionalEquipmentById" resultType="com.rongwei.bsentity.vo.OptionalEquipmentVo">
     <select id="getOptionalEquipmentById" resultType="com.rongwei.bsentity.vo.OptionalEquipmentVo">
         SELECT
         SELECT
         CONCAT(aci.USEDEPTID, '/', aci.ID ) AS value ,
         CONCAT(aci.USEDEPTID, '/', aci.ID ) AS value ,
@@ -727,4 +778,224 @@
         AND art.TENANTID = #{tenantId}
         AND art.TENANTID = #{tenantId}
         ORDER BY aci.USEDEPTNAME,aci.CHECKITEMNAME,art.ROLLERTYPE
         ORDER BY aci.USEDEPTNAME,aci.CHECKITEMNAME,art.ROLLERTYPE
     </select>
     </select>
+    <select id="selectDeliveryOffestList" resultType="com.rongwei.bsentity.domain.ApsProcessOperationProcessEquDo">
+        SELECT
+            apope.ID,
+            apope.ROOTTASKID,
+            IF(apope.WORKSTATUS = '加工中',
+               ROUND(TIMESTAMPDIFF(MINUTE,apope.SCHEDULEENDDATE,apope.PLANENDDATE)/60),
+               ROUND(TIMESTAMPDIFF(MINUTE,apope.SCHEDULEENDDATE,GREATEST(apope.PLANENDDATE,DATE_ADD(now(),INTERVAL TIMESTAMPDIFF(MINUTE,apope.PLANSTARTDATE,apope.PLANENDDATE) MINUTE)))/60)) AS 'TIMEOFFSET'
+        FROM
+            aps_process_operation_process_equ apope
+                LEFT JOIN aps_process_operation_process_equ preapope ON preapope.ID = apope.PREVIOUSPROCESSESIDS
+                AND preapope.DELETED = 0
+        WHERE
+            apope.DELETED = 0
+          AND (
+            apope.WORKSTATUS = '加工中'
+                OR
+            (apope.WORKSTATUS = '待开工' AND apope.BACHMATERIALPROCESS != ''))
+          AND apope.ROOTTASKID IN
+            <foreach collection="cardIdList" open="(" close=")" item="item" separator=",">
+                #{item}
+            </foreach>
+    </select>
+    <select id="selectProductionCards" resultType="com.rongwei.bsentity.vo.ProductCardVo">
+        select a.* from (select
+        apope.ROOTTASKID as id,
+        max(abo.TENANTID) as tenantid,
+        max(abo.CUSTOMORDERNO) as customOrderNo,
+        max(abo.ORDERNO) as orderNo,
+        max(abo.CUSTOMNAME) as customName,
+        max(abo.BLANKNUMBER) as blankNumber, -- 坯料计划编号
+        max(abo.OUTPUTORDERPRODUCT) as outputOrderProduct, -- 输出订单产品
+        max(CONCAT(IFNULL(DATE(abo.PROMISEDATESTART),''),'~',IFNULL(DATE(abo.PROMISEDATEEND),''))) AS promiseDate, -- 承诺交货期
+        max(abo.PLANNEDPRODUCTIONWORKSHOP) as plannedProductionWorkshop, -- 计划排产车间
+        max(abo.ID) AS blankId, -- 坯料计划ID
+        max(abo.CRAFTROUTEID) as craftrouteId, -- 工艺路线ID
+        max(apro.AUDITDATE) as auditDate, -- 订单评审日期
+        -- 坯料批次号
+        max(
+        IF(apope.BLANKBATCHNUMBER is not null and apope.BLANKBATCHNUMBER!='',
+        apope.BLANKBATCHNUMBER,
+        IF(abo.PREPAREMATERIAL = '是',
+        SUBSTRING_INDEX( abo.PREPAREMATERIALBATCHNO, ',', 1 ),
+        apope.BACHMATERIALPROCESS
+        )
+        )
+        ) AS blankBatchNumber,
+        max(ifnull(arr.REPORTOUTMATERREQUEST,abo.INPUTREPORTDESCRIBE)) as blankDesc , -- 坯料描述
+        max(arr.ID) as reportId, -- 坯料报工记录ID
+        max(arr.CHECKDATE) as checkDate, -- 坯料检验日期
+        max(arr.BLANKLEVEL) as insepctionLevel, -- 坯料检验等级
+        -- 卡片状态
+        apope.status,
+        -- 最近修改时间
+        max(apope.MODIFYDATE) as cardModifyDate,
+        -- 最后报工记录的工序显示名
+        SUBSTRING_INDEX(group_concat(arr.REPORTPROCESSNAME order by arr.CREATEDATE desc), ',', 1) as processName,
+        '0' AS deleted,
+        apro.DELIVERYTIME AS deliverTime,
+        apro.ID AS orderId
+        FROM
+        (
+        SELECT
+        *,
+        -- 卡片状态
+        IF(sum(LEAVEWAITWORKROLL)>0 and sum(STARTINGROLL)=0,'待开工', -- 已开工卷数=0
+        if(sum(LEAVEWAITWORKROLL)>0,'加工中', -- 剩余待报工卷数>0
+        if(sum(CANCELROLL)=0,'已完工', -- 已取消卷数合计=0
+        if(sum(CANCELROLL)&lt;sum(PLANPROCESSRALL),'部分取消','全部取消')))) as STATUS -- 已取消卷数小于计划加工卷数
+            FROM
+            aps_process_operation_process_equ
+            WHERE
+            DELETED = 0
+              <if test="tenantId != ''">
+                  AND TENANTID = #{tenantId}
+              </if>
+        GROUP BY ROOTTASKID
+        ) apope
+        JOIN aps_blank_order abo ON apope.BLANKID = abo.ID
+        JOIN aps_production_order apro ON abo.PRODUCTIONORDERID = apro.ID
+        left join aps_report_records arr on arr.DELETED = '0' AND apope.id=arr.PROCESSEQUID
+        where
+        apope.DELETED = '0' AND abo.DELETED = '0' AND apro.DELETED = '0' and apope.ROOTTASKID != ''
+        and (apope.BLANKBATCHNUMBER!='' or abo.PREPAREMATERIAL = '是') -- 有坯料批次号
+        group by
+        apope.ROOTTASKID
+        ) a LEFT JOIN aps_delivery_offset ado ON a.ID = ado.ID AND ado.DELETED = 0
+        where ado.ID is null or (ado.`STATUS` not in ('已完工','全部取消'))
+    </select>
+    <select id="selectAllLatestEndDateByCarIds"
+            resultType="com.rongwei.bsentity.vo.ProductCardAndLatestScheduleEndDateVo">
+        SELECT
+            apope.ROOTTASKID as cardId,
+            MAX(apope.SCHEDULEENDDATE) AS latestScheduleEndDate
+        FROM
+            aps_process_operation_process_equ apope
+                JOIN aps_process_operation_out_mater apoom ON apope.PROCESSID = apoom.MAINID
+                AND apoom.DELETED = 0
+        WHERE
+            apope.DELETED = 0
+          AND apoom.PLANPRODROLLNUM > 0
+	      AND apope.ROOTTASKID IN
+        <foreach collection="cardIdList" open="(" close=")" item="item" separator=",">
+            #{item}
+        </foreach>
+        GROUP BY apope.ROOTTASKID
+    </select>
+    <select id="selectAllLatestEndDateByCarIds2"
+            resultType="com.rongwei.bsentity.vo.ProductCardAndLatestScheduleEndDateVo">
+        SELECT
+        apope.ROOTTASKID as cardId,
+        MAX(apope.SCHEDULEENDDATE) AS latestScheduleEndDate
+        FROM
+        aps_process_operation_process_equ apope
+        JOIN aps_process_operation_out_mater apoom ON apope.PROCESSID = apoom.MAINID
+        AND apoom.DELETED = 0
+        WHERE
+        apope.DELETED = 0
+        AND apoom.PLANPRODROLLNUM > 0
+        AND apope.WORKSTATUS IN ('待开工','加工中')
+        AND apope.ROOTTASKID IN
+        <foreach collection="cardIdList" open="(" close=")" item="item" separator=",">
+            #{item}
+        </foreach>
+        GROUP BY apope.ROOTTASKID
+    </select>
+    <select id="selectAllLatestEndDateByCarIds3"
+            resultType="com.rongwei.bsentity.vo.ProductCardAndLatestScheduleEndDateVo">
+        SELECT
+        apope.ROOTTASKID as cardId,
+        MAX(apope.ACTUALFINISHDATE) AS latestScheduleEndDate
+        FROM
+        aps_process_operation_process_equ apope
+        JOIN aps_process_operation_out_mater apoom ON apope.PROCESSID = apoom.MAINID
+        AND apoom.DELETED = 0
+        WHERE
+        apope.DELETED = 0
+        AND apoom.PLANPRODROLLNUM > 0
+        AND apope.ROOTTASKID IN
+        <foreach collection="cardIdList" open="(" close=")" item="item" separator=",">
+            #{item}
+        </foreach>
+        GROUP BY apope.ROOTTASKID
+    </select>
+    <select id="selectAllNeedDelIds" resultType="java.lang.String">
+        select ado.ID from (select
+        apope.ROOTTASKID as id,
+        max(abo.TENANTID) as tenantId,
+        max(abo.CUSTOMORDERNO) as customOrderNo,
+        max(abo.ORDERNO) as orderNo,
+        max(abo.CUSTOMNAME) as customName,
+        max(abo.BLANKNUMBER) as blankNumber, -- 坯料计划编号
+        max(abo.OUTPUTORDERPRODUCT) as outputOrderProduct, -- 输出订单产品
+        max(CONCAT(IFNULL(DATE(abo.PROMISEDATESTART),''),'~',IFNULL(DATE(abo.PROMISEDATEEND),''))) AS promiseDate, -- 承诺交货期
+        max(abo.PLANNEDPRODUCTIONWORKSHOP) as plannedProductionWorkshop, -- 计划排产车间
+        max(abo.ID) AS blankId, -- 坯料计划ID
+        max(abo.CRAFTROUTEID) as craftrouteId, -- 工艺路线ID
+        max(apro.AUDITDATE) as auditDate, -- 订单评审日期
+        -- 坯料批次号
+        max(
+        IF(apope.BLANKBATCHNUMBER is not null and apope.BLANKBATCHNUMBER!='',
+        apope.BLANKBATCHNUMBER,
+        IF(abo.PREPAREMATERIAL = '是',
+        SUBSTRING_INDEX( abo.PREPAREMATERIALBATCHNO, ',', 1 ),
+        apope.BACHMATERIALPROCESS
+        )
+        )
+        ) AS blankBatchNumber,
+        max(ifnull(arr.REPORTOUTMATERREQUEST,abo.INPUTREPORTDESCRIBE)) as blankDesc , -- 坯料描述
+        max(arr.ID) as reportId, -- 坯料报工记录ID
+        max(arr.CHECKDATE) as checkDate, -- 坯料检验日期
+        max(arr.BLANKLEVEL) as insepctionLevel, -- 坯料检验等级
+        -- 卡片状态
+        apope.status,
+        -- 最近修改时间
+        max(apope.MODIFYDATE) as cardModifyDate,
+        -- 最后报工记录的工序显示名
+        SUBSTRING_INDEX(group_concat(arr.REPORTPROCESSNAME order by arr.CREATEDATE desc), ',', 1) as processName,
+        '0' AS deleted,
+        apro.DELIVERYTIME AS deliverTime,
+        apro.ID AS orderId
+        FROM
+        (
+        SELECT
+        *,
+        -- 卡片状态
+        IF(sum(STARTINGROLL)=0,'待开工', -- 已开工卷数=0
+        if(sum(LEAVEWAITWORKROLL)>0,'加工中', -- 剩余待报工卷数>0
+        if(sum(CANCELROLL)=0,'已完工', -- 已取消卷数合计=0
+        if(sum(CANCELROLL)&lt;sum(PLANPROCESSRALL),'部分取消','全部取消')))) as STATUS -- 已取消卷数小于计划加工卷数
+        FROM
+        aps_process_operation_process_equ
+        WHERE
+        DELETED = 0
+        GROUP BY ROOTTASKID
+        ) apope
+        JOIN aps_blank_order abo ON apope.BLANKID = abo.ID
+        JOIN aps_production_order apro ON abo.PRODUCTIONORDERID = apro.ID
+        left join aps_report_records arr on arr.DELETED = '0' AND apope.id=arr.PROCESSEQUID
+        where
+        apope.DELETED = '0' AND abo.DELETED = '0' AND apro.DELETED = '0' and apope.ROOTTASKID != ''
+        and (apope.BLANKBATCHNUMBER!='' or abo.PREPAREMATERIAL = '是') -- 有坯料批次号
+        group by
+        apope.ROOTTASKID
+        ) a RIGHT JOIN aps_delivery_offset ado ON a.ID = ado.ID AND ado.DELETED = 0
+        where a.ID is null
+    </select>
+    <select id="selectNeedPushMessageUsers" resultType="com.rongwei.bsentity.vo.TenantIdAndUserIdsVo">
+        SELECT
+            su.TENANTID AS 'tenantId',
+            GROUP_CONCAT(DISTINCT su.ID) AS 'userIds',
+            GROUP_CONCAT(DISTINCT IF(su.EMAIL = '',null,su.EMAIL)) AS 'emails'
+        FROM
+            sys_user su
+                LEFT JOIN sys_user_role sur ON su.ID = sur.USERID
+                AND sur.DELETED = 0
+        WHERE
+            su.DELETED = 0
+          AND sur.ROLEID IN (select ID from sys_role where `CODE` IN ('role071','cjddzz'))
+        GROUP BY su.TENANTID
+    </select>
 </mapper>
 </mapper>

+ 5 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsBlankOrderDo.java

@@ -337,5 +337,10 @@ public class ApsBlankOrderDo extends BaseDo {
      */
      */
     @TableField("CUSTOMEASYNAME")
     @TableField("CUSTOMEASYNAME")
     private String customeasyname;
     private String customeasyname;
+    /**
+     * 工艺路线标签
+     */
+    @TableField("ROUTETAG")
+    private String routetag;
 
 
 }
 }

+ 5 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsBlankOrderHistoryDo.java

@@ -284,4 +284,9 @@ public class ApsBlankOrderHistoryDo extends BaseDo {
      */
      */
     @TableField("CUSTOMEASYNAME")
     @TableField("CUSTOMEASYNAME")
     private String customeasyname;
     private String customeasyname;
+    /**
+     * 工艺路线标签
+     */
+    @TableField("ROUTETAG")
+    private String routetag;
 }
 }

+ 84 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsDeliveryOffsetDo.java

@@ -0,0 +1,84 @@
+package com.rongwei.bsentity.domain;
+
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import com.rongwei.rwcommon.base.BaseDo;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 生产订单-坯料计划-工序作业加工设备-作业明细-料卷交货期偏差
+ * @TableName aps_delivery_offset
+ */
+@TableName(value ="aps_delivery_offset")
+@Data
+public class ApsDeliveryOffsetDo extends BaseDo {
+    /**
+     * 主键ID(作业明细ID)
+     */
+    @TableId(value = "ID")
+    private String id;
+
+    /**
+     * 
+     */
+    @TableField(value = "TENANTID")
+    private String tenantid;
+
+    /**
+     * 扩展json格式配置
+     */
+    @TableField(value = "ROPTION")
+    private String roption;
+
+    /**
+     * 料卷交货期偏差
+     */
+    @TableField(value = "DELIVERYOFFSET",updateStrategy = FieldStrategy.IGNORED)
+    private Integer deliveryoffset;
+
+    /**
+     * 料卷计划完工时间
+     */
+    @TableField(value = "PLANENDDATE")
+    private Date planenddate;
+
+    /**
+     * 料卷预计完工时间
+     */
+    @TableField(value = "GUESSENDDATE",updateStrategy = FieldStrategy.IGNORED)
+    private Date guessenddate;
+
+    /**
+     * 料卷实际完工时间
+     */
+    @TableField(value = "ACTUALENDDATE",updateStrategy = FieldStrategy.IGNORED)
+    private Date actualenddate;
+
+    /**
+     * 卡片状态
+     */
+    @TableField(value = "STATUS")
+    private String status;
+
+    /**
+     * 订单ID
+     */
+    @TableField(value = "ORDERID")
+    private String orderid;
+
+    /**
+     * 坯料计划ID
+     */
+    @TableField(value = "BLANKID")
+    private String blankid;
+
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 5 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/domain/ApsProcessOperationProcessEquDo.java

@@ -258,4 +258,9 @@ public class ApsProcessOperationProcessEquDo extends BaseDo {
      */
      */
     @TableField("SCHEDULEENDDATE")
     @TableField("SCHEDULEENDDATE")
     private Date scheduleenddate;
     private Date scheduleenddate;
+    /**
+     * 时间偏差
+     */
+    @TableField("TIMEOFFSET")
+    private Integer timeoffset;
 }
 }

+ 13 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ApsDeliveryOffsetVo.java

@@ -0,0 +1,13 @@
+package com.rongwei.bsentity.vo;
+
+import com.rongwei.bsentity.domain.ApsDeliveryOffsetDo;
+import lombok.Data;
+
+/**
+ * @author :sc
+ * @since :2025/5/19
+ */
+@Data
+public class ApsDeliveryOffsetVo extends ApsDeliveryOffsetDo {
+    private String orderId;
+}

+ 16 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductCardAndLatestScheduleEndDateVo.java

@@ -0,0 +1,16 @@
+package com.rongwei.bsentity.vo;
+
+import cn.hutool.core.date.DateTime;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author :sc
+ * @since :2025/5/22
+ */
+@Data
+public class ProductCardAndLatestScheduleEndDateVo {
+    private String cardId;
+    private Date latestScheduleEndDate;
+}

+ 50 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductCardVo.java

@@ -0,0 +1,50 @@
+package com.rongwei.bsentity.vo;
+
+import com.rongwei.rwcommon.base.BaseDo;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author :sc
+ * @since :2025/5/22
+ */
+@Data
+public class ProductCardVo extends BaseDo {
+    private String id;
+    private String tenantId;
+    private String customOrderNo;
+    private String orderNo;
+    private String customName;
+    private String blankNumber;
+    private String outputOrderProduct;
+    //承诺交货期
+    private String promiseDate;
+    //计划排产车间
+    private String plannedProductionWorkshop;
+    private String blankId;
+    //工艺路线ID
+    private String craftrouteId;
+    //订单评审日期
+    private Date auditDate;
+    //坯料批次号
+    private String blankBatchNumber;
+    //坯料描述
+    private String blankDesc;
+    //报工记录ID
+    private String reportId;
+    //坯料检验日期
+    private Date checkDate;
+    //坯料检验等级
+    private String insepctionLevel;
+    //卡片状态
+    private String status;
+    //最近修改时间
+    private Date cardModifyDate;
+    //工序显示名
+    private String processName;
+    //送货时长
+    private Integer deliverTime;
+    //订单ID
+    private String orderId;
+}

+ 3 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductPlanReportParamVo.java

@@ -19,6 +19,9 @@ public class ProductPlanReportParamVo {
 
 
     private String productionOrderStatus = "";
     private String productionOrderStatus = "";
 
 
+    //是否超交期
+    private String isOutPromiseDate = "";
+
     private String orders = "";
     private String orders = "";
 
 
     //输出工厂
     //输出工厂

+ 5 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/ProductPlanReportVo.java

@@ -91,4 +91,9 @@ public class ProductPlanReportVo extends ApsProductionOrderDo {
      */
      */
     private String newCustomOrderNo;
     private String newCustomOrderNo;
 
 
+    //是否超交期
+    private String isOutPromiseDate;
+
+    //交期偏差
+    private String deliveryOffset;
 }
 }

+ 14 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/TenantIdAndUserIdsVo.java

@@ -0,0 +1,14 @@
+package com.rongwei.bsentity.vo;
+
+import lombok.Data;
+
+/**
+ * @author :sc
+ * @since :2025/5/23
+ */
+@Data
+public class TenantIdAndUserIdsVo {
+    private String tenantId;
+    private String userIds;
+    private String emails;
+}

+ 12 - 0
cx-aps/cx-aps-entity/src/main/java/com/rongwei/bsentity/vo/WorkDeliveryOffsetReq.java

@@ -0,0 +1,12 @@
+package com.rongwei.bsentity.vo;
+
+import lombok.Data;
+
+/**
+ * @author :sc
+ * @since :2025/5/19
+ */
+@Data
+public class WorkDeliveryOffsetReq {
+    private Boolean needSendMessage = true;
+}

+ 20 - 0
cx-aps/cx-aps-server/src/main/java/com/rongwei/bsserver/controller/ApsProcessOperationProcessEquController.java

@@ -8,6 +8,7 @@ import com.rongwei.rwcommon.base.R;
 import com.rongwei.rwcommon.vo.CriteriaQuery;
 import com.rongwei.rwcommon.vo.CriteriaQuery;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
@@ -168,5 +169,24 @@ public class ApsProcessOperationProcessEquController {
         log.info("车间作业跟踪,导入");
         log.info("车间作业跟踪,导入");
         return apsProcessOperationProcessEquService.workShopImport(multipartFile, response);
         return apsProcessOperationProcessEquService.workShopImport(multipartFile, response);
     }
     }
+
+    /**
+     * 作业延期提醒
+     */
+    @PostMapping("workDeliveryOffset")
+    public R workDeliveryOffset(@RequestBody WorkDeliveryOffsetReq req) {
+        log.info("手动调用作业延期提醒,入参:{}",req);
+        return apsProcessOperationProcessEquService.workDeliveryOffset(req);
+    }
+
+    /**
+     * 作业延期提醒定时任务
+     */
+    @Scheduled(cron = "0 0 23 * * *")
+    public R workDeliveryOffset() {
+        log.info("定时任务调用作业延期提醒");
+        WorkDeliveryOffsetReq req = new WorkDeliveryOffsetReq();
+        return apsProcessOperationProcessEquService.workDeliveryOffset(req);
+    }
 }
 }