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;
}
}
}