package com.ruoyi.system.controller; import cn.dev33.satoken.annotation.SaIgnore; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.ProcessOrderPro; import com.ruoyi.system.domain.bo.plm.PlmProcessRoutePushBo; import com.ruoyi.system.domain.bo.plm.PlmProcessRoutePushNormalizer; import com.ruoyi.system.mapper.ProcessOrderProMapper; import com.ruoyi.system.service.IPlmProcessRoutePushService; import com.ruoyi.system.service.IPlmReceiveLogService; import com.ruoyi.system.service.ProcessPlanExcelPushService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; 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.RestController; import java.io.File; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; /** * PLM 系统推送完整工艺信息接收入口(POST JSON,英文键名)。 * * @see PlmProcessRoutePushBo */ @Slf4j @Validated @RequiredArgsConstructor @RestController @RequestMapping("/system/plm") public class ProcessRoutePushReceiveController extends BaseController { private static final String API_PATH = "/system/plm/routReceive"; private final IPlmProcessRoutePushService plmProcessRoutePushService; private final IPlmReceiveLogService plmReceiveLogService; private final ProcessOrderProMapper processOrderProMapper; /** 导出计划表落盘 + 按 plan-user-id 推送(与手动上传一致) */ private final ProcessPlanExcelPushService processPlanExcelPushService; /** * 接收一个生产令号下的物料、材料 BOM、工艺路线及工步数据。 *

* POST /system/plm/routReceive
* Content-Type: application/json; charset=UTF-8 */ @SaIgnore @Log(title = "PLM工艺信息推送接收", businessType = BusinessType.OTHER) @PostMapping("/routReceive") public R receive( @Validated @RequestBody PlmProcessRoutePushBo body) { String payloadJson = JSON.toJSONString(body); String orderNo = body == null ? null : body.getProductionOrder(); String clientIp = resolveClientIp(); try { PlmProcessRoutePushNormalizer.normalize(body); PlmProcessRoutePushNormalizer.PlmProcessRoutePushSummary summary = plmProcessRoutePushService.persist(body); log.info("收到 PLM 工艺推送 productionOrder={} materialCount={} processCount={} stepCount={} bomLineCount={} savedBom={} savedRoute={}", summary.getProductionOrder(), summary.getMaterialCount(), summary.getProcessCount(), summary.getStepCount(), summary.getBomLineCount(), summary.getSavedBomCount(), summary.getSavedRouteCount()); log.info("PLM 工艺推送完整 JSON:\n{}", JSON.toJSONString(body, SerializerFeature.PrettyFormat)); Map summaryMap = new LinkedHashMap<>(); summaryMap.put("productionOrder", summary.getProductionOrder()); summaryMap.put("materialCount", summary.getMaterialCount()); summaryMap.put("bomLineCount", summary.getBomLineCount()); summaryMap.put("processCount", summary.getProcessCount()); summaryMap.put("stepCount", summary.getStepCount()); summaryMap.put("savedBomCount", summary.getSavedBomCount()); summaryMap.put("savedMaterialBomCount", summary.getSavedMaterialBomCount()); summaryMap.put("savedRouteCount", summary.getSavedRouteCount()); int saved = summary.getSavedBomCount() + summary.getSavedMaterialBomCount() + summary.getSavedRouteCount(); plmReceiveLogService.record( IPlmReceiveLogService.BIZ_PROCESS_ROUTE, API_PATH, summary.getProductionOrder(), payloadJson, JSON.toJSONString(summaryMap), summary.getMaterialCount(), saved, Boolean.TRUE, IPlmReceiveLogService.STATUS_SUCCESS, null, clientIp ); // 入库成功后异步:导出工艺计划表 → 落盘 → 推送给 plan-user-id(与手动上传同一套用户) final String productionOrder = summary.getProductionOrder(); CompletableFuture.runAsync(() -> exportAndPushPlanExcel(productionOrder)); return R.ok("接收成功", summary); } catch (Exception e) { Integer lineCount = null; if (body != null && body.getMaterialList() != null) { lineCount = body.getMaterialList().size(); } plmReceiveLogService.record( IPlmReceiveLogService.BIZ_PROCESS_ROUTE, API_PATH, orderNo, payloadJson, null, lineCount, null, null, IPlmReceiveLogService.STATUS_FAIL, e.getMessage(), clientIp ); throw e; } } /** * PLM 工艺入库后:按模板导出计划表,存到 app.plan-excel.base-dir,再按 plan-user-id 推送。 */ private void exportAndPushPlanExcel(String productionOrder) { try { ProcessOrderPro order = processOrderProMapper.selectByProjectNumber(productionOrder); if (order == null || order.getId() == null) { log.warn("PLM 导出计划表跳过:项目不存在 productionOrder={}", productionOrder); return; } // 复用现有导出(PLM 来源会走 generateRoute3ExcelFromPlm) ProcessOrderProController controller = SpringUtils.getBean(ProcessOrderProController.class); String generatedPath = controller.generateRoute3Excel(order.getId(), true); File generated = new File(generatedPath); if (!generated.exists() || generated.length() <= 0) { log.warn("PLM 导出计划表失败:文件为空 path={}", generatedPath); return; } // 落到固定目录,再推送;临时生成文件可删 File saved = processPlanExcelPushService.savePlanExcel(productionOrder, generated, generated.getName()); File toSend = saved != null ? saved : generated; processPlanExcelPushService.sendToPlanUsers(toSend, toSend.getName()); if (saved != null && generated.exists() && !generated.getAbsolutePath().equals(saved.getAbsolutePath())) { if (!generated.delete()) { log.warn("删除临时计划表失败: {}", generated.getAbsolutePath()); } } } catch (Exception e) { log.error("PLM 工艺推送后导出/推送计划表失败 productionOrder={}", productionOrder, e); } } private static String resolveClientIp() { try { return ServletUtils.getClientIP(); } catch (Exception ignored) { return null; } } }