| | |
| | | package com.aps.core.service.impl; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.*; |
| | | |
| | | import cn.hutool.core.util.IdUtil; |
| | | import com.aps.common.core.utils.DateUtils; |
| | | import com.aps.common.security.utils.DictUtils; |
| | | import com.aps.common.security.utils.SecurityUtils; |
| | | import com.aps.core.domain.*; |
| | | import com.aps.core.mapper.*; |
| | | import com.aps.core.service.*; |
| | | import com.aps.system.api.domain.SysDictData; |
| | | import jakarta.annotation.Resource; |
| | | import com.aps.core.domain.ApsPlateStandardRequire; |
| | | import com.aps.core.mapper.ApsPlateStandardRequireMapper; |
| | | import com.aps.core.service.IApsPlateStandardRequireService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import com.aps.core.service.IApsPlateStandardRequireService; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.List; |
| | | |
| | |
| | | * @author zhl |
| | | * @date 2025-05-06 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class ApsPlateStandardRequireServiceImpl implements IApsPlateStandardRequireService |
| | | { |
| | | @Autowired |
| | | private ApsPlateStandardRequireMapper apsPlateStandardRequireMapper; |
| | | |
| | | @Resource |
| | | private ApsPlateStandardRequireBatchMapper requireBatchMapper; |
| | | |
| | | @Resource |
| | | ApsPlatePlanMapper platePlanMapper; |
| | | |
| | | @Resource |
| | | ApsBomHeaderMapper bomHeaderMapper; |
| | | @Resource |
| | | IApsBomService bomLineService; |
| | | |
| | | @Resource |
| | | ApsMaterialStorageManagementMapper itemStorageMapper; |
| | | |
| | | @Resource |
| | | ApsPlateStandardRequireBomStockDetailMapper plateBomStockDetailMapper; |
| | | |
| | | @Resource |
| | | IApsPlateStandardRequireBomStockDetailService bomStockDetailService; |
| | | @Resource |
| | | ApsStandardProcessRouteHeaderMapper standardProcessRouteHeaderMapper; |
| | | |
| | | @Resource |
| | | ApsStandardProcessRouteLineMapper ApsStandardProcessRouteLineMapper; |
| | | |
| | | @Resource |
| | | ApsPlateStandardRequireBomOrderDetailMapper plateBomOrderDetailMapper; |
| | | |
| | | @Autowired |
| | | private ApsPlatePlanMapper apsPlatePlanMapper; |
| | | |
| | | @Resource |
| | | IApsPlateStandardRequireBomOrderDetailService bomOrderDetailService; |
| | | |
| | | @Resource |
| | | IApsStandardProcessRouteLineService routeLineService; |
| | | |
| | | @Resource |
| | | IApsPlateStandardRequireBatchService requireBatchService; |
| | | @Resource |
| | | private IApsMaterialStorageManagementService storageManagementService; |
| | | |
| | | /** |
| | | * 查询钣金工单标准需求 |
| | |
| | | public List<ApsPlateStandardRequire> selectPlateSupplyGapList(ApsPlateStandardRequire apsPlateStandardRequire) { |
| | | return apsPlateStandardRequireMapper.selectPlateSupplyGapList(apsPlateStandardRequire); |
| | | } |
| | | |
| | | @Transactional |
| | | @Override |
| | | public void generatorPlan(){ |
| | | |
| | | /*初始化数据*/ |
| | | requireBatchService.initRequireBatch(); |
| | | /*定义工厂为南通 */ |
| | | String plantCode="FORTUNA"; |
| | | /*定义主单类型为钣金主单*/ |
| | | String mainOrderType = "main"; |
| | | /*生成新批次号*/ |
| | | String batchNum= requireBatchService.getNewBatchNumber(); |
| | | /*获取钣金主单信息*/ |
| | | List<ApsPlatePlan> mainPlans = platePlanMapper.selectPlatePlanByPlantMajor(plantCode,mainOrderType); |
| | | Hashtable<String, ApsMaterialStorageManagement> usedStorage = new Hashtable<>(); |
| | | log.info("开始生成需求:"); |
| | | for (ApsPlatePlan mainPlan : mainPlans) { |
| | | String itemNumber = mainPlan.getItemNumber(); |
| | | /*根据料号 获取BOM Header */ |
| | | /*当前Bom节点处理完成后,处理下级BOM*/ |
| | | |
| | | List<ApsPlateStandardRequire> requiresList=new ArrayList<>(); |
| | | List<ApsPlateStandardRequireBomStockDetail> stockDetailsList=new ArrayList<>(); |
| | | List<ApsPlateStandardRequireBomOrderDetail> orderDetailsList=new ArrayList<>(); |
| | | |
| | | log.info("开始生成需求:工单号:"+mainPlan.getDocumentNumber()); |
| | | getBomRequires(plantCode, "0","0",itemNumber,BigDecimal.ONE, batchNum, null |
| | | , mainPlan, requiresList, 0L,stockDetailsList,orderDetailsList,usedStorage |
| | | ); |
| | | |
| | | // 批量插入以提高性能 |
| | | if (!requiresList.isEmpty()) { |
| | | int batchSize = 1000; |
| | | for (int i = 0; i < requiresList.size(); i += batchSize) { |
| | | int end = Math.min(i + batchSize, requiresList.size()); |
| | | List<ApsPlateStandardRequire> batch = requiresList.subList(i, end); |
| | | apsPlateStandardRequireMapper.batchInsert(batch); |
| | | } |
| | | } |
| | | log.info("生成完成"); |
| | | // if(!stockDetailsList.isEmpty()){ |
| | | // int batchSize = 1000; |
| | | // stockDetailsList.forEach(x->x.setId(IdUtil.getSnowflakeNextId())); |
| | | // for (int i = 0; i < stockDetailsList.size(); i += batchSize) { |
| | | // int end = Math.min(i + batchSize, stockDetailsList.size()); |
| | | // List<ApsPlateStandardRequireBomStockDetail> batch = stockDetailsList.subList(i, end); |
| | | // plateBomStockDetailMapper.batchInsert(batch); |
| | | // } |
| | | // } |
| | | // |
| | | // if(!orderDetailsList.isEmpty()){ |
| | | // int batchSize = 1000; |
| | | // orderDetailsList.forEach(x->x.setId(IdUtil.getSnowflakeNextId())); |
| | | // for (int i = 0; i < orderDetailsList.size(); i += batchSize) { |
| | | // int end = Math.min(i + batchSize, orderDetailsList.size()); |
| | | // List<ApsPlateStandardRequireBomOrderDetail> batch = orderDetailsList.subList(i, end); |
| | | // plateBomOrderDetailMapper.batchInsert(batch); |
| | | // } |
| | | // } |
| | | /*集中保存库存信息*/ |
| | | /* if(!usedStorage.isEmpty()){ |
| | | usedStorage.forEach((key, sm) -> { |
| | | storageManagementService.updateRemainderStock(sm.getId(),sm.getRemainderStock(),sm.getVersion()); |
| | | }); |
| | | }*/ |
| | | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 构建需求信息 |
| | | * |
| | | * @param plant 工厂代码 |
| | | * @param bomHeaderCode BOM上级物料编码 |
| | | * @param bomLineId BOM行ID |
| | | * @param itemCode 物料代码 |
| | | * @param itemNum 物料数量 |
| | | * @param batchNum 批次号 |
| | | * @param upLevelStartDate 上级工序的开始日期 |
| | | * @param plan 计划对象 |
| | | * @param allRequires 所有需求的列表 |
| | | * @param level 层级 |
| | | * @param stockDetailsList 库存匹配记录 |
| | | * @param orderDetailsList 子件工单匹配记录 |
| | | */ |
| | | private void getBomRequires(String plant, String bomHeaderCode, String bomLineId,String itemCode,BigDecimal itemNum, |
| | | String batchNum, Date upLevelStartDate, ApsPlatePlan plan, |
| | | List<ApsPlateStandardRequire> allRequires, |
| | | Long level,List<ApsPlateStandardRequireBomStockDetail> stockDetailsList, |
| | | List<ApsPlateStandardRequireBomOrderDetail> orderDetailsList, |
| | | Hashtable<String, ApsMaterialStorageManagement> usedStorage |
| | | ) { |
| | | /*构建需求信息*/ |
| | | ApsPlateStandardRequire require = new ApsPlateStandardRequire(); |
| | | require.setId(IdUtil.getSnowflakeNextId()); |
| | | require.setRequireTrackId(plan.getId() + ""); |
| | | require.setBatchNumber(batchNum); |
| | | require.setDocNum(plan.getDocumentNumber()); |
| | | require.setOrgCode(plant); |
| | | require.setBomHeaderCode(bomHeaderCode); |
| | | require.setBomLineId(bomLineId); |
| | | require.setBomLineCode(itemCode); |
| | | require.setBomLineLevel(level); |
| | | require.setOrderCreateTime(plan.getOrderCreateTime()); |
| | | require.setCreateTime(DateUtils.getNowDate()); |
| | | require.setCreateBy(SecurityUtils.getUsername()); |
| | | require.setDelFlag("0"); |
| | | require.setProductionBase(plan.getProductionBase()); |
| | | /*BOM用量 level0=1 */ |
| | | if (level == 0) { |
| | | require.setBomUseAmount(BigDecimal.ONE); |
| | | } else { |
| | | require.setBomUseAmount(itemNum); |
| | | } |
| | | /*计算需求数量*/ |
| | | require.setRequireAmount(itemNum.multiply(plan.getProductionQuantity())); |
| | | /*查找库存,计算净需求,保存剩余库存,保存库存扣减明细*/ |
| | | /*默认净需求为BOM用量*/ |
| | | require.setNetRequirement(require.getRequireAmount()); |
| | | |
| | | /*读取库存信息的优先级-> 内存、redis、db*/ |
| | | /* ApsMaterialStorageManagement storage = usedStorage.get(itemCode); |
| | | if(storage==null){ |
| | | storage = storageManagementService.getRdsStorage(plant, itemCode); |
| | | if(storage!=null){ |
| | | *//*剩余库存*//* |
| | | remainderStock = storage.getRemainderStock(); |
| | | } |
| | | }*/ |
| | | Optional<ApsMaterialStorageManagement> itemStorageOpt = storageManagementService.getItemStorage(plant, itemCode); |
| | | if (itemStorageOpt.isPresent()) { |
| | | ApsMaterialStorageManagement itemStorage=itemStorageOpt.get(); |
| | | BigDecimal remainderStock =itemStorage.getRemainderStock(); |
| | | /*计算净需求 默认=需求数量*/ |
| | | if (remainderStock.compareTo(BigDecimal.ZERO) == 0) { |
| | | require.setNetRequirement(require.getBomUseAmount()); |
| | | } |
| | | if (remainderStock.compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal subtract = require.getBomUseAmount().subtract(remainderStock); |
| | | BigDecimal deductionAmount = BigDecimal.ZERO; |
| | | BigDecimal afterStockAmount = BigDecimal.ZERO; |
| | | if (subtract.compareTo(BigDecimal.ZERO) >= 0) { |
| | | deductionAmount = remainderStock; |
| | | afterStockAmount = BigDecimal.ZERO; |
| | | require.setNetRequirement(subtract); |
| | | } else { |
| | | deductionAmount = require.getBomUseAmount(); |
| | | afterStockAmount = remainderStock.subtract(deductionAmount); |
| | | require.setNetRequirement(BigDecimal.ZERO); |
| | | } |
| | | /*记录库存剩余数量,记录库存使用记录*/ |
| | | bomStockDetailService.saveStorageAndDetail(itemStorage, plan, bomLineId, itemCode, batchNum, deductionAmount |
| | | , afterStockAmount, require.getId(), stockDetailsList |
| | | ); |
| | | /*更新内存中的库存使用信息*/ |
| | | // usedStorage.put(itemCode, storage); |
| | | } |
| | | } |
| | | /*未匹配数量,默认为净需求*/ |
| | | require.setUnmatchedDemandAmount(require.getNetRequirement()); |
| | | /*工艺路线总需求*/ |
| | | ApsStandardProcessRouteLine routeHeader = routeLineService.getRouteHeaderRouteTime(require); |
| | | String routeId = routeHeader.getRouteId(); |
| | | BigDecimal totalRouteTime = routeHeader.getRouteTime(); |
| | | long millisecond = 60 * 60 * 1000L; |
| | | long totalRouteMillisecond = totalRouteTime.multiply(BigDecimal.valueOf(millisecond)).longValue(); |
| | | require.setProcessRouteId(routeId); |
| | | require.setProcessRouteHours(String.valueOf(totalRouteTime)); |
| | | |
| | | |
| | | /*预留天数*/ |
| | | Long reservedDay = getReservedDays(); |
| | | /*设置完成日期*/ |
| | | if(level==0){ |
| | | /*完成时间,level=0 时默认为工单的计划完成日期*/ |
| | | require.setCompleteDate(plan.getPlanEndDay()); |
| | | require.setDemandDate(plan.getPlanEndDay()); |
| | | } else { |
| | | /* 当前需求完成日期为上阶层需求的开始时间 - 预留天数(转换为毫秒)*/ |
| | | long reservedMillisecond = reservedDay * 24 * millisecond; |
| | | Date completeDate = new Date(); |
| | | completeDate.setTime(upLevelStartDate.getTime() - reservedMillisecond); |
| | | require.setCompleteDate(completeDate); |
| | | require.setDemandDate(completeDate); |
| | | } |
| | | |
| | | |
| | | /*设置开始时间*/ |
| | | Date startDay = new Date(); |
| | | /*开始时间 默认为 完成时间 如果净需求>0 开始时间=结束时间-工序总工时*/ |
| | | startDay.setTime(require.getCompleteDate().getTime()); |
| | | if(require.getNetRequirement().compareTo(BigDecimal.ZERO)>0){ |
| | | startDay.setTime(require.getCompleteDate().getTime() - totalRouteMillisecond); |
| | | } |
| | | |
| | | require.setStartDate(startDay); |
| | | |
| | | /*计算是否有风险*/ |
| | | require.setHasDelayRisk("0"); |
| | | Date dateZero = getDateZero(DateUtils.getNowDate()); |
| | | if (startDay.before(dateZero)) { |
| | | require.setHasDelayRisk("1"); |
| | | } |
| | | |
| | | /*生产基地*/ |
| | | require.setProductionBase(plan.getProductionBase()); |
| | | |
| | | require.setMatchState("已匹配"); |
| | | require.setMatchMode("库存匹配"); |
| | | if (require.getNetRequirement().compareTo(BigDecimal.ZERO) > 0) { |
| | | require.setMatchMode("工单匹配"); |
| | | /*使用子件工单进行需求匹配*/ |
| | | matchRequireAndSubPlan(require,orderDetailsList); |
| | | } |
| | | allRequires.add(require); |
| | | log.info("已生成需求:"+plan.getDocumentNumber()+"bomHeaderCode:"+bomHeaderCode+"bomLineCode:"+itemCode+"bomLevel:"+level); |
| | | if (require.getNetRequirement().compareTo(BigDecimal.ZERO) > 0) { |
| | | /*当前Bom节点处理完成后,处理下级BOM*/ |
| | | long nextLevel=level+1; |
| | | List<ApsBom> bomLineList = bomLineService.selectRdsBomLineList(plant, itemCode); |
| | | if (!bomLineList.isEmpty()) { |
| | | bomLineList.forEach(line -> { |
| | | getBomRequires(plant, itemCode, line.getBomLineId(),line.getItemCode() ,line.getNum() , batchNum |
| | | , require.getStartDate(), plan, allRequires, nextLevel,stockDetailsList |
| | | ,orderDetailsList,usedStorage |
| | | ); |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取钣金计划 预留天数 |
| | | * */ |
| | | private Long getReservedDays(){ |
| | | long days=0L; |
| | | List<SysDictData> dictDataList = DictUtils.getDictCache("plate_plan_param"); |
| | | if (dictDataList != null && !dictDataList.isEmpty()) { |
| | | days = Long.parseLong(dictDataList.get(0).getDictValue()); |
| | | } |
| | | return days; |
| | | } |
| | | |
| | | /** |
| | | * 使用子件工单匹配需求中的净需求 |
| | | * */ |
| | | |
| | | private void matchRequireAndSubPlan(ApsPlateStandardRequire require, List<ApsPlateStandardRequireBomOrderDetail> orderDetailsList) { |
| | | BigDecimal netRequirement = require.getNetRequirement(); |
| | | require.setMatchMode("工单匹配"); |
| | | require.setMatchState("已匹配"); |
| | | if (netRequirement.compareTo(BigDecimal.ZERO) > 0) { |
| | | ApsPlatePlan platePlan = apsPlatePlanMapper.selectUnMatchPlateSubPlan(require.getOrgCode(), require.getBomLineCode()); |
| | | /*子件工单的未匹配数量 作为当前的库存*/ |
| | | while (platePlan != null && netRequirement.compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal stock = platePlan.getUnmatchedQuantity(); |
| | | if (netRequirement.compareTo(stock) < 0) { |
| | | /* 库存数量 大于 净需求数量*/ |
| | | /* 净需求数量=0 ,子件工单未匹配数量= 库存-净需求*/ |
| | | BigDecimal subtract = stock.subtract(netRequirement); |
| | | bomOrderDetailService.savePlastPlanAndBomOrderDetail(require, platePlan, subtract, stock, netRequirement,orderDetailsList); |
| | | netRequirement = BigDecimal.ZERO; |
| | | |
| | | require.setUnmatchedDemandAmount(BigDecimal.ZERO); |
| | | /*净需求已经被满足,不需要继续匹配*/ |
| | | |
| | | } else if (netRequirement.compareTo(stock) == 0) { |
| | | /*净需求数量 == 库存数量*/ |
| | | |
| | | BigDecimal subtract = BigDecimal.ZERO; |
| | | bomOrderDetailService.savePlastPlanAndBomOrderDetail(require, platePlan, subtract, stock, netRequirement,orderDetailsList); |
| | | netRequirement = BigDecimal.ZERO; |
| | | |
| | | require.setUnmatchedDemandAmount(BigDecimal.ZERO); |
| | | /*净需求已经被满足,不需要继续匹配*/ |
| | | } |
| | | if (netRequirement.compareTo(stock) > 0) { |
| | | /*需求大于库存*/ |
| | | /*净需求 被部分满足 */ |
| | | BigDecimal rest = netRequirement.subtract(stock); |
| | | |
| | | /*工单 未匹配数量为0 全部用于匹配需求*/ |
| | | bomOrderDetailService.savePlastPlanAndBomOrderDetail(require, platePlan, BigDecimal.ZERO, stock, netRequirement,orderDetailsList); |
| | | /*净需求未被满足,需要继续匹配*/ |
| | | platePlan = apsPlatePlanMapper.selectUnMatchPlateSubPlan(require.getOrgCode(), require.getBomLineCode()); |
| | | /*剩余净需求*/ |
| | | netRequirement = rest; |
| | | require.setUnmatchedDemandAmount(rest); |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取日期零点 |
| | | * */ |
| | | private Date getDateZero(Date date){ |
| | | Calendar calendar = Calendar.getInstance(); |
| | | calendar.setTime(date); |
| | | calendar.set(Calendar.HOUR_OF_DAY, 0); |
| | | calendar.set(Calendar.MINUTE, 0); |
| | | calendar.set(Calendar.SECOND, 0); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTime(); |
| | | } |
| | | |
| | | } |