| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.production.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.exception.base.BaseException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.bean.BeanUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.production.bean.dto.ProductionPlanDto; |
| | | import com.ruoyi.production.bean.dto.ProductionPlanImportDto; |
| | | import com.ruoyi.production.bean.vo.ProductionPlanVo; |
| | | import com.ruoyi.production.mapper.ProductionOrderMapper; |
| | | import com.ruoyi.production.mapper.ProductionPlanMapper; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionPlan; |
| | | import com.ruoyi.production.service.ProductionOrderService; |
| | | import com.ruoyi.production.service.ProductionPlanService; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class ProductionPlanServiceImpl extends ServiceImpl<ProductionPlanMapper, ProductionPlan> implements ProductionPlanService { |
| | | |
| | | private static final int PLAN_STATUS_WAIT = 0; |
| | | private static final int PLAN_STATUS_PARTIAL = 1; |
| | | private static final int PLAN_STATUS_ISSUED = 2; |
| | | |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOrderService productionOrderService; |
| | | |
| | | @Override |
| | | public IPage<ProductionPlanVo> listPage(Page<ProductionPlanDto> page, ProductionPlanDto productionPlanDto) { |
| | | return productionPlanMapper.listPage(page, productionPlanDto); |
| | | } |
| | | |
| | | /** |
| | | * åå¹¶ç产计åå¹¶ä¸åç产订åã |
| | | * ä¸å¡çº¦æï¼ |
| | | * 1. ä»
å
许åä¸äº§ååå·ç计ååå¹¶ï¼ |
| | | * 2. å·²ä¸åæé¨åä¸åç计åç¦æ¢å次åå¹¶ï¼ |
| | | * 3. ä¸åæ°éä¸è½å¤§äºæé计åéæ±æ»éï¼ |
| | | * 4. 订åå建ç»ä¸å¤ç¨ ProductionOrderService.saveProductionOrderï¼ç¡®ä¿å·¥èº/BOM/颿䏻åçåç»é»è¾ä¸è´ã |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean combine(ProductionPlanDto productionPlanDto) { |
| | | if (productionPlanDto == null || productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) { |
| | | return false; |
| | | } |
| | | |
| | | List<Long> planIds = productionPlanDto.getIds().stream() |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (planIds.isEmpty()) { |
| | | throw new ServiceException("ä¸åå¤±è´¥ï¼æªéæ©ç产计å"); |
| | | } |
| | | |
| | | List<ProductionPlanDto> planLists = productionPlanMapper.selectWithMaterialByIds(planIds); |
| | | if (planLists == null || planLists.isEmpty() || planLists.size() != planIds.size()) { |
| | | throw new ServiceException("ä¸å失败ï¼ç产计åä¸åå¨æå·²è¢«å é¤"); |
| | | } |
| | | |
| | | ProductionPlanDto firstPlan = planLists.getFirst(); |
| | | if (firstPlan.getProductModelId() == null) { |
| | | throw new ServiceException("ä¸å失败ï¼ç产计å缺å°äº§ååå·"); |
| | | } |
| | | |
| | | boolean hasDifferentModel = planLists.stream() |
| | | .anyMatch(item -> !Objects.equals(item.getProductModelId(), firstPlan.getProductModelId())); |
| | | if (hasDifferentModel) { |
| | | throw new BaseException("åå¹¶å¤±è´¥ï¼æéç产计åç产ååå·ä¸ä¸è´"); |
| | | } |
| | | |
| | | boolean hasIssuedPlan = planLists.stream() |
| | | .anyMatch(item -> item.getStatus() != null && item.getStatus() == PLAN_STATUS_ISSUED); |
| | | if (hasIssuedPlan) { |
| | | throw new BaseException("åå¹¶å¤±è´¥ï¼æéç产计ååå¨å·²ä¸åæé¨åä¸åæ°æ®"); |
| | | } |
| | | |
| | | BigDecimal totalRequiredQuantity = planLists.stream() |
| | | .map(this::resolveRemainingQuantity) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (totalRequiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("ä¸åå¤±è´¥ï¼æéç产计åéæ±æ»éå¿
须大äº0"); |
| | | } |
| | | |
| | | BigDecimal assignedQuantity = productionPlanDto.getTotalAssignedQuantity(); |
| | | if (assignedQuantity == null || assignedQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("ä¸å失败ï¼ä¸åæ°éå¿
须大äº0"); |
| | | } |
| | | if (assignedQuantity.compareTo(totalRequiredQuantity) > 0) { |
| | | throw new ServiceException("ä¸å失败ï¼ä¸åæ°éä¸è½å¤§äºè®¡åéæ±æ»é"); |
| | | } |
| | | |
| | | BigDecimal remainingForOrderBind = assignedQuantity; |
| | | List<Long> issuedPlanIds = new ArrayList<>(); |
| | | for (ProductionPlanDto plan : planLists) { |
| | | BigDecimal remainingQuantity = resolveRemainingQuantity(plan); |
| | | if (remainingForOrderBind.compareTo(BigDecimal.ZERO) <= 0 || remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | BigDecimal issueForThisPlan = remainingForOrderBind.min(remainingQuantity); |
| | | remainingForOrderBind = remainingForOrderBind.subtract(issueForThisPlan); |
| | | if (issueForThisPlan.compareTo(BigDecimal.ZERO) > 0) { |
| | | issuedPlanIds.add(plan.getId()); |
| | | } |
| | | } |
| | | if (issuedPlanIds.isEmpty()) { |
| | | throw new ServiceException("Issue failed, no quantity available for dispatch"); |
| | | } |
| | | |
| | | ProductionOrder productionOrder = new ProductionOrder(); |
| | | productionOrder.setProductionPlanIds(formatPlanIds(issuedPlanIds)); |
| | | productionOrder.setProductModelId(firstPlan.getProductModelId()); |
| | | productionOrder.setQuantity(assignedQuantity); |
| | | productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime()); |
| | | |
| | | boolean saved = productionOrderService.saveProductionOrder(productionOrder); |
| | | if (!saved) { |
| | | throw new ServiceException("ä¸å失败ï¼ç产订åä¿å失败"); |
| | | } |
| | | |
| | | //å·²ä¸åæ°é |
| | | BigDecimal remainingAssignedQuantity = assignedQuantity; |
| | | List<ProductionPlan> updates = new ArrayList<>(); |
| | | for (ProductionPlanDto plan : planLists) { |
| | | BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO); |
| | | if (requiredQuantity.compareTo(BigDecimal.ZERO) < 0) { |
| | | requiredQuantity = BigDecimal.ZERO; |
| | | } |
| | | BigDecimal remainingQuantity = resolveRemainingQuantity(plan); |
| | | BigDecimal historicalIssuedQuantity = requiredQuantity.subtract(remainingQuantity); |
| | | BigDecimal issuedQuantity = BigDecimal.ZERO; |
| | | if (remainingAssignedQuantity.compareTo(BigDecimal.ZERO) > 0 && remainingQuantity.compareTo(BigDecimal.ZERO) > 0) { |
| | | issuedQuantity = remainingAssignedQuantity.min(remainingQuantity); |
| | | remainingAssignedQuantity = remainingAssignedQuantity.subtract(issuedQuantity); |
| | | } |
| | | |
| | | BigDecimal totalIssuedQuantity = historicalIssuedQuantity.add(issuedQuantity); |
| | | int planStatus = resolvePlanStatus(requiredQuantity, totalIssuedQuantity); |
| | | ProductionPlan update = new ProductionPlan(); |
| | | update.setId(plan.getId()); |
| | | update.setStatus(planStatus); |
| | | update.setQuantityIssued(totalIssuedQuantity); |
| | | updates.add(update); |
| | | } |
| | | if (!updates.isEmpty()) { |
| | | this.updateBatchById(updates); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean add(ProductionPlanDto dto) { |
| | | if (StringUtils.isBlank(dto.getMpsNo())) { |
| | | dto.setMpsNo(generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))); |
| | | }else checkMpsNoUnique(dto.getMpsNo(), null); |
| | | dto.setStatus(PLAN_STATUS_WAIT); |
| | | dto.setSource("å
é¨"); |
| | | return productionPlanMapper.insert(dto) > 0; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean update(ProductionPlanDto dto) { |
| | | if (dto == null || dto.getId() == null) { |
| | | throw new ServiceException("ç¼è¾å¤±è´¥ï¼æ°æ®ä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | ProductionPlan old = getById(dto.getId()); |
| | | if (old == null) { |
| | | throw new ServiceException("ç¼è¾å¤±è´¥ï¼ç产计åä¸åå¨"); |
| | | } |
| | | |
| | | if (old.getStatus() != PLAN_STATUS_WAIT) { |
| | | throw new BaseException("ç¼è¾å¤±è´¥ï¼è¯¥ç产计åå·²ä¸åæé¨åä¸åï¼ç¦æ¢ç¼è¾"); |
| | | } |
| | | |
| | | if (StringUtils.isNotBlank(dto.getMpsNo()) && !dto.getMpsNo().equals(old.getMpsNo())) { |
| | | checkMpsNoUnique(dto.getMpsNo(), dto.getId()); |
| | | } |
| | | |
| | | return productionPlanMapper.updateById(dto) > 0; |
| | | } |
| | | |
| | | private void checkMpsNoUnique(String mpsNo, Long excludeId) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = Wrappers.lambdaQuery(); |
| | | wrapper.eq(ProductionPlan::getMpsNo, mpsNo); |
| | | if (excludeId != null) { |
| | | wrapper.ne(ProductionPlan::getId, excludeId); |
| | | } |
| | | if (productionPlanMapper.selectCount(wrapper) > 0) { |
| | | throw new ServiceException("ç产计åå· " + mpsNo + " å·²åå¨"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean delete(List<Long> ids) { |
| | | if (productionPlanMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery().in(ProductionPlan::getId, ids)) |
| | | .stream() |
| | | .anyMatch(p -> p.getStatus() == PLAN_STATUS_PARTIAL || p.getStatus() == PLAN_STATUS_ISSUED)) { |
| | | throw new BaseException("å é¤å¤±è´¥ï¼åå¨å·²ä¸åæé¨åä¸åç计å"); |
| | | } |
| | | |
| | | if (productionOrderMapper.selectList(Wrappers.<ProductionOrder>lambdaQuery().in(ProductionOrder::getProductionPlanIds, ids)) |
| | | .stream() |
| | | .anyMatch(p -> p.getId() != null)) { |
| | | throw new BaseException("å é¤å¤±è´¥ï¼åå¨å
³èç产订å"); |
| | | } |
| | | |
| | | return productionPlanMapper.deleteBatchIds(ids) > 0; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void importProdData(MultipartFile file) { |
| | | if (file == null || file.isEmpty()) { |
| | | throw new ServiceException("导å
¥æ°æ®ä¸è½ä¸ºç©º"); |
| | | } |
| | | ExcelUtil<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class); |
| | | List<ProductionPlanImportDto> list; |
| | | try { |
| | | list = excelUtil.importExcel(file.getInputStream()); |
| | | } catch (Exception e) { |
| | | throw new ServiceException("Excelè§£æå¤±è´¥"); |
| | | } |
| | | if (list == null || list.isEmpty()) { |
| | | throw new ServiceException("Excelæ²¡ææ°æ®"); |
| | | } |
| | | Set<String> mpsNos = new HashSet<>(); |
| | | for (int i = 0; i < list.size(); i++) { |
| | | ProductionPlanImportDto dto = list.get(i); |
| | | String mpsNo = dto.getMpsNo(); |
| | | if (StringUtils.isEmpty(mpsNo)) { |
| | | generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); |
| | | } |
| | | if (!mpsNos.add(mpsNo)) { |
| | | throw new ServiceException("导å
¥å¤±è´¥ï¼Excel ä¸åå¨éå¤çç³è¯·åç¼å· " + mpsNo); |
| | | } |
| | | } |
| | | Long existApplyNoCount = baseMapper.selectCount(Wrappers.<ProductionPlan>lambdaQuery() |
| | | .in(ProductionPlan::getMpsNo, mpsNos)); |
| | | if (existApplyNoCount > 0) { |
| | | List<String> existMpsNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery() |
| | | .in(ProductionPlan::getMpsNo, mpsNos)) |
| | | .stream() |
| | | .map(ProductionPlan::getMpsNo) |
| | | .collect(Collectors.toList()); |
| | | throw new ServiceException("导å
¥å¤±è´¥ï¼ç产计åå·å·²åå¨: " + String.join(", ", existMpsNos)); |
| | | } |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | List<ProductionPlan> entityList = list.stream().map(dto -> { |
| | | ProductionPlan entity = new ProductionPlan(); |
| | | BeanUtils.copyProperties(dto, entity); |
| | | entity.setStatus(PLAN_STATUS_WAIT); |
| | | entity.setSource("å
é¨"); |
| | | entity.setCreateTime(now); |
| | | entity.setUpdateTime(now); |
| | | return entity; |
| | | }).collect(Collectors.toList()); |
| | | this.saveBatch(entityList); |
| | | } |
| | | |
| | | @Override |
| | | public void exportProdData(HttpServletResponse response, List<Long> ids) { |
| | | List<ProductionPlanDto> list = productionPlanMapper.selectWithMaterialByIds(ids); |
| | | List<ProductionPlanImportDto> exportList = new ArrayList<>(); |
| | | for (ProductionPlanDto entity : list) { |
| | | ProductionPlanImportDto dto = new ProductionPlanImportDto(); |
| | | BeanUtils.copyProperties(entity, dto); |
| | | exportList.add(dto); |
| | | } |
| | | ExcelUtil<ProductionPlanImportDto> util = new ExcelUtil<>(ProductionPlanImportDto.class); |
| | | util.exportExcel(response, exportList, "主ç产计å"); |
| | | } |
| | | |
| | | private BigDecimal resolveRemainingQuantity(ProductionPlan plan) { |
| | | if (plan == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO); |
| | | if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | BigDecimal issuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO); |
| | | if (issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return requiredQuantity; |
| | | } |
| | | if (issuedQuantity.compareTo(requiredQuantity) >= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return requiredQuantity.subtract(issuedQuantity); |
| | | } |
| | | |
| | | private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) { |
| | | if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return PLAN_STATUS_WAIT; |
| | | } |
| | | if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return PLAN_STATUS_WAIT; |
| | | } |
| | | return issuedQuantity.compareTo(requiredQuantity) < 0 ? PLAN_STATUS_PARTIAL : PLAN_STATUS_ISSUED; |
| | | } |
| | | |
| | | private String formatPlanIds(List<Long> planIds) { |
| | | return planIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .map(String::valueOf) |
| | | .collect(Collectors.joining(",", "[", "]")); |
| | | } |
| | | |
| | | private String generateNextPlanNo(String datePrefix) { |
| | | QueryWrapper<ProductionPlan> queryWrapper = new QueryWrapper<>(); |
| | | queryWrapper.likeRight("mps_no", "JH" + datePrefix); |
| | | queryWrapper.orderByDesc("mps_no"); |
| | | queryWrapper.last("LIMIT 1"); |
| | | ProductionPlan latestPlan = productionPlanMapper.selectOne(queryWrapper); |
| | | int sequence = 1; |
| | | if (latestPlan != null && latestPlan.getMpsNo() != null && !latestPlan.getMpsNo().isEmpty()) { |
| | | String sequenceStr = latestPlan.getMpsNo().substring(("JH" + datePrefix).length()); |
| | | try { |
| | | sequence = Integer.parseInt(sequenceStr) + 1; |
| | | } catch (NumberFormatException e) { |
| | | sequence = 1; |
| | | } |
| | | } |
| | | return "JH" + datePrefix + String.format("%04d", sequence); |
| | | } |
| | | } |