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.ProductionOperationTaskMapper; 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.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 implements ProductionOperationTaskService { private final SysUserMapper sysUserMapper; private final FileUtil fileUtil; private final FileProperties fileProperties; @Value("${file.temp-dir}") private String tempDir; @Override public IPage pageProductionOperationTask(Page page, ProductionOperationTaskDto dto) { Page voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); IPage result = baseMapper.pageProductionOperationTask(voPage, dto); fillUserNames(result.getRecords()); return result; } @Override public List listProductionOperationTask(ProductionOperationTaskDto dto) { List result = BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOperationTaskVo.class); fillUserNames(result); return result; } @Override public ProductionOperationTaskVo getProductionOperationTaskInfo(Long id) { ProductionOperationTask item = this.getById(id); if (item == null) { return null; } ProductionOperationTaskVo vo = BeanUtil.copyProperties(item, ProductionOperationTaskVo.class); fillUserNames(Collections.singletonList(vo)); return vo; } @Override public boolean saveProductionOperationTask(ProductionOperationTask productionOperationTask) { return this.saveOrUpdate(productionOperationTask); } @Override public boolean removeProductionOperationTask(List 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 buildQueryWrapper(ProductionOperationTaskDto dto) { ProductionOperationTask query = dto == null ? new ProductionOperationTask() : dto; return Wrappers.lambdaQuery() .eq(query.getId() != null, ProductionOperationTask::getId, query.getId()) .eq(query.getProductionOrderId() != null, ProductionOperationTask::getProductionOrderId, query.getProductionOrderId()) .eq(query.getProductionOrderRoutingOperationId() != null, ProductionOperationTask::getProductionOrderRoutingOperationId, query.getProductionOrderRoutingOperationId()) .eq(query.getStatus() != null, ProductionOperationTask::getStatus, query.getStatus()) .like(query.getWorkOrderNo() != null && !query.getWorkOrderNo().trim().isEmpty(), ProductionOperationTask::getWorkOrderNo, query.getWorkOrderNo()) .orderByDesc(ProductionOperationTask::getId); } private void fillUserNames(List voList) { if (voList == null || voList.isEmpty()) { return; } Set userIdSet = new LinkedHashSet<>(); for (ProductionOperationTaskVo vo : voList) { if (vo == null) { continue; } userIdSet.addAll(parseUserIdList(vo.getUserIds(), false)); } if (userIdSet.isEmpty()) { return; } List userList = sysUserMapper.selectUsersByIds(new ArrayList<>(userIdSet)); if (userList == null || userList.isEmpty()) { return; } Map 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 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 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 parsed = JSON.parseArray(text, Long.class); LinkedHashSet 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 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> images = buildTaskAttachmentImages(taskDto.getId()); Map 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> buildTaskAttachmentImages(Long taskId) { List> images = new ArrayList<>(); StorageAttachmentDTO storageAttachmentDTO = new StorageAttachmentDTO(); storageAttachmentDTO.setRecordType(RecordTypeEnum.PRODUCTION_OPERATION_TASK.getType()); storageAttachmentDTO.setRecordId(taskId); List 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 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; } } }