package com.aps.core.service.impl.ApsPlate; 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.ApsBom; import com.aps.core.domain.ApsMaterialStorageManagement; import com.aps.core.domain.ApsPlate.*; import com.aps.core.domain.ApsStandardProcessRouteLine; import com.aps.core.mapper.*; import com.aps.core.service.ApsPlate.*; import com.aps.core.service.IApsBomService; import com.aps.core.service.IApsMaterialStorageManagementService; import com.aps.core.service.IApsStandardProcessRouteLineService; import com.aps.system.api.domain.SysDictData; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.*; /** * 钣金工单标准需求Service业务层处理 * * @author zhl * @date 2025-05-06 */ @Slf4j @Service public class ApsPlateStandardRequireServiceImpl implements IApsPlateStandardRequireService { @Autowired private ApsPlateStandardRequireMapper apsPlateStandardRequireMapper; @Resource ApsPlatePlanMapper platePlanMapper; @Resource IApsBomService bomLineService; @Resource ApsPlateStandardRequireBomStockDetailMapper plateBomStockDetailMapper; @Resource IApsPlateStandardRequireBomStockDetailService bomStockDetailService; @Resource ApsPlateStandardRequireBomOrderDetailMapper plateBomOrderDetailMapper; @Autowired private IApsPlatePlanService apsPlatePlanService; @Resource IApsPlateStandardRequireBomOrderDetailService bomOrderDetailService; @Resource IApsStandardProcessRouteLineService routeLineService; @Resource IApsPlateStandardRequireErrorService requireErrorService; @Resource private IApsMaterialStorageManagementService storageManagementService; @Resource IApsPlateStandardRequireBatchService requireBatchService; /** * 查询钣金工单标准需求 * * @param id 钣金工单标准需求主键 * @return 钣金工单标准需求 */ @Override public ApsPlateStandardRequire selectApsPlateStandardRequireById(Long id) { return apsPlateStandardRequireMapper.selectApsPlateStandardRequireById(id); } /** * 查询钣金工单标准需求列表 * * @param apsPlateStandardRequire 钣金工单标准需求 * @return 钣金工单标准需求 */ @Override public List selectApsPlateStandardRequireList(ApsPlateStandardRequire apsPlateStandardRequire) { return apsPlateStandardRequireMapper.selectApsPlateStandardRequireList(apsPlateStandardRequire); } /** * 新增钣金工单标准需求 * * @param apsPlateStandardRequire 钣金工单标准需求 * @return 结果 */ @Override public int insertApsPlateStandardRequire(ApsPlateStandardRequire apsPlateStandardRequire) { apsPlateStandardRequire.setCreateTime(DateUtils.getNowDate()); return apsPlateStandardRequireMapper.insertApsPlateStandardRequire(apsPlateStandardRequire); } /** * 修改钣金工单标准需求 * * @param apsPlateStandardRequire 钣金工单标准需求 * @return 结果 */ @Override public int updateApsPlateStandardRequire(ApsPlateStandardRequire apsPlateStandardRequire) { apsPlateStandardRequire.setUpdateTime(DateUtils.getNowDate()); return apsPlateStandardRequireMapper.updateApsPlateStandardRequire(apsPlateStandardRequire); } /** * 批量删除钣金工单标准需求 * * @param ids 需要删除的钣金工单标准需求主键 * @return 结果 */ @Override public int deleteApsPlateStandardRequireByIds(Long[] ids) { return apsPlateStandardRequireMapper.deleteApsPlateStandardRequireByIds(ids); } /** * 删除钣金工单标准需求信息 * * @param id 钣金工单标准需求主键 * @return 结果 */ @Override public int deleteApsPlateStandardRequireById(Long id) { return apsPlateStandardRequireMapper.deleteApsPlateStandardRequireById(id); } /** * 钣金供应缺口报表 * @param apsPlateStandardRequire * @return */ @Override public List selectPlateSupplyGapList(ApsPlateStandardRequire apsPlateStandardRequire) { return apsPlateStandardRequireMapper.selectPlateSupplyGapList(apsPlateStandardRequire); } @Transactional @Override public void generatorPlan(String batchNum){ requireBatchService.initRequireBatch(batchNum); /*定义工厂为南通 */ String plantCode="FORTUNA"; /*获取钣金主单信息*/ List mainPlans = platePlanMapper.selectPlatePlanByPlantMajor(plantCode); Hashtable usedStorage = new Hashtable<>(); /*内存中存储子件工单*/ Hashtable> subPlans = new Hashtable<>(); log.info("开始生成需求:"); for (ApsPlatePlan mainPlan : mainPlans) { String itemNumber = mainPlan.getItemNumber(); /*根据料号 获取BOM Header */ List requiresList=new ArrayList<>(); List stockDetailsList=new ArrayList<>(); List orderDetailsList=new ArrayList<>(); log.info("开始生成需求:工单号:"+mainPlan.getDocumentNumber()); /*当前Bom节点处理完成后,处理下级BOM*/ getBomRequires(plantCode, "0","0",itemNumber,BigDecimal.ONE, batchNum, null , mainPlan, requiresList, 0L,stockDetailsList,orderDetailsList,usedStorage,subPlans ); // 批量插入以提高性能 if (!requiresList.isEmpty()) { int batchSize = 1000; for (int i = 0; i < requiresList.size(); i += batchSize) { int end = Math.min(i + batchSize, requiresList.size()); List batch = requiresList.subList(i, end); apsPlateStandardRequireMapper.batchInsert(batch); } } 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 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 batch = orderDetailsList.subList(i, end); plateBomOrderDetailMapper.batchInsert(batch); } } } /*集中保存库存信息*/ if(!usedStorage.isEmpty()){ usedStorage.forEach((key, sm) -> { storageManagementService.updateRemainderStock(sm.getId(),sm.getRemainderStock(),sm.getVersion()); }); } if(!subPlans.isEmpty()){ subPlans.forEach((key, subPlansList) -> { subPlansList.forEach(x->{ if (!x.getUnmatchedQuantity().equals(x.getProductionQuantity())){ platePlanMapper.updatePlanUnMatchQtyAndVersion(x); } }); }); } } /** * 构建需求信息 * * @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 allRequires, Long level,List stockDetailsList, List orderDetailsList, Hashtable usedStorage, Hashtable> subPlans ) { /*构建需求信息*/ 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*/ Optional itemStorageOpt = Optional.ofNullable(usedStorage.get(itemCode)) ; if(itemStorageOpt.isEmpty()){ itemStorageOpt = storageManagementService.getRdsStorage(plant, itemCode); } if (itemStorageOpt.isPresent()) { ApsMaterialStorageManagement itemStorage=itemStorageOpt.get(); BigDecimal remainderStock =BigDecimal.ZERO; if(null!=itemStorage.getRemainderStock()){ 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, itemStorage); } } /*未匹配数量,默认为净需求*/ 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,subPlans); } 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 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,subPlans ); }); } } } /** * 获取钣金计划 预留天数 * */ private Long getReservedDays(){ long days=0L; List 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 orderDetailsList, Hashtable> subPlans) { BigDecimal netRequirement = require.getNetRequirement(); require.setMatchMode("工单匹配"); require.setMatchState("已匹配"); if (netRequirement.compareTo(BigDecimal.ZERO) > 0) { ApsPlatePlan platePlan = apsPlatePlanService.selectUnMatchPlateSubPlan(require.getOrgCode(), require.getBomLineCode(),subPlans); /*子件工单的未匹配数量 作为当前的库存*/ while (platePlan != null && netRequirement.compareTo(BigDecimal.ZERO) > 0) { BigDecimal stock = platePlan.getUnmatchedQuantity(); if (netRequirement.compareTo(stock) < 0) { /* 库存数量 大于 净需求数量*/ /* 净需求数量=0 ,子件工单未匹配数量= 库存-净需求*/ BigDecimal subtract = stock.subtract(netRequirement); platePlan.setUnmatchedQuantity(subtract); platePlan.setVersion(platePlan.getVersion() + 1); 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; platePlan.setUnmatchedQuantity(subtract); platePlan.setVersion(platePlan.getVersion() + 1); 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); platePlan.setUnmatchedQuantity(BigDecimal.ZERO); platePlan.setVersion(platePlan.getVersion() + 1); /*工单 未匹配数量为0 全部用于匹配需求*/ bomOrderDetailService.savePlastPlanAndBomOrderDetail(require, platePlan, BigDecimal.ZERO, stock, netRequirement,orderDetailsList); /*净需求未被满足,需要继续匹配*/ platePlan = apsPlatePlanService.selectUnMatchPlateSubPlan(require.getOrgCode(), require.getBomLineCode(),subPlans); /*剩余净需求*/ 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(); } }