| | |
| | | package com.ruoyi.production.service.impl; |
| | | |
| | | import cn.hutool.core.bean.BeanUtil; |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | | 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.deepoove.poi.XWPFTemplate; |
| | | import com.deepoove.poi.data.PictureRenderData; |
| | | import com.deepoove.poi.data.PictureType; |
| | | import com.deepoove.poi.data.Pictures; |
| | | import com.ruoyi.basic.dto.StorageAttachmentDTO; |
| | | import com.ruoyi.basic.dto.StorageBlobVO; |
| | | import com.ruoyi.basic.enums.RecordTypeEnum; |
| | | import com.ruoyi.basic.utils.FileUtil; |
| | | import com.ruoyi.common.config.FileProperties; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.MatrixToImageWriter; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.production.bean.dto.ProductionOperationTaskDto; |
| | | import com.ruoyi.production.bean.vo.ProductionOperationTaskVo; |
| | | import com.ruoyi.production.mapper.ProductionOrderMapper; |
| | | import com.ruoyi.production.mapper.ProductionOperationTaskMapper; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionOperationTask; |
| | | import com.ruoyi.production.service.ProductionOperationTaskService; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | | import com.ruoyi.project.system.mapper.SysUserMapper; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import java.net.URLEncoder; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class ProductionOperationTaskServiceImpl extends ServiceImpl<ProductionOperationTaskMapper, ProductionOperationTask> implements ProductionOperationTaskService { |
| | | |
| | | private final SysUserMapper sysUserMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | |
| | | private final FileUtil fileUtil; |
| | | |
| | | private final FileProperties fileProperties; |
| | | |
| | | @Value("${file.temp-dir}") |
| | | private String tempDir; |
| | | |
| | | @Override |
| | | public IPage<ProductionOperationTaskVo> pageProductionOperationTask(Page<ProductionOperationTaskDto> page, ProductionOperationTaskDto dto) { |
| | | Page<ProductionOperationTaskVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); |
| | | return baseMapper.pageProductionOperationTask(voPage, dto); |
| | | IPage<ProductionOperationTaskVo> result = baseMapper.pageProductionOperationTask(voPage, dto); |
| | | fillUserNames(result.getRecords()); |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public List<ProductionOperationTaskVo> listProductionOperationTask(ProductionOperationTaskDto dto) { |
| | | return BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOperationTaskVo.class); |
| | | List<ProductionOperationTaskVo> result = BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOperationTaskVo.class); |
| | | fillUserNames(result); |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public ProductionOperationTaskVo getProductionOperationTaskInfo(Long id) { |
| | | ProductionOperationTask item = this.getById(id); |
| | | return item == null ? null : BeanUtil.copyProperties(item, ProductionOperationTaskVo.class); |
| | | if (item == null) { |
| | | return null; |
| | | } |
| | | ProductionOperationTaskVo vo = BeanUtil.copyProperties(item, ProductionOperationTaskVo.class); |
| | | if (item.getProductionOrderId() != null) { |
| | | ProductionOrder productionOrder = productionOrderMapper.selectById(item.getProductionOrderId()); |
| | | if (productionOrder != null) { |
| | | vo.setEndOrder(productionOrder.getEndOrder()); |
| | | } |
| | | } |
| | | fillUserNames(Collections.singletonList(vo)); |
| | | return vo; |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Override |
| | | public boolean removeProductionOperationTask(List<Long> ids) { |
| | | return ids != null && !ids.isEmpty() && this.removeByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public int updateProductWorkOrder(ProductionOperationTaskDto dto) { |
| | | return baseMapper.updateById(dto); |
| | | } |
| | | |
| | | @Override |
| | | public boolean assign(ProductionOperationTaskDto dto) { |
| | | if (dto == null || dto.getId() == null) { |
| | | throw new ServiceException("工单ID不能为空"); |
| | | } |
| | | |
| | | ProductionOperationTask update = new ProductionOperationTask(); |
| | | update.setId(dto.getId()); |
| | | update.setUserIds(dto.getUserIds()); |
| | | int rows = baseMapper.updateById(update); |
| | | if (rows <= 0) { |
| | | throw new ServiceException("工单不存在或已删除"); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private LambdaQueryWrapper<ProductionOperationTask> buildQueryWrapper(ProductionOperationTaskDto dto) { |
| | |
| | | .orderByDesc(ProductionOperationTask::getId); |
| | | } |
| | | |
| | | private void fillUserNames(List<ProductionOperationTaskVo> voList) { |
| | | if (voList == null || voList.isEmpty()) { |
| | | return; |
| | | } |
| | | Set<Long> userIdSet = new LinkedHashSet<>(); |
| | | for (ProductionOperationTaskVo vo : voList) { |
| | | if (vo == null) { |
| | | continue; |
| | | } |
| | | userIdSet.addAll(parseUserIdList(vo.getUserIds(), false)); |
| | | } |
| | | if (userIdSet.isEmpty()) { |
| | | return; |
| | | } |
| | | List<SysUser> userList = sysUserMapper.selectUsersByIds(new ArrayList<>(userIdSet)); |
| | | if (userList == null || userList.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<Long, String> userNameById = userList.stream() |
| | | .filter(item -> item.getUserId() != null) |
| | | .collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName, (left, right) -> left)); |
| | | |
| | | for (ProductionOperationTaskVo vo : voList) { |
| | | if (vo == null) { |
| | | continue; |
| | | } |
| | | List<Long> userIds = parseUserIdList(vo.getUserIds(), false); |
| | | if (userIds.isEmpty()) { |
| | | vo.setUserNames(null); |
| | | continue; |
| | | } |
| | | String userNames = userIds.stream() |
| | | .map(userNameById::get) |
| | | .filter(StringUtils::isNotBlank) |
| | | .collect(Collectors.joining(",")); |
| | | vo.setUserNames(userNames); |
| | | } |
| | | } |
| | | |
| | | private List<Long> parseUserIdList(String userIds, boolean strict) { |
| | | if (StringUtils.isBlank(userIds)) { |
| | | if (strict) { |
| | | throw new ServiceException("userIds格式不正确,必须为JSON数字数组"); |
| | | } |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | String text = userIds.trim(); |
| | | try { |
| | | List<Long> parsed = JSON.parseArray(text, Long.class); |
| | | LinkedHashSet<Long> idSet = parsed == null ? new LinkedHashSet<>() : parsed.stream() |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toCollection(LinkedHashSet::new)); |
| | | if (strict && idSet.isEmpty()) { |
| | | throw new ServiceException("userIds格式不正确,必须为JSON数字数组"); |
| | | } |
| | | return new ArrayList<>(idSet); |
| | | } catch (Exception e) { |
| | | if (strict) { |
| | | throw new ServiceException("userIds格式不正确,必须为JSON数字数组"); |
| | | } |
| | | return new ArrayList<>(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public int updateProductWorkOrder(ProductionOperationTaskDto dto) { |
| | | return baseMapper.updateById(dto); |
| | | public void down(HttpServletResponse response, ProductionOperationTaskDto dto) { |
| | | if (dto == null || dto.getId() == null) { |
| | | throw new ServiceException("工单ID不能为空"); |
| | | } |
| | | |
| | | ProductionOperationTaskDto taskDto = baseMapper.getProductWorkOrderFlowCard(dto.getId()); |
| | | if (taskDto == null) { |
| | | throw new ServiceException("工单不存在,ID=" + dto.getId()); |
| | | } |
| | | String codePath; |
| | | try { |
| | | codePath = new MatrixToImageWriter().code(taskDto.getId().toString(), tempDir); |
| | | } catch (Exception e) { |
| | | throw new ServiceException("生成二维码失败"); |
| | | } |
| | | |
| | | List<Map<String, Object>> images = buildTaskAttachmentImages(taskDto.getId()); |
| | | Map<String, Object> renderData = new HashMap<>(); |
| | | renderData.put("process", taskDto.getProcessName()); |
| | | renderData.put("workOrderNo", taskDto.getWorkOrderNo()); |
| | | renderData.put("productOrderNpsNo", taskDto.getProductOrderNpsNo()); |
| | | renderData.put("productName", taskDto.getProductName()); |
| | | renderData.put("planQuantity", taskDto.getPlanQuantity()); |
| | | renderData.put("model", taskDto.getModel()); |
| | | renderData.put("completeQuantity", taskDto.getCompleteQuantity()); |
| | | renderData.put("scrapQty", taskDto.getScrapQty()); |
| | | renderData.put("planStartTime", taskDto.getPlanStartTime()); |
| | | renderData.put("planEndTime", taskDto.getPlanEndTime()); |
| | | renderData.put("actualStartTime", taskDto.getActualStartTime()); |
| | | renderData.put("actualEndTime", taskDto.getActualEndTime()); |
| | | renderData.put("twoCode", Pictures.ofLocal(codePath).create()); |
| | | renderData.put("images", images.isEmpty() ? null : images); |
| | | |
| | | try (InputStream inputStream = this.getClass().getResourceAsStream("/static/work-order-template.docx")) { |
| | | if (inputStream == null) { |
| | | throw new ServiceException("流转卡模板不存在"); |
| | | } |
| | | XWPFTemplate template = XWPFTemplate.compile(inputStream).render(renderData); |
| | | response.setContentType("application/msword"); |
| | | String fileName = URLEncoder.encode("流转卡", "UTF-8"); |
| | | response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); |
| | | response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".docx"); |
| | | try (OutputStream os = response.getOutputStream()) { |
| | | template.write(os); |
| | | os.flush(); |
| | | } |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("导出失败"); |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> buildTaskAttachmentImages(Long taskId) { |
| | | List<Map<String, Object>> images = new ArrayList<>(); |
| | | StorageAttachmentDTO storageAttachmentDTO = new StorageAttachmentDTO(); |
| | | storageAttachmentDTO.setRecordType(RecordTypeEnum.PRODUCTION_OPERATION_TASK.getType()); |
| | | storageAttachmentDTO.setRecordId(taskId); |
| | | List<StorageBlobVO> taskWorkOrderFiles = |
| | | fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(storageAttachmentDTO); |
| | | if (CollectionUtils.isEmpty(taskWorkOrderFiles)) { |
| | | return images; |
| | | } |
| | | for (StorageBlobVO blobVO : taskWorkOrderFiles) { |
| | | if (blobVO == null) { |
| | | continue; |
| | | } |
| | | PictureType pictureType = resolvePictureType(blobVO); |
| | | if (pictureType == null) { |
| | | continue; |
| | | } |
| | | File imageFile = resolveImageFile(blobVO); |
| | | if (imageFile == null || !imageFile.exists() || !imageFile.isFile()) { |
| | | continue; |
| | | } |
| | | try (InputStream imageInputStream = new FileInputStream(imageFile)) { |
| | | Map<String, Object> image = new HashMap<>(); |
| | | PictureRenderData pictureRenderData = Pictures.ofStream(imageInputStream, pictureType) |
| | | .sizeInCm(17, 20) |
| | | .create(); |
| | | image.put("url", pictureRenderData); |
| | | images.add(image); |
| | | } catch (Exception ignored) { |
| | | // 单个附件解析失败时跳过,避免影响整个流转卡导出 |
| | | } |
| | | } |
| | | return images; |
| | | } |
| | | |
| | | private File resolveImageFile(StorageBlobVO blobVO) { |
| | | if (blobVO == null || StringUtils.isBlank(blobVO.getUidFilename())) { |
| | | return null; |
| | | } |
| | | if (StringUtils.isBlank(blobVO.getPath())) { |
| | | return new File(fileProperties.getPath(), blobVO.getUidFilename()); |
| | | } |
| | | return new File(new File(fileProperties.getPath(), blobVO.getPath()), blobVO.getUidFilename()); |
| | | } |
| | | |
| | | private PictureType resolvePictureType(StorageBlobVO blobVO) { |
| | | if (blobVO == null) { |
| | | return null; |
| | | } |
| | | PictureType type = parsePictureTypeByFileName(blobVO.getOriginalFilename()); |
| | | if (type != null) { |
| | | return type; |
| | | } |
| | | type = parsePictureTypeByFileName(blobVO.getUidFilename()); |
| | | if (type != null) { |
| | | return type; |
| | | } |
| | | return parsePictureTypeByContentType(blobVO.getContentType()); |
| | | } |
| | | |
| | | private PictureType parsePictureTypeByFileName(String fileName) { |
| | | if (StringUtils.isBlank(fileName) || !fileName.contains(".")) { |
| | | return null; |
| | | } |
| | | try { |
| | | return PictureType.suggestFileType(fileName); |
| | | } catch (Exception ex) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private PictureType parsePictureTypeByContentType(String contentType) { |
| | | if (StringUtils.isBlank(contentType)) { |
| | | return null; |
| | | } |
| | | String normalized = contentType.trim().toLowerCase(Locale.ROOT); |
| | | switch (normalized) { |
| | | case "image/jpeg": |
| | | case "image/jpg": |
| | | case "image/pjpeg": |
| | | return PictureType.JPEG; |
| | | case "image/png": |
| | | return PictureType.PNG; |
| | | case "image/gif": |
| | | return PictureType.GIF; |
| | | case "image/bmp": |
| | | case "image/x-ms-bmp": |
| | | return PictureType.BMP; |
| | | case "image/tiff": |
| | | case "image/tif": |
| | | return PictureType.TIFF; |
| | | case "image/svg+xml": |
| | | return PictureType.SVG; |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | } |