diff --git a/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java b/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java index 10d6a3f..7849876 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/controller/ProductionPlanController.java @@ -16,10 +16,13 @@ import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleQueryData; import com.xujie.sys.modules.longchuang.service.ProductionPlanService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @Slf4j @RestController @@ -49,8 +52,8 @@ public class ProductionPlanController { @PostMapping("/homeLiftOrder/reportNode") public R reportHomeLiftOrderNode(@RequestBody ProductionPlanNodeReportData data) { - productionPlanService.reportHomeLiftOrderNode(data); - return R.ok().put("msg", "报工成功"); + String logNo = productionPlanService.reportHomeLiftOrderNode(data); + return R.ok().put("msg", "报工成功").put("logNo", logNo); } @PostMapping("/homeLiftOrder/delete") @@ -79,8 +82,8 @@ public class ProductionPlanController { @PostMapping("/cableCopTask/reportNode") public R reportCableCopTaskNode(@RequestBody ProductionPlanNodeReportData data) { - productionPlanService.reportCableCopTaskNode(data); - return R.ok().put("msg", "报工成功"); + String logNo = productionPlanService.reportCableCopTaskNode(data); + return R.ok().put("msg", "报工成功").put("logNo", logNo); } @PostMapping("/cableCopTask/delete") @@ -109,8 +112,8 @@ public class ProductionPlanController { @PostMapping("/renovationOrder/reportNode") public R reportRenovationOrderNode(@RequestBody ProductionPlanNodeReportData data) { - productionPlanService.reportRenovationOrderNode(data); - return R.ok().put("msg", "报工成功"); + String logNo = productionPlanService.reportRenovationOrderNode(data); + return R.ok().put("msg", "报工成功").put("logNo", logNo); } @PostMapping("/renovationOrder/delete") @@ -139,8 +142,15 @@ public class ProductionPlanController { @PostMapping("/machiningTask/reportNode") public R reportMachiningTaskNode(@RequestBody ProductionPlanNodeReportData data) { - productionPlanService.reportMachiningTaskNode(data); - return R.ok().put("msg", "报工成功"); + String logNo = productionPlanService.reportMachiningTaskNode(data); + return R.ok().put("msg", "报工成功").put("logNo", logNo); + } + + @PostMapping("/workReport/reportNodeWithMedia") + public R reportNodeWithMedia(@ModelAttribute ProductionPlanNodeReportData data, + @RequestParam("file") MultipartFile[] files) { + String logNo = productionPlanService.reportNodeWithMedia(data, files); + return R.ok().put("msg", "报工并上传成功").put("logNo", logNo); } @PostMapping("/machiningTask/delete") diff --git a/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java b/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java index 0e77cb7..2452dbd 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/service/ProductionPlanService.java @@ -15,6 +15,7 @@ import com.xujie.sys.modules.longchuang.data.ProductionPlanReportLogQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleData; import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanUserOptionData; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -25,7 +26,7 @@ public interface ProductionPlanService { void finishHomeLiftOrder(String orderNo); - void reportHomeLiftOrderNode(ProductionPlanNodeReportData data); + String reportHomeLiftOrderNode(ProductionPlanNodeReportData data); void deleteHomeLiftOrder(String orderNo); @@ -35,7 +36,7 @@ public interface ProductionPlanService { void finishCableCopTask(String orderNo); - void reportCableCopTaskNode(ProductionPlanNodeReportData data); + String reportCableCopTaskNode(ProductionPlanNodeReportData data); void deleteCableCopTask(String orderNo); @@ -45,7 +46,7 @@ public interface ProductionPlanService { void finishRenovationOrder(String orderNo); - void reportRenovationOrderNode(ProductionPlanNodeReportData data); + String reportRenovationOrderNode(ProductionPlanNodeReportData data); void deleteRenovationOrder(String orderNo); @@ -55,7 +56,9 @@ public interface ProductionPlanService { void finishMachiningTask(String orderNo); - void reportMachiningTaskNode(ProductionPlanNodeReportData data); + String reportMachiningTaskNode(ProductionPlanNodeReportData data); + + String reportNodeWithMedia(ProductionPlanNodeReportData data, MultipartFile[] files); void deleteMachiningTask(String orderNo); diff --git a/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java b/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java index f38b9f8..672718b 100644 --- a/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java +++ b/src/main/java/com/xujie/sys/modules/longchuang/service/impl/ProductionPlanServiceImpl.java @@ -3,6 +3,7 @@ package com.xujie.sys.modules.longchuang.service.impl; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.xujie.sys.common.exception.I18nException; +import com.xujie.sys.common.exception.XJException; import com.xujie.sys.common.utils.PageUtils; import com.xujie.sys.modules.longchuang.data.ProductionPlanNodeReportData; import com.xujie.sys.modules.longchuang.data.ProductionPlanNodeAssigneeData; @@ -22,25 +23,32 @@ import com.xujie.sys.modules.longchuang.data.ProductionPlanRoleQueryData; import com.xujie.sys.modules.longchuang.data.ProductionPlanUserOptionData; import com.xujie.sys.modules.longchuang.mapper.ProductionPlanMapper; import com.xujie.sys.modules.longchuang.service.ProductionPlanService; +import com.xujie.sys.modules.oss.entity.SysOssEntity; +import com.xujie.sys.modules.oss.service.SysOssService; import com.xujie.sys.modules.sys.entity.SysUserEntity; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @@ -60,10 +68,17 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { private static final String NODE_STATUS_DONE = "已完成"; private static final String NODE_REPORT_MODE_PARALLEL = "PARALLEL"; private static final String NODE_REPORT_MODE_SEQUENTIAL = "SEQUENTIAL"; + private static final String NODE_MEDIA_REF_TYPE = "LONGCHUANG_NODE_REPORT_MEDIA"; @Autowired private ProductionPlanMapper productionPlanMapper; + @Autowired + private SysOssService sysOssService; + + @Value("${longchuang.production-report.media-root:D:\\longchuang}") + private String workReportMediaRootPath; + @Override public PageUtils queryHomeLiftOrderPage(ProductionPlanOrderQueryData data) { return queryOrderPage(data, ORDER_TYPE_HOME_LIFT); @@ -83,8 +98,8 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { @Override @Transactional(rollbackFor = Exception.class) - public void reportHomeLiftOrderNode(ProductionPlanNodeReportData data) { - reportOrderNode(data, ORDER_TYPE_HOME_LIFT); + public String reportHomeLiftOrderNode(ProductionPlanNodeReportData data) { + return reportOrderNode(data, ORDER_TYPE_HOME_LIFT); } @Override @@ -112,8 +127,8 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { @Override @Transactional(rollbackFor = Exception.class) - public void reportCableCopTaskNode(ProductionPlanNodeReportData data) { - reportOrderNode(data, ORDER_TYPE_CABLE_COP); + public String reportCableCopTaskNode(ProductionPlanNodeReportData data) { + return reportOrderNode(data, ORDER_TYPE_CABLE_COP); } @Override @@ -141,8 +156,8 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { @Override @Transactional(rollbackFor = Exception.class) - public void reportRenovationOrderNode(ProductionPlanNodeReportData data) { - reportOrderNode(data, ORDER_TYPE_RENOVATION); + public String reportRenovationOrderNode(ProductionPlanNodeReportData data) { + return reportOrderNode(data, ORDER_TYPE_RENOVATION); } @Override @@ -170,8 +185,26 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { @Override @Transactional(rollbackFor = Exception.class) - public void reportMachiningTaskNode(ProductionPlanNodeReportData data) { - reportOrderNode(data, ORDER_TYPE_MACHINING); + public String reportMachiningTaskNode(ProductionPlanNodeReportData data) { + return reportOrderNode(data, ORDER_TYPE_MACHINING); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String reportNodeWithMedia(ProductionPlanNodeReportData data, MultipartFile[] files) { + if (data == null || !StringUtils.hasText(data.getOrderType())) { + throw new I18nException("lc.production.report.param.incomplete"); + } + if (files == null || files.length <= 0) { + throw new XJException("请先拍照或录制视频"); + } + String orderType = normalizeOrderType(data.getOrderType()); + if (ORDER_TYPE_MACHINING.equals(orderType)) { + throw new XJException("机加工报工无需上传影像"); + } + String logNo = reportOrderNode(data, orderType); + saveNodeReportMedia(data, orderType, logNo, files); + return logNo; } @Override @@ -717,7 +750,7 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { return node; } - private void reportOrderNode(ProductionPlanNodeReportData data, String orderType) { + private String reportOrderNode(ProductionPlanNodeReportData data, String orderType) { if (data == null || !StringUtils.hasText(data.getOrderNo()) || !StringUtils.hasText(data.getNodeCode())) { throw new I18nException("lc.production.report.param.incomplete"); } @@ -780,9 +813,138 @@ public class ProductionPlanServiceImpl implements ProductionPlanService { data.setNodeName(node.getNodeName()); data.setReportUserId(userId); data.setReportUserName(userName); - data.setLogNo(generateLogNo(orderType)); + String logNo = generateLogNo(orderType); + data.setLogNo(logNo); data.setRemark(ORDER_TYPE_MACHINING.equals(orderType)&&data.getReportQty() != null ? "报工数量:"+data.getReportQty():""); productionPlanMapper.insertNodeReportLog(data); + return logNo; + } + + private void saveNodeReportMedia(ProductionPlanNodeReportData data, String orderType, String logNo, MultipartFile[] files) { + ProductionPlanOrderNodeData node = productionPlanMapper.queryOrderNodeByCode(data.getOrderNo(), data.getNodeCode()); + if (node == null) { + throw new I18nException("lc.production.report.node.not.exists"); + } + String rootPath = StringUtils.hasText(workReportMediaRootPath) ? workReportMediaRootPath : "D:\\longchuang"; + File rootDir = new File(rootPath); + if (!rootDir.exists() && !rootDir.mkdirs()) { + throw new XJException("报工影像根目录创建失败"); + } + String folderName = sanitizeFolderName(node.getNodeName()); + if (!StringUtils.hasText(folderName)) { + folderName = sanitizeFolderName(node.getNodeCode()); + } + if (!StringUtils.hasText(folderName)) { + folderName = "UNKNOWN_NODE"; + } + File nodeFolder = new File(rootDir, folderName); + if (!nodeFolder.exists() && !nodeFolder.mkdirs()) { + throw new XJException("节点影像目录创建失败"); + } + String dateFolderName = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); + File nodeDateFolder = new File(nodeFolder, dateFolderName); + if (!nodeDateFolder.exists() && !nodeDateFolder.mkdirs()) { + throw new XJException("节点影像日期目录创建失败"); + } + + Long userId = getCurrentUserId(); + String userName = getCurrentUserName(); + List savedFileList = new ArrayList<>(); + int uploadCount = 0; + try { + for (MultipartFile file : files) { + if (file == null || file.isEmpty()) { + continue; + } + String suffix = resolveMediaSuffix(file.getOriginalFilename(), file.getContentType()); + String newFileName = buildMediaFileName(suffix); + File targetFile = new File(nodeDateFolder, newFileName); + file.transferTo(targetFile); + savedFileList.add(targetFile); + + SysOssEntity ossEntity = new SysOssEntity(); + ossEntity.setUrl(targetFile.getAbsolutePath()); + ossEntity.setCreateDate(new Date()); + ossEntity.setFileName(StringUtils.hasText(file.getOriginalFilename()) ? file.getOriginalFilename() : newFileName); + ossEntity.setNewFileName(newFileName); + ossEntity.setFileType(suffix); + ossEntity.setCreatedBy(userName); + ossEntity.setOrderRef1(data.getOrderNo()); + ossEntity.setOrderRef2(node.getNodeCode()); + ossEntity.setOrderRef3(logNo); + ossEntity.setOrderRef4(orderType); + ossEntity.setOrderRef5(String.valueOf(userId)); + ossEntity.setOrderRef6(node.getNodeName()); + ossEntity.setOrderReftype(NODE_MEDIA_REF_TYPE); + ossEntity.setCAdditionalInfo(resolveMediaCategory(file.getContentType())); + ossEntity.setConclusion(userName); + sysOssService.save(ossEntity); + uploadCount++; + } + } catch (Exception e) { + for (File savedFile : savedFileList) { + if (savedFile != null && savedFile.exists()) { + savedFile.delete(); + } + } + throw new XJException("报工影像上传失败,请重试"); + } + if (uploadCount <= 0) { + throw new XJException("请先拍照或录制视频"); + } + } + + private String sanitizeFolderName(String folderName) { + if (!StringUtils.hasText(folderName)) { + return ""; + } + return folderName.replaceAll("[\\\\/:*?\"<>|]", "_").trim(); + } + + private String resolveMediaSuffix(String originalFileName, String contentType) { + if (StringUtils.hasText(originalFileName)) { + int dotIndex = originalFileName.lastIndexOf("."); + if (dotIndex >= 0 && dotIndex < originalFileName.length() - 1) { + return originalFileName.substring(dotIndex + 1).toLowerCase(); + } + } + if (!StringUtils.hasText(contentType)) { + return "bin"; + } + String lowerType = contentType.toLowerCase(); + if (lowerType.contains("jpeg")) { + return "jpg"; + } + if (lowerType.contains("png")) { + return "png"; + } + if (lowerType.contains("webm")) { + return "webm"; + } + if (lowerType.contains("mp4")) { + return "mp4"; + } + return "bin"; + } + + private String buildMediaFileName(String suffix) { + String timeText = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); + String randomText = UUID.randomUUID().toString().replace("-", ""); + return "LCM_" + timeText + "_" + randomText + "." + suffix; + } + + private String resolveMediaCategory(String contentType) { + if (!StringUtils.hasText(contentType)) { + return "OTHER"; + } + String lowerType = contentType.toLowerCase(); + if (lowerType.startsWith("image/")) { + return "IMAGE"; + } + if (lowerType.startsWith("video/")) { + return "VIDEO"; + } + return "OTHER"; } private void checkNodeAssigneePermission(String orderNo, String orderType, String nodeCode) {