更新,工序计划

This commit is contained in:
tzy 2026-04-15 19:43:06 +08:00 committed by tzy
parent e5aab8b9a7
commit c71f6ecc98
63 changed files with 3945 additions and 1718 deletions

View File

@ -94,7 +94,7 @@
<artifactId>ruoyi-job</artifactId> <artifactId>ruoyi-job</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.ruoyi</groupId> <groupId>com.ruoyi</groupId>
<artifactId>ruoyi-oss</artifactId> <artifactId>ruoyi-oss</artifactId>
</dependency> </dependency>
@ -161,6 +161,22 @@
<version>1.39.0</version> <version>1.39.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>1.39.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>1.39.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.ortools/ortools-java --> <!-- https://mvnrepository.com/artifact/com.google.ortools/ortools-java -->

View File

@ -9,6 +9,7 @@ import com.ruoyi.common.dingding.DingCallbackCrypto;
import com.ruoyi.common.dingding.robot.RobotGroupService; import com.ruoyi.common.dingding.robot.RobotGroupService;
import com.ruoyi.common.dingding.util.DingUtil; import com.ruoyi.common.dingding.util.DingUtil;
import com.ruoyi.common.dingding.webhook.WebhookClient; import com.ruoyi.common.dingding.webhook.WebhookClient;
import com.ruoyi.common.dingding.workflow.SchemeApplicationBpmsEventHandler;
import com.ruoyi.system.controller.KingdeeWorkCenterDataController; import com.ruoyi.system.controller.KingdeeWorkCenterDataController;
import com.ruoyi.system.domain.ProcessOrderPro; import com.ruoyi.system.domain.ProcessOrderPro;
import com.ruoyi.system.mapper.ProcessOrderProMapper; import com.ruoyi.system.mapper.ProcessOrderProMapper;
@ -49,6 +50,9 @@ public class CallbackController {
@Autowired @Autowired
private WebhookClient webhookClient; // 注入 WebhookClient private WebhookClient webhookClient; // 注入 WebhookClient
@Autowired
private SchemeApplicationBpmsEventHandler schemeApplicationBpmsEventHandler;
@PostMapping("/receive") @PostMapping("/receive")
public Map<String, String> callBack(@RequestBody(required = false) Map<String, Object> json) { public Map<String, String> callBack(@RequestBody(required = false) Map<String, Object> json) {
@ -216,6 +220,8 @@ public class CallbackController {
eventType = "robot_message"; eventType = "robot_message";
} }
bizLogger.info("事件类型: {}", eventType); bizLogger.info("事件类型: {}", eventType);
//只处理技术方案表同意场景
schemeApplicationBpmsEventHandler.handleEventPayload(eventType, eventJson.toJSONString());
// 4. 根据EventType分类处理 // 4. 根据EventType分类处理
if ("check_url".equals(eventType)) { if ("check_url".equals(eventType)) {
bizLogger.info("测试回调url的正确性"); bizLogger.info("测试回调url的正确性");

View File

@ -2,12 +2,14 @@ package com.ruoyi.web.controller.dingtalk;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.dingding.relay.DingMessageRelayService;
import com.ruoyi.common.dingding.util.DingUtil; import com.ruoyi.common.dingding.util.DingUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -18,6 +20,8 @@ import com.dingtalk.api.response.OapiV2UserGetResponse;
@RequestMapping("/ding/talk") @RequestMapping("/ding/talk")
public class DingTalkController { public class DingTalkController {
private final DingMessageRelayService dingMessageRelayService;
/** /**
* 查询用户详情 * 查询用户详情
*/ */
@ -136,4 +140,28 @@ public class DingTalkController {
return R.ok("发送成功", processQueryKey); return R.ok("发送成功", processQueryKey);
} }
/**
* 示例经消息中转平台发送 Markdown 单聊需启动中转服务并配置 dingtalk.message-relay-base-url
*/
@GetMapping("/testRelayMarkdown")
public R<String> testRelayMarkdown() {
StringBuilder markdownMsg = new StringBuilder();
markdownMsg.append("### 安全库存提醒\n\n")
.append("今日暂无安全库存预警数据。\n\n")
.append("**物料编号: 123321**\n")
.append("- 物料名称: 测试测试567\n")
.append("- 预计可用量: 200\n")
.append("- 即时库存: 100\n")
.append("- 最大库存: 50\n")
.append("---\n")
.append("**总数汇总**\n")
.append("- 今日安全库存预警数据总数: **50**\n");
String resp = dingMessageRelayService.sendMarkdownToUser(
"test测试",
markdownMsg.toString(),
Arrays.asList("史国臣", "田志阳"));
return R.ok("中转返回", resp);
}
} }

View File

@ -49,8 +49,10 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
#url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true # 本机双实例5.7 常用 33068.0 常用其它端口(示例 3307内网库另见下行注释
url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true #url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#url: jdbc:mysql://localhost:3307/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root username: root
password: root password: root
# 从库数据源 # 从库数据源
@ -58,11 +60,13 @@ spring:
lazy: true lazy: true
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://localhost:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true #url: jdbc:mysql://localhost:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true #url: jdbc:mysql://localhost:3307/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
url: jdbc:mysql://192.168.5.121:3306/item_retrieval_salve?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root username: root
password: root password: root
sqlserver: sqlserver:
lazy: true
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://192.168.5.9:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true url: jdbc:sqlserver://192.168.5.9:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true

View File

@ -30,7 +30,6 @@ server:
servlet: servlet:
# 应用的访问路径 # 应用的访问路径
context-path: / context-path: /
undertow:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的 # HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1 max-http-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理 # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
@ -71,7 +70,7 @@ spring:
# 国际化资源文件路径 # 国际化资源文件路径
basename: i18n/messages basename: i18n/messages
profiles: profiles:
active: '@profiles.active@' active: ${SPRING_PROFILES_ACTIVE:dev}
# 文件上传 # 文件上传
servlet: servlet:
multipart: multipart:
@ -99,6 +98,15 @@ spring:
# 允许对象忽略json中不存在的属性 # 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false fail_on_unknown_properties: false
plm:
pdf:
base-dir: D:/plm/pdf
separator: ""
auth:
app-key: plm
app-secret: change-me
username: plm_bot
# Sa-Token配置 # Sa-Token配置
sa-token: sa-token:
# token名称 (同时也是cookie名称) # token名称 (同时也是cookie名称)
@ -296,6 +304,8 @@ management:
logfile: logfile:
external-file: ./logs/sys-console.log external-file: ./logs/sys-console.log
dingtalk: dingtalk:
# 钉钉消息中转服务地址(本服务 POST {base}/ding/send
message-relay-base-url: http://192.168.5.121:8089
app-key: dingebfrpzqko25vo8w6 app-key: dingebfrpzqko25vo8w6
app-secret: M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk app-secret: M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk
robot-code: dingebfrpzqko25vo8w6 robot-code: dingebfrpzqko25vo8w6
@ -310,3 +320,15 @@ dingtalk:
route-approval-id: 01201038164023402380 route-approval-id: 01201038164023402380
plan-user-id: 313514580637707245,192530665123549546 plan-user-id: 313514580637707245,192530665123549546
sync-k3-notify-user-ids: 313514580637707245,351225426738742993,192530665123549546 sync-k3-notify-user-ids: 313514580637707245,351225426738742993,192530665123549546
# 技术方案申请表 OA 模板 processCode钉钉后台复制留空则不处理 BPMS 事件
scheme-application-process-code: PROC-81A79B4D-F0EA-4353-9A91-68FD7E3BCEBB
# 整单审批结束且 result=agree 时拉取表单(推荐,与 event.json 顶层 result=agree 一致)
scheme-bpms-on-instance-agree: true
# 单审批任务结束且 result=agree 时拉取(会多次触发;任务节点可能出现 audit 等非 agree
scheme-bpms-on-task-agree: false
# 日志中只输出这些表单控件名称(逗号分隔);留空则输出全部非空字段
scheme-application-log-field-names: 方案提交日期,是否收到方案,任务详情
pdf:
# PDF字体支持 classpath: 前缀)
font-path: classpath:font/arial unicode ms.ttf

View File

@ -78,6 +78,31 @@ public class DingTalkProperties {
*/ */
private String syncK3NotifyUserIds; private String syncK3NotifyUserIds;
/**
* 消息中转服务根地址 http://host:8089不要带尾斜杠也可为空则不调用中转发送
*/
private String messageRelayBaseUrl;
/**
* 技术方案申请表 OA 模板 processCode PROC-xxx为空则不处理 BPMS 审批事件
*/
private String schemeApplicationProcessCode = "";
/**
* 是否在 bpms_instance_change 整单结束且 result=agree 时拉取审批详情推荐
*/
private boolean schemeBpmsOnInstanceAgree = true;
/**
* 是否在 bpms_task_change 单任务结束且 result=agree 时拉取审批详情会多次触发默认关闭
*/
private boolean schemeBpmsOnTaskAgree = false;
/**
* 拉取详情后日志中输出的控件名称逗号分隔为空则输出全部非空表单字段
*/
private String schemeApplicationLogFieldNames = "";
/** /**
* 回调路由 Key * 回调路由 Key
*/ */

View File

@ -11,15 +11,19 @@ import org.springframework.stereotype.Component;
import shade.com.alibaba.fastjson2.JSONObject; import shade.com.alibaba.fastjson2.JSONObject;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
/** /**
* 钉钉 Stream 模式事件推送服务 * 钉钉 Stream 模式事件推送服务
*/ */
@Component @Component
@RequiredArgsConstructor
public class DingTalkStreamService { public class DingTalkStreamService {
private static final Logger log = LoggerFactory.getLogger(DingTalkStreamService.class); private static final Logger log = LoggerFactory.getLogger(DingTalkStreamService.class);
private final com.ruoyi.common.dingding.workflow.SchemeApplicationBpmsEventHandler schemeApplicationBpmsEventHandler;
// 钉钉应用的 AppKey // 钉钉应用的 AppKey
private static final String CLIENT_ID = "dingebfrpzqko25vo8w6"; private static final String CLIENT_ID = "dingebfrpzqko25vo8w6";
// 钉钉应用的 AppSecret // 钉钉应用的 AppSecret
@ -57,6 +61,7 @@ public class DingTalkStreamService {
log.info("BornTime: {}", bornTime); log.info("BornTime: {}", bornTime);
log.info("BizData: {}", bizData != null ? bizData.toString() : "null"); log.info("BizData: {}", bizData != null ? bizData.toString() : "null");
schemeApplicationBpmsEventHandler.handleEventPayload(eventType, bizData != null ? bizData.toJSONString() : "{}");
//处理事件 //处理事件
process(bizData); process(bizData);
//消费成功 //消费成功

View File

@ -934,7 +934,14 @@ public class DingUtil {
} }
} }
/**
* 使用 Token 初始化账号 ClientOA 审批 / workflow
*/
public static com.aliyun.dingtalkworkflow_1_0.Client createWorkflowClient() throws Exception {
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
return new com.aliyun.dingtalkworkflow_1_0.Client(config);
}
} }

View File

@ -4,7 +4,7 @@ spring:
application: application:
name: ruoyi-monitor-admin name: ruoyi-monitor-admin
profiles: profiles:
active: @profiles.active@ active: ${SPRING_PROFILES_ACTIVE:dev}
logging: logging:
config: classpath:logback-plus.xml config: classpath:logback-plus.xml

View File

@ -9,12 +9,14 @@ spring.boot.admin.client:
username: ruoyi username: ruoyi
password: 123456 password: 123456
--- # 数据库配置 --- # 数据库配置(与 ruoyi-admin dev 同一实例;本机 5.7 多为 3306MySQL8 多为 3307
spring: spring:
datasource: datasource:
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai #url: jdbc:mysql://localhost:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
url: jdbc:mysql://localhost:3307/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#url: jdbc:mysql://192.168.5.121:3306/item_retrieval-evo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root username: root
password: root password: root
hikari: hikari:

View File

@ -7,7 +7,7 @@ spring:
application: application:
name: ruoyi-xxl-job-admin name: ruoyi-xxl-job-admin
profiles: profiles:
active: @profiles.active@ active: ${SPRING_PROFILES_ACTIVE:dev}
mvc: mvc:
servlet: servlet:
load-on-startup: 0 load-on-startup: 0

View File

@ -84,6 +84,8 @@ public class BomDetailsController extends BaseController {
private final IProcessOrderProService iProcessOrderProService; private final IProcessOrderProService iProcessOrderProService;
private final IProductionOrderService iProductionOrderService;
private final ProcessOrderProMapper processOrderProMapper; private final ProcessOrderProMapper processOrderProMapper;
private final BomDetailsMapper bomDetailsMapper; private final BomDetailsMapper bomDetailsMapper;
@ -390,12 +392,17 @@ public class BomDetailsController extends BaseController {
if (bomDetails != null && !bomDetails.isEmpty()) { if (bomDetails != null && !bomDetails.isEmpty()) {
//TODO 处理父级物料 //TODO 处理父级物料
ProcessOrderPro bo = iProcessOrderProService.selectByProjectNumber(totalWeight); ProcessOrderPro bo = iProcessOrderProService.selectByProjectNumber(totalWeight);
// 第一步处理所有物料的保存 // 第一步处理所有物料的保存
for (BomDetails material : bomDetails) { for (BomDetails material : bomDetails) {
// 获取工艺表中的非委外工时 // 获取工艺表中的非委外工时
Double fbWorkTime = iProcessRouteService.getFbWorkTime(material); Double fbWorkTime = iProcessRouteService.getFbWorkTime(material);
if (material.getPartNumber() != null && material.getName() != null && material.getUnitWeight().equals("")) { if (material.getPartNumber() != null && material.getName() != null && material.getUnitWeight().equals("")) {
if (material.getPartNumber().startsWith("015") || material.getPartNumber().startsWith("009")) {
return R.fail("015和009为管控物料请先申请");
}
if (material.getRemarks() != null && material.getRemarks().contains("甲供")) {
continue;
}
String state = determineState(material); String state = determineState(material);
//判断bom类型是生产还是电气 //判断bom类型是生产还是电气
if (material.getBomType().equals("0")) { if (material.getBomType().equals("0")) {
@ -437,13 +444,13 @@ public class BomDetailsController extends BaseController {
if (needUpload) { if (needUpload) {
try { try {
// 物料清单保存方法,判断是电气还是生产 // 物料清单保存方法,判断是电气还是生产
BOMUploadResult bomUploadResult = FBloadBillOfMaterialsPreservation(bomDetails, bo,groupName); BOMUploadResult bomUploadResult = FBloadBillOfMaterialsPreservation(bomDetails, bo, groupName);
if (bomUploadResult.isSuccess()) { if (bomUploadResult.isSuccess()) {
KindegeeLogDTO logDTO = new KindegeeLogDTO(); KindegeeLogDTO logDTO = new KindegeeLogDTO();
logDTO.setProjectCode(bo.getProductionOrderNo()); logDTO.setProjectCode(bo.getProductionOrderNo());
logDTO.setMaterialCode(fnumber); // 使用物料编码而不是单个物料的编码 logDTO.setMaterialCode(fnumber); // 使用物料编码而不是单个物料的编码
logDTO.setCode("200"); logDTO.setCode("200");
logDTO.setReason("成功上传"+"版本号:"+bomUploadResult.getNumber()); logDTO.setReason("成功上传" + "版本号:" + bomUploadResult.getNumber());
logDTOS.add(logDTO); logDTOS.add(logDTO);
} else { } else {
KindegeeLogDTO logDTO = new KindegeeLogDTO(); KindegeeLogDTO logDTO = new KindegeeLogDTO();
@ -498,8 +505,8 @@ public class BomDetailsController extends BaseController {
} }
//查询工艺路线数量 //查询工艺路线数量
LambdaQueryWrapper<ProcessRoute> wqroute = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ProcessRoute> wqroute = new LambdaQueryWrapper<>();
wqroute.eq(ProcessRoute::getRouteDescription,totalWeight) wqroute.eq(ProcessRoute::getRouteDescription, totalWeight)
.eq(ProcessRoute::getProcessNo,"10"); .eq(ProcessRoute::getProcessNo, "10");
Long routeCount = routeMapper.selectCount(wqroute); Long routeCount = routeMapper.selectCount(wqroute);
// 计算统计数据 // 计算统计数据
int bomCount = fNumberCount + partdiagramCodeCount; int bomCount = fNumberCount + partdiagramCodeCount;
@ -771,7 +778,7 @@ public class BomDetailsController extends BaseController {
} }
// FBOM物料清单保存 // FBOM物料清单保存
public BOMUploadResult FBloadBillOfMaterialsPreservation(List<BomDetails> bomlist, ProcessOrderPro bo,String groupName) { public BOMUploadResult FBloadBillOfMaterialsPreservation(List<BomDetails> bomlist, ProcessOrderPro bo, String groupName) {
BomDetails bomDetails1 = bomlist.get(0); BomDetails bomDetails1 = bomlist.get(0);
int verification = isMaterialVerification(bomDetails1.getFNumber(), bomDetails1.getFName()); int verification = isMaterialVerification(bomDetails1.getFNumber(), bomDetails1.getFName());
@ -928,7 +935,20 @@ public class BomDetailsController extends BaseController {
JsonObject FSVRIAssistant = new JsonObject(); JsonObject FSVRIAssistant = new JsonObject();
FSVRIAssistant.addProperty("FNumber", materialProperties.getMaterialAttributeId()); FSVRIAssistant.addProperty("FNumber", materialProperties.getMaterialAttributeId());
model.add("F_SVRI_Assistant", FSVRIAssistant); model.add("F_SVRI_Assistant", FSVRIAssistant);
// 单重 Excel 批量匹配逻辑一致production_order.drawing_no process_route.material_code图号用物料编码 partNumber库中未查到则为 null
Double dz = iProductionOrderService.resolveSingleWeightByMaterialCode(bomDetails.getPartNumber());
if (dz == null) {
if (bomDetails.getDanZhong() != null && bomDetails.getSingleWeghit() != null) {
dz = bomDetails.getSingleWeghit();
} else if (bomDetails.getDanZhong() != null) {
dz = bomDetails.getDanZhong();
} else if (bomDetails.getSingleWeghit() != null) {
dz = bomDetails.getSingleWeghit();
}
}
if (dz != null) {
model.addProperty("F_HBYT_DZ", dz);
}
// 创建FMaterialGroup对象并加入Model // 创建FMaterialGroup对象并加入Model
JsonObject fMaterialGroup = new JsonObject(); JsonObject fMaterialGroup = new JsonObject();
fMaterialGroup.addProperty("FNumber", "XM-100"); fMaterialGroup.addProperty("FNumber", "XM-100");
@ -946,6 +966,9 @@ public class BomDetailsController extends BaseController {
// 创建FCategoryID对象并加入SubHeadEntity // 创建FCategoryID对象并加入SubHeadEntity
JsonObject fCategoryID = new JsonObject(); JsonObject fCategoryID = new JsonObject();
if(bomDetails.getFNumber().contains("-MP")){
fCategoryID.addProperty("FNumber", "007");
}
fCategoryID.addProperty("FNumber", "CHLB05_SYS"); fCategoryID.addProperty("FNumber", "CHLB05_SYS");
subHeadEntity.add("FCategoryID", fCategoryID); subHeadEntity.add("FCategoryID", fCategoryID);
@ -1094,7 +1117,7 @@ public class BomDetailsController extends BaseController {
/* /*
* FB父级物料保存接口 * FB父级物料保存接口
*/ */
public static int loadFBMaterialPreservation(BomDetails bomDetail) { public int loadFBMaterialPreservation(BomDetails bomDetail) {
K3CloudApi client = new K3CloudApi(); K3CloudApi client = new K3CloudApi();
// 创建一个空的JsonObject // 创建一个空的JsonObject
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
@ -1117,7 +1140,20 @@ public class BomDetailsController extends BaseController {
model.add("FMaterialGroup", fMaterialGroup); model.add("FMaterialGroup", fMaterialGroup);
model.addProperty("FIsHandleReserve", true); model.addProperty("FIsHandleReserve", true);
// 单重 Excel 批量匹配逻辑一致production_order.drawing_no process_route.material_code图号用物料编码 partNumber库中未查到则为 null
Double dz = iProductionOrderService.resolveSingleWeightByMaterialCode(bomDetail.getPartNumber());
if (dz == null) {
if (bomDetail.getDanZhong() != null && bomDetail.getSingleWeghit() != null) {
dz = bomDetail.getSingleWeghit();
} else if (bomDetail.getDanZhong() != null) {
dz = bomDetail.getDanZhong();
} else if (bomDetail.getSingleWeghit() != null) {
dz = bomDetail.getSingleWeghit();
}
}
if (dz != null) {
model.addProperty("F_HBYT_DZ", dz);
}
// 创建SubHeadEntity对象并加入Model // 创建SubHeadEntity对象并加入Model
JsonObject subHeadEntity = new JsonObject(); JsonObject subHeadEntity = new JsonObject();
model.add("SubHeadEntity", subHeadEntity); model.add("SubHeadEntity", subHeadEntity);
@ -1127,6 +1163,9 @@ public class BomDetailsController extends BaseController {
// 创建FCategoryID对象并加入SubHeadEntity // 创建FCategoryID对象并加入SubHeadEntity
JsonObject fCategoryID = new JsonObject(); JsonObject fCategoryID = new JsonObject();
if (bomDetail.getFNumber().contains("-MP")){
fCategoryID.addProperty("FNumber", "007");//
}
fCategoryID.addProperty("FNumber", "CHLB05_SYS");// 产成品 fCategoryID.addProperty("FNumber", "CHLB05_SYS");// 产成品
subHeadEntity.add("FCategoryID", fCategoryID); subHeadEntity.add("FCategoryID", fCategoryID);
@ -1512,11 +1551,19 @@ public class BomDetailsController extends BaseController {
json.add("Model", model); json.add("Model", model);
model.addProperty("FMATERIALID", 0); model.addProperty("FMATERIALID", 0);
//单重 // 单重 Excel 批量匹配逻辑一致production_order.drawing_no process_route.material_code图号用物料编码 partNumber库中未查到则为 null
if (!(bomDetails1.getDanZhong() == null) && !(bomDetails1.getSingleWeghit() == null)) { Double dz = iProductionOrderService.resolveSingleWeightByMaterialCode(bomDetails1.getPartNumber());
model.addProperty("F_HBYT_DZ", bomDetails1.getSingleWeghit()); if (dz == null) {
} else { if (bomDetails1.getDanZhong() != null && bomDetails1.getSingleWeghit() != null) {
model.addProperty("F_HBYT_DZ", bomDetails1.getDanZhong()); dz = bomDetails1.getSingleWeghit();
} else if (bomDetails1.getDanZhong() != null) {
dz = bomDetails1.getDanZhong();
} else if (bomDetails1.getSingleWeghit() != null) {
dz = bomDetails1.getSingleWeghit();
}
}
if (dz != null) {
model.addProperty("F_HBYT_DZ", dz);
} }
model.addProperty("FSpecification", bomDetails1.getRemarks()); model.addProperty("FSpecification", bomDetails1.getRemarks());
model.addProperty("FNumber", bomDetails1.getPartNumber()); model.addProperty("FNumber", bomDetails1.getPartNumber());
@ -1544,9 +1591,9 @@ public class BomDetailsController extends BaseController {
subHeadEntity.addProperty("FErpClsID", states); subHeadEntity.addProperty("FErpClsID", states);
subHeadEntity.addProperty("FFeatureItem", "1"); subHeadEntity.addProperty("FFeatureItem", "1");
//存货类别
JsonObject fCategoryID = new JsonObject(); JsonObject fCategoryID = new JsonObject();
if (states.equals("2") || states.equals("3")) { if (states.equals("2") || states.equals("3")||bomDetails1.getPartNumber().contains("-MP")) {
fCategoryID.addProperty("FNumber", "007"); fCategoryID.addProperty("FNumber", "007");
} else { } else {
fCategoryID.addProperty("FNumber", "006"); fCategoryID.addProperty("FNumber", "006");
@ -1834,7 +1881,7 @@ public class BomDetailsController extends BaseController {
// 创建SubHeadEntity对象并加入Model // 创建SubHeadEntity对象并加入Model
JsonObject subHeadEntity = new JsonObject(); JsonObject subHeadEntity = new JsonObject();
model.add("SubHeadEntity", subHeadEntity); model.add("SubHeadEntity", subHeadEntity);
if (bomDetails1.getWareHouse().contains("伊特")){ if (bomDetails1.getWareHouse().contains("伊特")) {
subHeadEntity.addProperty("FErpClsID", "2"); subHeadEntity.addProperty("FErpClsID", "2");
} }
subHeadEntity.addProperty("FErpClsID", "1"); subHeadEntity.addProperty("FErpClsID", "1");
@ -2260,7 +2307,7 @@ public class BomDetailsController extends BaseController {
List<BomDetails> list = new ArrayList<>(); List<BomDetails> list = new ArrayList<>();
if (rows != null) { if (rows != null) {
for (ElectricalMaterialBomVO bomVO : rows) { for (ElectricalMaterialBomVO bomVO : rows) {
if(bomVO.getRemark().equals("研发已领用")|| bomVO.getRemark().contains("领用")){ if (bomVO.getRemark() != null && bomVO.getRemark().contains("领用")) {
continue; continue;
} }
BomDetails bomDetails = new BomDetails(); BomDetails bomDetails = new BomDetails();
@ -2271,6 +2318,9 @@ public class BomDetailsController extends BaseController {
bomDetails.setName(bomVO.getDrawingName()); bomDetails.setName(bomVO.getDrawingName());
bomDetails.setMaterial(bomVO.getModel()); bomDetails.setMaterial(bomVO.getModel());
bomDetails.setWareHouse(bomVO.getBrand()); bomDetails.setWareHouse(bomVO.getBrand());
if(bomVO.getBrand().contains("伊特")){
bomDetails.setStats("自制");
}
bomDetails.setStats("外购"); bomDetails.setStats("外购");
bomDetails.setQuantity(bomVO.getQuantity() == null ? null : bomVO.getQuantity().toString()); bomDetails.setQuantity(bomVO.getQuantity() == null ? null : bomVO.getQuantity().toString());
bomDetails.setRemarks(bomVO.getUnit()); bomDetails.setRemarks(bomVO.getUnit());
@ -2320,6 +2370,7 @@ public class BomDetailsController extends BaseController {
} }
return materialsToAdd; return materialsToAdd;
} }
@Log(title = "查看BOM上传日志") @Log(title = "查看BOM上传日志")
@SaCheckPermission("system:route:viewGetBomUploadStatus") @SaCheckPermission("system:route:viewGetBomUploadStatus")
@PostMapping("/viewGetBomUploadStatus") @PostMapping("/viewGetBomUploadStatus")

View File

@ -13,9 +13,13 @@ import java.util.stream.Collectors;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kingdee.bos.webapi.entity.SuccessEntity; import com.kingdee.bos.webapi.entity.SuccessEntity;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.excel.ExcelResult; import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.utils.JdUtils; import com.ruoyi.common.utils.JdUtils;
import com.ruoyi.system.domain.EleMaterials; import com.ruoyi.system.domain.EleMaterials;
@ -453,43 +457,103 @@ public class EleMaterialsController extends BaseController {
} }
@Log(title = "更新物料单重", businessType = BusinessType.IMPORT) @Log(title = "更新物料单重", businessType = BusinessType.UPDATE)
@SaCheckPermission("system:route:updateDanzhong") @SaCheckPermission("system:route:updateDanzhong")
@PostMapping(value = "/updateDanzhong", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @PostMapping("/updateDanzhong")
public R<Void> updateDanzhong(@RequestParam("file") MultipartFile file) throws Exception { public R<Void> updateDanzhong() {
String originalFilename = file.getOriginalFilename(); final int pageSize = 500;
log.info("读取文件名: " + originalFilename); int pageNum = 1;
ExcelResult<JdDanZhong> result = ExcelUtil.importExcelSheet1(file.getInputStream(), JdDanZhong.class, true); long totalRows = 0;
List<JdDanZhong> list = result.getList(); long skippedDisabled = 0;
long skippedNoWeight = 0;
long submitted = 0;
int failedTasks = 0;
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(10); ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<Void>> futures = new ArrayList<>();
list.forEach(vmi -> executor.submit(() -> {
List<JdEntryVmi> entryID = JdUtil.getEntryID2(vmi.getMaterialCode());
entryID.forEach(jdEntryVmi -> {
int fmaterialid = jdEntryVmi.getFMATERIALID();
try {
log.info("=====================>开始更新 " + vmi.getMaterialCode() + " 的单重");
JdUtil.updateDanzhong(fmaterialid,vmi.getDanzhong() );
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}));
// 关闭线程池并等待所有任务完成
executor.shutdown();
try { try {
if (!executor.awaitTermination(60, TimeUnit.MINUTES)) { while (true) {
executor.shutdownNow(); // 超时后强制关闭 LambdaQueryWrapper<ImMaterial> qw = Wrappers.lambdaQuery();
qw.isNotNull(ImMaterial::getMaterialCode);
qw.ne(ImMaterial::getMaterialCode, "");
qw.and(w -> w.isNull(ImMaterial::getForbidStatus).or().ne(ImMaterial::getForbidStatus, "B"));
qw.orderByAsc(ImMaterial::getId);
Page<ImMaterial> page = materialMapper.selectPage(new Page<>(pageNum, pageSize), qw);
List<ImMaterial> rows = page.getRecords();
if (rows.isEmpty()) {
break;
}
for (ImMaterial m : rows) {
totalRows++;
if (m == null || StringUtils.isBlank(m.getMaterialCode())) {
continue;
}
if (StringUtils.isNotBlank(m.getForbidStatus()) && "B".equalsIgnoreCase(m.getForbidStatus().trim())) {
skippedDisabled++;
continue;
}
if (m.getSingleWeight() == null) {
skippedNoWeight++;
continue;
}
final String materialCode = m.getMaterialCode().trim();
final String danzhongStr = m.getSingleWeight().stripTrailingZeros().toPlainString();
futures.add(executor.submit(() -> {
List<JdEntryVmi> entryID = JdUtil.getEntryID2(materialCode);
if (entryID == null || entryID.isEmpty()) {
log.warn("金蝶未查到物料编码: {}", materialCode);
return null;
}
for (JdEntryVmi jdEntryVmi : entryID) {
int fmaterialid = jdEntryVmi.getFMATERIALID();
try {
log.info("=====================>开始更新 {} 的单重(来自 im_material)", materialCode);
JdUtil.updateDanzhong(fmaterialid, danzhongStr);
} catch (Exception e) {
log.error("更新单重失败 materialCode={}, fmaterialid={}", materialCode, fmaterialid, e);
throw new RuntimeException(e);
}
}
return null;
}));
submitted++;
}
if (rows.size() < pageSize) {
break;
}
pageNum++;
}
for (Future<Void> future : futures) {
try {
future.get();
} catch (ExecutionException e) {
failedTasks++;
log.error("更新物料单重任务异常: {}", e.getCause() != null ? e.getCause().getMessage() : e.getMessage(), e);
}
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
executor.shutdownNow(); Thread.currentThread().interrupt();
Thread.currentThread().interrupt(); // 保留中断状态 log.error("更新物料单重被中断", e);
return R.fail("任务被中断");
} finally {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.MINUTES)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
} }
log.info("updateDanzhong 完成: 扫描行={}, 提交金蝶任务={}, 跳过禁用={}, 跳过无单重={}, 失败任务={}", totalRows, submitted, skippedDisabled, skippedNoWeight, failedTasks);
if (failedTasks > 0) {
return R.fail(String.format("部分失败: 失败任务数=%d详见日志", failedTasks));
}
return R.ok("更新成功"); return R.ok("更新成功");
} }
@ -542,8 +606,6 @@ public class EleMaterialsController extends BaseController {
log.info("读取文件名: " + originalFilename); log.info("读取文件名: " + originalFilename);
ExcelResult<JdDanZhong> result = ExcelUtil.importExcelSheet1(file.getInputStream(), JdDanZhong.class, true); ExcelResult<JdDanZhong> result = ExcelUtil.importExcelSheet1(file.getInputStream(), JdDanZhong.class, true);
List<JdDanZhong> list = result.getList(); List<JdDanZhong> list = result.getList();
return R.ok("更新成功"); return R.ok("更新成功");
} }

View File

@ -21,17 +21,26 @@ import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.excel.ExcelResult; import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.utils.JdUtils; import com.ruoyi.common.utils.JdUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.ImMaterial; import com.ruoyi.system.domain.ImMaterial;
import com.ruoyi.system.domain.bo.ImMaterialBo; import com.ruoyi.system.domain.bo.ImMaterialBo;
import com.ruoyi.system.domain.dto.buildcapacity.BuildCapacityDTO;
import com.ruoyi.system.domain.dto.buildcapacity.CalcInfo;
import com.ruoyi.system.domain.dto.buildcapacity.MaterialItem;
import com.ruoyi.system.domain.dto.buildcapacity.StockInfo;
import com.ruoyi.system.domain.dto.productionorder.ProductionOrderMaterialsDTO;
import com.ruoyi.system.domain.vo.BuildCapacityMaterialExportVo;
import com.ruoyi.system.domain.vo.ImMaterialVo; import com.ruoyi.system.domain.vo.ImMaterialVo;
import com.ruoyi.system.mapper.ImMaterialMapper; import com.ruoyi.system.mapper.ImMaterialMapper;
import com.ruoyi.system.service.IImMaterialService; import com.ruoyi.system.service.IImMaterialService;
import com.ruoyi.system.service.IPartCostService; import com.ruoyi.system.service.IPartCostService;
import com.xxl.job.core.handler.annotation.XxlJob; import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -43,8 +52,15 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static com.ruoyi.common.core.mapper.BaseMapperPlus.log; import static com.ruoyi.common.core.mapper.BaseMapperPlus.log;
@ -72,6 +88,12 @@ public class ImMaterialController extends BaseController {
private final ImMaterialMapper imMaterialMapper; private final ImMaterialMapper imMaterialMapper;
private final IPartCostService iPartCostService; private final IPartCostService iPartCostService;
@Value("${plm.pdf.base-dir:D:/plm/pdf}")
private String plmPdfBaseDir;
@Value("${plm.pdf.separator:}")
private String plmPdfSeparator;
/** /**
* 金蝶物料保存 自动推送到此接口 * 金蝶物料保存 自动推送到此接口
* SaIgnore 注解用于开放此接口无需 token 验证 * SaIgnore 注解用于开放此接口无需 token 验证
@ -114,6 +136,426 @@ public class ImMaterialController extends BaseController {
} }
} }
@SaIgnore
@PostMapping(value = "/plm/pdf", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, String>> receivePlmPdfDrawing(
@RequestParam("productionOrderNo") String productionOrderNo,
@RequestParam("materialCode") String materialCode,
@RequestParam("materialName") String materialName,
@RequestParam(value = "pdfFileName", required = false) String pdfFileName,
@RequestPart("file") MultipartFile file
) {
if (StringUtils.isBlank(productionOrderNo)) {
return R.fail("生产令号不能为空");
}
if (StringUtils.isBlank(materialCode)) {
return R.fail("物料编码不能为空");
}
if (StringUtils.isBlank(materialName)) {
return R.fail("物料名称不能为空");
}
if (file == null || file.isEmpty()) {
return R.fail("PDF文件不能为空");
}
String originalName = file.getOriginalFilename();
String finalPdfFileName = StringUtils.isNotBlank(pdfFileName) ? pdfFileName : originalName;
if (!isPdfFileName(finalPdfFileName)) {
return R.fail("仅支持上传PDF文件");
}
String safeProductionOrderNo = sanitizePathPart(productionOrderNo);
String safeMaterialCode = sanitizeFileNamePart(materialCode);
String safeMaterialName = sanitizeFileNamePart(materialName);
String separator = StringUtils.trimToEmpty(plmPdfSeparator);
String targetBaseName = safeMaterialCode + separator + safeMaterialName;
Path dir = Paths.get(plmPdfBaseDir, safeProductionOrderNo);
try {
Files.createDirectories(dir);
Path targetFile = resolveAvailablePdfPath(dir, targetBaseName);
try (InputStream in = file.getInputStream()) {
Files.copy(in, targetFile, StandardCopyOption.REPLACE_EXISTING);
}
Map<String, String> resp = new HashMap<>(8);
resp.put("productionOrderNo", productionOrderNo);
resp.put("materialCode", materialCode);
resp.put("materialName", materialName);
resp.put("pdfFileName", finalPdfFileName);
resp.put("savedFileName", targetFile.getFileName().toString());
resp.put("savedDir", dir.toString());
resp.put("savedPath", targetFile.toString());
return R.ok(resp);
} catch (Exception e) {
log.error("接收PLM PDF图纸失败: productionOrderNo={}, materialCode={}", productionOrderNo, materialCode, e);
return R.fail("接收PLM PDF图纸失败: " + e.getMessage());
}
}
@SaIgnore
@PostMapping(value = "/plm/pdf/batch", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, Object>> receivePlmPdfDrawingsBatch(
@RequestParam("productionOrderNo") String productionOrderNo,
@RequestPart("items") String itemsJson,
@RequestPart("files") MultipartFile[] files
) {
if (StringUtils.isBlank(productionOrderNo)) {
return R.fail("生产令号不能为空");
}
if (StringUtils.isBlank(itemsJson)) {
return R.fail("items不能为空");
}
if (files == null || files.length == 0) {
return R.fail("files不能为空");
}
List<PlmPdfBatchItem> items;
try {
items = new ObjectMapper().readValue(itemsJson, new TypeReference<List<PlmPdfBatchItem>>() {});
} catch (JsonProcessingException e) {
return R.fail("items不是合法JSON: " + e.getOriginalMessage());
}
if (items == null || items.isEmpty()) {
return R.fail("items不能为空");
}
Map<String, PlmPdfBatchItem> metaByFileName = new HashMap<>(items.size() * 2);
for (PlmPdfBatchItem item : items) {
if (item == null) {
continue;
}
String pdfName = normalizeOriginalName(item.getPdfFileName());
if (StringUtils.isBlank(pdfName)) {
return R.fail("items中pdfFileName不能为空");
}
if (StringUtils.isBlank(item.getMaterialCode())) {
return R.fail("items中materialCode不能为空: " + pdfName);
}
if (StringUtils.isBlank(item.getMaterialName())) {
return R.fail("items中materialName不能为空: " + pdfName);
}
if (!isPdfFileName(pdfName)) {
return R.fail("items中仅支持PDF文件名: " + pdfName);
}
if (metaByFileName.containsKey(pdfName)) {
return R.fail("items中存在重复pdfFileName: " + pdfName);
}
metaByFileName.put(pdfName, item);
}
Set<String> fileNames = new HashSet<>(files.length * 2);
List<String> duplicateFiles = new ArrayList<>();
List<String> filesWithoutMeta = new ArrayList<>();
for (MultipartFile f : files) {
if (f == null || f.isEmpty()) {
continue;
}
String fn = normalizeOriginalName(f.getOriginalFilename());
if (StringUtils.isBlank(fn)) {
return R.fail("files中存在无文件名的上传项");
}
if (!isPdfFileName(fn)) {
return R.fail("仅支持上传PDF文件: " + fn);
}
if (!fileNames.add(fn)) {
duplicateFiles.add(fn);
}
if (!metaByFileName.containsKey(fn)) {
filesWithoutMeta.add(fn);
}
}
if (!duplicateFiles.isEmpty()) {
return R.fail("files中存在重复文件名: " + String.join(", ", duplicateFiles));
}
if (!filesWithoutMeta.isEmpty()) {
return R.fail("以下文件未在items中声明: " + String.join(", ", filesWithoutMeta));
}
List<String> missingFiles = new ArrayList<>();
for (String metaFileName : metaByFileName.keySet()) {
if (!fileNames.contains(metaFileName)) {
missingFiles.add(metaFileName);
}
}
if (!missingFiles.isEmpty()) {
return R.fail("以下items声明的文件未上传: " + String.join(", ", missingFiles));
}
String safeProductionOrderNo = sanitizePathPart(productionOrderNo);
String separator = StringUtils.trimToEmpty(plmPdfSeparator);
Path dir = Paths.get(plmPdfBaseDir, safeProductionOrderNo);
List<Map<String, String>> saved = new ArrayList<>(files.length);
try {
Files.createDirectories(dir);
for (MultipartFile f : files) {
if (f == null || f.isEmpty()) {
continue;
}
String fn = normalizeOriginalName(f.getOriginalFilename());
PlmPdfBatchItem item = metaByFileName.get(fn);
String safeMaterialCode = sanitizeFileNamePart(item.getMaterialCode());
String safeMaterialName = sanitizeFileNamePart(item.getMaterialName());
String targetBaseName = safeMaterialCode + separator + safeMaterialName;
Path targetFile = resolveAvailablePdfPath(dir, targetBaseName);
try (InputStream in = f.getInputStream()) {
Files.copy(in, targetFile, StandardCopyOption.REPLACE_EXISTING);
}
Map<String, String> row = new HashMap<>(6);
row.put("pdfFileName", fn);
row.put("materialCode", item.getMaterialCode());
row.put("materialName", item.getMaterialName());
row.put("savedFileName", targetFile.getFileName().toString());
row.put("savedDir", dir.toString());
row.put("savedPath", targetFile.toString());
saved.add(row);
}
Map<String, Object> resp = new HashMap<>(6);
resp.put("productionOrderNo", productionOrderNo);
resp.put("savedDir", dir.toString());
resp.put("count", saved.size());
resp.put("items", saved);
return R.ok(resp);
} catch (Exception e) {
log.error("批量接收PLM PDF图纸失败: productionOrderNo={}", productionOrderNo, e);
return R.fail("批量接收PLM PDF图纸失败: " + e.getMessage());
}
}
@SaIgnore
@PostMapping(value = "/plm/pdf/zip", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Map<String, Object>> receivePlmPdfDrawingsZip(
@RequestPart("manifest") String manifestJson,
@RequestPart("zip") MultipartFile zipFile
) {
if (StringUtils.isBlank(manifestJson)) {
return R.fail("manifest不能为空");
}
if (zipFile == null || zipFile.isEmpty()) {
return R.fail("zip不能为空");
}
PlmPdfManifest manifest;
try {
manifest = new ObjectMapper().readValue(manifestJson, PlmPdfManifest.class);
} catch (JsonProcessingException e) {
return R.fail("manifest不是合法JSON: " + e.getOriginalMessage());
}
if (manifest == null || StringUtils.isBlank(manifest.getProductionOrderNo())) {
return R.fail("manifest.productionOrderNo不能为空");
}
if (manifest.getItems() == null || manifest.getItems().isEmpty()) {
return R.fail("manifest.items不能为空");
}
String productionOrderNo = manifest.getProductionOrderNo();
String safeProductionOrderNo = sanitizePathPart(productionOrderNo);
String separator = StringUtils.trimToEmpty(plmPdfSeparator);
Path dir = Paths.get(plmPdfBaseDir, safeProductionOrderNo);
Map<String, PlmPdfManifestItem> itemByPdfPath = new LinkedHashMap<>(manifest.getItems().size() * 2);
for (PlmPdfManifestItem item : manifest.getItems()) {
if (item == null) {
continue;
}
String pdfPath = normalizeZipEntryName(item.getPdfPath());
if (StringUtils.isBlank(pdfPath)) {
return R.fail("manifest.items[].pdfPath不能为空");
}
if (!isPdfFileName(pdfPath)) {
return R.fail("manifest.items[].pdfPath仅支持PDF: " + pdfPath);
}
if (StringUtils.isBlank(item.getMaterialCode())) {
return R.fail("manifest.items[].materialCode不能为空: " + pdfPath);
}
if (StringUtils.isBlank(item.getMaterialName())) {
return R.fail("manifest.items[].materialName不能为空: " + pdfPath);
}
if (itemByPdfPath.containsKey(pdfPath)) {
return R.fail("manifest中存在重复pdfPath: " + pdfPath);
}
itemByPdfPath.put(pdfPath, item);
}
Path tempZip = null;
try {
Files.createDirectories(dir);
tempZip = Files.createTempFile("plm-pdf-", ".zip");
try (InputStream in = zipFile.getInputStream()) {
Files.copy(in, tempZip, StandardCopyOption.REPLACE_EXISTING);
}
Map<String, ZipEntry> pdfEntryByPath = new HashMap<>(itemByPdfPath.size() * 2);
Set<String> extraPdfFiles = new LinkedHashSet<>();
try (ZipFile zf = new ZipFile(tempZip.toFile())) {
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
if (ze == null || ze.isDirectory()) {
continue;
}
String name = normalizeZipEntryName(ze.getName());
if (StringUtils.isBlank(name)) {
continue;
}
if (!isPdfFileName(name)) {
continue;
}
if (pdfEntryByPath.containsKey(name)) {
return R.fail("zip中存在重复文件路径: " + name);
}
pdfEntryByPath.put(name, ze);
if (!itemByPdfPath.containsKey(name)) {
extraPdfFiles.add(name);
}
}
List<String> missing = new ArrayList<>();
for (String pdfPath : itemByPdfPath.keySet()) {
if (!pdfEntryByPath.containsKey(pdfPath)) {
missing.add(pdfPath);
}
}
if (!missing.isEmpty()) {
return R.fail("zip缺少以下PDF文件: " + String.join(", ", missing));
}
if (!extraPdfFiles.isEmpty()) {
return R.fail("zip包含未在manifest声明的PDF文件: " + String.join(", ", extraPdfFiles));
}
List<Map<String, String>> saved = new ArrayList<>(itemByPdfPath.size());
for (Map.Entry<String, PlmPdfManifestItem> kv : itemByPdfPath.entrySet()) {
String pdfPath = kv.getKey();
PlmPdfManifestItem item = kv.getValue();
ZipEntry ze = pdfEntryByPath.get(pdfPath);
String safeMaterialCode = sanitizeFileNamePart(item.getMaterialCode());
String safeMaterialName = sanitizeFileNamePart(item.getMaterialName());
String targetBaseName = safeMaterialCode + separator + safeMaterialName;
Path targetFile = resolveAvailablePdfPath(dir, targetBaseName);
try (InputStream in = zf.getInputStream(ze)) {
Files.copy(in, targetFile, StandardCopyOption.REPLACE_EXISTING);
}
Map<String, String> row = new HashMap<>(7);
row.put("pdfPath", pdfPath);
row.put("materialCode", item.getMaterialCode());
row.put("materialName", item.getMaterialName());
row.put("savedFileName", targetFile.getFileName().toString());
row.put("savedDir", dir.toString());
row.put("savedPath", targetFile.toString());
saved.add(row);
}
Map<String, Object> resp = new HashMap<>(6);
resp.put("productionOrderNo", productionOrderNo);
resp.put("savedDir", dir.toString());
resp.put("count", saved.size());
resp.put("items", saved);
return R.ok(resp);
}
} catch (Exception e) {
log.error("接收PLM PDF压缩包失败: productionOrderNo={}", manifest.getProductionOrderNo(), e);
return R.fail("接收PLM PDF压缩包失败: " + e.getMessage());
} finally {
if (tempZip != null) {
try {
Files.deleteIfExists(tempZip);
} catch (Exception ignored) {
}
}
}
}
private static boolean isPdfFileName(String fileName) {
if (StringUtils.isBlank(fileName)) {
return false;
}
return fileName.trim().toLowerCase(Locale.ROOT).endsWith(".pdf");
}
private static String normalizeOriginalName(String fileName) {
String s = StringUtils.trimToEmpty(fileName);
if (StringUtils.isBlank(s)) {
return s;
}
s = s.replace("\\", "/");
int idx = s.lastIndexOf('/');
if (idx >= 0 && idx + 1 < s.length()) {
s = s.substring(idx + 1);
}
return s.trim();
}
private static String normalizeZipEntryName(String entryName) {
String s = StringUtils.trimToEmpty(entryName);
if (StringUtils.isBlank(s)) {
return s;
}
s = s.replace("\\", "/");
while (s.startsWith("/")) {
s = s.substring(1);
}
while (s.startsWith("./")) {
s = s.substring(2);
}
if (s.contains("..")) {
throw new IllegalArgumentException("非法zip路径: " + entryName);
}
return s.trim();
}
private static String sanitizePathPart(String input) {
String s = StringUtils.trimToEmpty(input);
s = s.replace("\\", "_").replace("/", "_");
return sanitizeFileNamePart(s);
}
private static String sanitizeFileNamePart(String input) {
String s = StringUtils.trimToEmpty(input);
s = s.replaceAll("[\\\\/:*?\"<>|]", "_");
s = s.replaceAll("\\s+", " ");
s = s.trim();
if (s.length() > 120) {
s = s.substring(0, 120);
}
if (StringUtils.isBlank(s)) {
return "_";
}
return s;
}
private static Path resolveAvailablePdfPath(Path dir, String targetBaseName) {
String base = sanitizeFileNamePart(targetBaseName);
Path p = dir.resolve(base + ".pdf");
if (!Files.exists(p)) {
return p;
}
String suffix = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
return dir.resolve(base + "-" + suffix + ".pdf");
}
@Data
private static class PlmPdfBatchItem {
private String pdfFileName;
private String materialCode;
private String materialName;
}
@Data
private static class PlmPdfManifest {
private String productionOrderNo;
private List<PlmPdfManifestItem> items;
}
@Data
private static class PlmPdfManifestItem {
private String pdfPath;
private String materialCode;
private String materialName;
}
/** /**
* 查询物料检索列表 * 查询物料检索列表
*/ */
@ -186,6 +628,16 @@ public class ImMaterialController extends BaseController {
return toAjax(iImMaterialService.deleteWithValidByIds(Arrays.asList(ids), true)); return toAjax(iImMaterialService.deleteWithValidByIds(Arrays.asList(ids), true));
} }
/**
* 手动触发全量分页刷新 im_material 单重按物料编码解析仅单重变化时更新 updateTimemodify_date
*/
@SaCheckPermission("system:material:edit")
@Log(title = "刷新物料单重", businessType = BusinessType.UPDATE)
@PostMapping("/refreshSingleWeightFromOrders")
public R<Map<String, Object>> refreshSingleWeightFromOrders() {
return iImMaterialService.refreshImMaterialSingleWeightFromOrders();
}
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", ImMaterialBo.class, response); ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", ImMaterialBo.class, response);
@ -485,6 +937,91 @@ public class ImMaterialController extends BaseController {
public TableDataInfo search(@RequestParam(value = "query", required = false) String query) { public TableDataInfo search(@RequestParam(value = "query", required = false) String query) {
return iImMaterialService.search(query); return iImMaterialService.search(query);
} }
/**
* 齐套物料查询
*/
@SaCheckPermission("system:material:buildCapacity")
@Log(title = "齐套物料", businessType = BusinessType.EXPORT)
@GetMapping("/buildCapacity")
public R<BuildCapacityDTO> buildCapacity(@RequestParam("productionOrderNo") String productionOrderNo,
@RequestParam("productCode") String productCode,
@RequestParam(value = "productName", required = false) String productName) {
return R.ok(iImMaterialService.queryBuildCapacity(productionOrderNo, productCode, productName));
}
@SaCheckPermission("system:material:exportBuildCapacity")
@Log(title = "齐套物料导出", businessType = BusinessType.EXPORT)
@PostMapping("/buildCapacity/export")
public void exportBuildCapacity(@RequestParam("productionOrderNo") String productionOrderNo,
@RequestParam("productCode") String productCode,
@RequestParam(value = "productName", required = false) String productName,
HttpServletResponse response) {
BuildCapacityDTO dto = iImMaterialService.queryBuildCapacity(productionOrderNo, productCode, productName);
List<BuildCapacityMaterialExportVo> rows = new ArrayList<>();
if (dto != null) {
appendBuildCapacityRows(rows, productionOrderNo, dto, "自制", dto.getSelfMadeMaterials());
appendBuildCapacityRows(rows, productionOrderNo, dto, "采购", dto.getPurchaseMaterials());
}
ExcelUtil.exportExcel(rows, "齐套物料", BuildCapacityMaterialExportVo.class, response);
}
@GetMapping("/buildCapacityByProductionOrderNo")
public R<List<BuildCapacityDTO>> buildCapacityByProductionOrderNo(@RequestParam("productionOrderNo") String productionOrderNo) {
return R.ok(iImMaterialService.queryBuildCapacityByProductionOrderNo(productionOrderNo));
}
@GetMapping("/productionOrderMaterials")
public R<ProductionOrderMaterialsDTO> productionOrderMaterials(@RequestParam("productionOrderNo") String productionOrderNo,
@RequestParam("productCode") String productCode) {
return R.ok(iImMaterialService.queryProductionOrderMaterials(productionOrderNo, productCode));
}
private static void appendBuildCapacityRows(List<BuildCapacityMaterialExportVo> out,
String productionOrderNo,
BuildCapacityDTO dto,
String materialType,
List<MaterialItem> items) {
if (items == null || items.isEmpty()) {
return;
}
for (MaterialItem item : items) {
if (item == null) {
continue;
}
BuildCapacityMaterialExportVo row = new BuildCapacityMaterialExportVo();
row.setProductionOrderNo(productionOrderNo);
row.setProductCode(dto.getProductCode());
row.setProductName(dto.getProductName());
row.setMaxBuildQty(dto.getMaxBuildQty());
row.setMaterialType(materialType);
row.setMaterialCode(item.getMaterialCode());
row.setMaterialName(item.getMaterialName());
row.setRequiredQty(item.getRequiredQty());
row.setMaterial(item.getMaterial());
row.setSingleWeight(item.getSingleWeight());
row.setTotalWeight(item.getTotalWeight());
row.setRemark(item.getRemark());
row.setBatchQuantity(item.getBatchQuantity());
StockInfo stock = item.getStock();
if (stock != null) {
row.setOnHandQty(stock.getOnHandQty());
row.setPoNotInQty(stock.getPoNotInQty());
row.setMoNotInQty(stock.getMoNotInQty());
row.setMoNotPickQty(stock.getMoNotPickQty());
}
CalcInfo calc = item.getCalc();
if (calc != null) {
row.setAvailableQty(calc.getAvailableQty());
row.setFinalAvailableQty(calc.getFinalAvailableQty());
row.setCanSupportQty(calc.getCanSupportQty());
}
row.setStatus(item.getStatus() != null ? item.getStatus().name() : null);
out.add(row);
}
}
@SaIgnore @SaIgnore
@PostMapping("/getObtainPartData2") @PostMapping("/getObtainPartData2")
public R<Void> getObtainPartData() { public R<Void> getObtainPartData() {

View File

@ -15,6 +15,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.ruoyi.common.excel.DefaultExcelListener; import com.ruoyi.common.excel.DefaultExcelListener;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.FtpUtil; import com.ruoyi.common.utils.FtpUtil;
import com.ruoyi.common.utils.HttpUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.VersionComparator; import com.ruoyi.common.utils.VersionComparator;
import com.ruoyi.common.utils.file.SmbUtil; import com.ruoyi.common.utils.file.SmbUtil;
@ -32,6 +33,7 @@ import com.ruoyi.system.mapper.ProcessOrderProMapper;
import com.ruoyi.system.mapper.SafetyStockMapper; import com.ruoyi.system.mapper.SafetyStockMapper;
import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.runner.JdUtil;
import com.ruoyi.system.service.*; import com.ruoyi.system.service.*;
import com.ruoyi.system.utils.ProductionOrderNoUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFRow;
@ -40,6 +42,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*; import javax.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -244,7 +247,14 @@ public class ProcessOrderProController extends BaseController {
String s = iProcessOrderProService.executDrawing(orderPro); String s = iProcessOrderProService.executDrawing(orderPro);
return R.ok(s); return R.ok(s);
} }
@GetMapping("/getstate")
public R<JSONObject> getPlotState() {
String result = HttpUtils.sendGet("http://192.168.5.18:9000/getstate", "UTF-8");
if (StringUtils.isBlank(result)) {
return R.ok("操作成功", null);
}
return R.ok("操作成功", JSON.parseObject(result));
}
@GetMapping("/executDrawing/progress/{id}") @GetMapping("/executDrawing/progress/{id}")
public SseEmitter executDrawingProgress(@PathVariable Long id) { public SseEmitter executDrawingProgress(@PathVariable Long id) {
return iProcessOrderProService.startDrawingSse(id); return iProcessOrderProService.startDrawingSse(id);
@ -933,8 +943,8 @@ public class ProcessOrderProController extends BaseController {
vo.setBatchQuantity(getCellValueAsString(row.getCell(8))); // 批次数量 vo.setBatchQuantity(getCellValueAsString(row.getCell(8))); // 批次数量
vo.setParentPart(getCellValueAsString(row.getCell(9))); // 上级部件 vo.setParentPart(getCellValueAsString(row.getCell(9))); // 上级部件
vo.setParentDrawingNo(getCellValueAsString(row.getCell(10))); // 上级部件图号 vo.setParentDrawingNo(getCellValueAsString(row.getCell(10))); // 上级部件图号
vo.setMainProducts(getCellValueAsString(row.getCell(11))); // 主产品图号 vo.setMainPart(getCellValueAsString(row.getCell(11))); // 主产品图号
vo.setMainProductsName(getCellValueAsString(row.getCell(12))); // 主产品名称 vo.setMainName(getCellValueAsString(row.getCell(12))); // 主产品名称
resultList.add(vo); resultList.add(vo);
} }
@ -1095,8 +1105,8 @@ public class ProcessOrderProController extends BaseController {
} }
map.put("batchQuantityInt", batchQuantityInt); map.put("batchQuantityInt", batchQuantityInt);
map.put("mainProducts", item.getMainProducts()); map.put("mainProducts", item.getMainPart());
map.put("mainProductsName", item.getMainProductsName()); map.put("mainProductsName", item.getMainName());
mapList.add(map); mapList.add(map);
} }
return mapList; return mapList;
@ -1882,6 +1892,25 @@ public class ProcessOrderProController extends BaseController {
return mapList; return mapList;
} }
/**
* 出图完成后由前端触发先推送出图完成Markdown再生成工艺计划表 Excel 并发至钉钉 notifyUserId
*/
@SaCheckPermission("system:orderPro:sendProcessRoutePlanExcel")
@Log(title = "发送工艺计划表", businessType = BusinessType.OTHER)
@PostMapping("/sendProcessRoutePlanExcel/{id}")
public R<Void> sendProcessRoutePlanExcel(@NotNull(message = "项目ID不能为空") @PathVariable Long id) {
return iProcessOrderProService.sendProcessRoutePlanExcel(id);
}
/**
* 查询工艺计划表钉钉发送成功次数用于按钮文案
*/
@SaIgnore
@GetMapping("/routePlanSendCount/{id}")
public R<Integer> getRoutePlanSendCount(@NotNull(message = "项目ID不能为空") @PathVariable Long id) {
return iProcessOrderProService.getRoutePlanSendCount(id);
}
@SaCheckPermission("system:orderPro:exportRoute3") @SaCheckPermission("system:orderPro:exportRoute3")
@Log(title = "下载工艺生产表3", businessType = BusinessType.EXPORT) @Log(title = "下载工艺生产表3", businessType = BusinessType.EXPORT)
@GetMapping ("/exportRoute3") @GetMapping ("/exportRoute3")
@ -1925,30 +1954,28 @@ public class ProcessOrderProController extends BaseController {
return generateRoute3Excel(id, false); return generateRoute3Excel(id, false);
} }
/** 去掉 Windows 文件名非法字符,避免生成或钉钉展示异常 */
private static String sanitizeFileNameSegment(String name) {
if (StringUtils.isBlank(name)) {
return "unknown";
}
String trimmed = name.trim();
String illegal = "\\/:*?\"<>|";
StringBuilder sb = new StringBuilder(trimmed.length());
for (int i = 0; i < trimmed.length(); i++) {
char c = trimmed.charAt(i);
sb.append(illegal.indexOf(c) >= 0 ? '_' : c);
}
String out = sb.toString();
return out.isEmpty() ? "unknown" : out;
}
public String generateRoute3Excel(Long id, boolean isDrawing) throws Exception { public String generateRoute3Excel(Long id, boolean isDrawing) throws Exception {
ProcessOrderPro orderPro = processOrderProMapper.selectById(id); ProcessOrderPro orderPro = processOrderProMapper.selectById(id);
// 下载Excel文件 // 下载Excel文件
// 1. 解析年份 // 1. 解析年份
String productionCode = orderPro.getProductionOrderNo(); String productionCode = orderPro.getProductionOrderNo();
String year = "2025"; // 默认 String year = ProductionOrderNoUtils.extractYear(productionCode);
if (productionCode != null) {
if (productionCode.startsWith("EY")) {
if (productionCode.length() >= 4) {
try {
Integer.parseInt(productionCode.substring(2, 4));
year = "20" + productionCode.substring(2, 4);
} catch (Exception e) {}
}
} else {
int firstDash = productionCode.indexOf("-");
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
try {
Integer.parseInt(productionCode.substring(firstDash + 1, firstDash + 3));
year = "20" + productionCode.substring(firstDash + 1, firstDash + 3);
} catch (Exception e) {}
}
}
}
// 2. 构建FTP路径和本地路径 // 2. 构建FTP路径和本地路径
String ftpPath = "/" + year + "/" + productionCode; String ftpPath = "/" + year + "/" + productionCode;
@ -2313,7 +2340,11 @@ public class ProcessOrderProController extends BaseController {
// 使用Excel模板文件 // 使用Excel模板文件
String templatePath = "jpg/生产及工艺计划模版.xlsx"; String templatePath = "jpg/生产及工艺计划模版.xlsx";
String outputPath = "D:/file/" + orderPro.getProductionOrderNo() + "生产及工艺计划表.xlsx"; String safeOrderNo = sanitizeFileNameSegment(orderPro.getProductionOrderNo());
// 钉钉发送场景用令号+工艺表避免过长中文后缀在客户端显示异常浏览器导出仍为生产及工艺计划表
String outputPath = isDrawing
? ("D:/file/" + safeOrderNo + "工艺表.xlsx")
: ("D:/file/" + safeOrderNo + "生产及工艺计划表.xlsx");
// 准备模板数据 // 准备模板数据
Map<String, Object> staticDataMap = new HashMap<>(); Map<String, Object> staticDataMap = new HashMap<>();
@ -2393,7 +2424,7 @@ public class ProcessOrderProController extends BaseController {
return; return;
} }
// 检查 quantity 字段假设你的数量字段名是 quantity 或者 unitQuantity 这里假设是 getQuantity()如果不对请调整为对应的 get 方法 // 检查 quantity 字段假设你的数量字段名是 quantity 或者 unitQuantity 这里假设是 getQuantity()如果不对请调整为对应的 get 方法
if (orderPro.getQuantity() != null && orderPro.getQuantity().intValue() == 0) { if (orderPro.getIsDownload() != null && orderPro.getIsDownload().intValue() == 0) {
response.setContentType("application/json;charset=utf-8"); response.setContentType("application/json;charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(R.fail("当前图纸数量不允许下载"))); response.getWriter().write(JSONObject.toJSONString(R.fail("当前图纸数量不允许下载")));
return; return;

View File

@ -40,6 +40,8 @@ import com.ruoyi.system.mapper.ProcessOrderProMapper;
import com.ruoyi.system.mapper.ProcessRouteMapper; import com.ruoyi.system.mapper.ProcessRouteMapper;
import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.runner.JdUtil;
import com.ruoyi.system.service.*; import com.ruoyi.system.service.*;
import com.ruoyi.system.service.support.OperationPlanningWorkshopUpdateService;
import com.ruoyi.system.service.support.OperationReportWorkshopUpdateService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -62,6 +64,8 @@ import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/** /**
* 工艺路线 * 工艺路线
@ -76,6 +80,8 @@ import java.util.stream.Collectors;
public class ProcessRouteController extends BaseController { public class ProcessRouteController extends BaseController {
private final ProcessRouteMapper baseMapper; private final ProcessRouteMapper baseMapper;
private final IProcessRouteService iProcessRouteService; private final IProcessRouteService iProcessRouteService;
private final OperationPlanningWorkshopUpdateService operationPlanningWorkshopUpdateService;
private final OperationReportWorkshopUpdateService operationReportWorkshopUpdateService;
private final IBomDetailsService iBomDetailsService; private final IBomDetailsService iBomDetailsService;
private final ProcessOrderProMapper proMapper; private final ProcessOrderProMapper proMapper;
private final IImMaterialService materialService; private final IImMaterialService materialService;
@ -603,6 +609,15 @@ public class ProcessRouteController extends BaseController {
if (!zipFile.exists()) { if (!zipFile.exists()) {
throw new FileNotFoundException("ZIP 文件未找到: " + zipFilePath); throw new FileNotFoundException("ZIP 文件未找到: " + zipFilePath);
} }
if (zipFile.length() <= 0) {
throw new IOException("ZIP 文件为空: " + zipFilePath);
}
try (ZipFile zf = new ZipFile(zipFile)) {
Enumeration<? extends ZipEntry> entries = zf.entries();
if (entries == null || !entries.hasMoreElements()) {
throw new IOException("ZIP 文件中没有PDF: " + zipFilePath);
}
}
FileInputStream fis = new FileInputStream(zipFile); FileInputStream fis = new FileInputStream(zipFile);
OutputStream outputStreams = response.getOutputStream(); OutputStream outputStreams = response.getOutputStream();
response.setContentType("application/zip"); response.setContentType("application/zip");
@ -624,6 +639,46 @@ public class ProcessRouteController extends BaseController {
return iProcessRouteService.updateProcessPlan(rooteProdet); return iProcessRouteService.updateProcessPlan(rooteProdet);
} }
/**
* 按车间+物料默认条件拉取工序计划回填工艺时间保存时部门统一为 018 {@link OperationPlanningWorkshopUpdateService} 一致
*/
@Log(title = "按车间物料更新工序计划(默认条件)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateProcessPlanByWorkshop")
public R<List<Model>> updateProcessPlanByWorkshop() throws Exception {
return R.ok(operationPlanningWorkshopUpdateService.updateProcessPlanByWorkshopFilter());
}
/**
* 同上BillQuery 过滤条件可自定义部门 Compare/Value物料 Compare/Value
*/
@Log(title = "按车间物料更新工序计划(自定义条件)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateProcessPlanByWorkshopCustom")
public R<List<Model>> updateProcessPlanByWorkshopCustom(
@RequestParam String departmentCompare,
@RequestParam String departmentNumber,
@RequestParam String productCompare,
@RequestParam String productFilterValue) throws Exception {
return R.ok(operationPlanningWorkshopUpdateService.updateProcessPlanByWorkshopFilter(
departmentCompare, departmentNumber, productCompare, productFilterValue));
}
@Log(title = "按车间物料更新工序汇报单(默认条件)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateOperationReportByWorkshop")
public R<List<Model>> updateOperationReportByWorkshop() throws Exception {
return R.ok(operationReportWorkshopUpdateService.updateOperationReportByWorkshopFilter());
}
@Log(title = "按车间物料更新工序汇报单(固定条件67/007/17/-MP)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateOperationReportByWorkshopCustom")
public R<List<Model>> updateOperationReportByWorkshopCustom() throws Exception {
return R.ok(operationReportWorkshopUpdateService.updateOperationReportByWorkshopFilter(
"67", "007", "17", "-MP"));
}
/** /**
* 更新生产订单的时间 * 更新生产订单的时间
*/ */

View File

@ -3,6 +3,8 @@ package com.ruoyi.system.controller;
import java.util.*; import java.util.*;
import com.ruoyi.system.domain.ProductionOrder; import com.ruoyi.system.domain.ProductionOrder;
import com.ruoyi.system.domain.dto.productionorder.ProductionOrderWeightExportDTO;
import com.ruoyi.system.domain.dto.productionorder.ProductionOrderWeightImportDTO;
import com.ruoyi.system.domain.dto.PurchaseOrderExcelDTO; import com.ruoyi.system.domain.dto.PurchaseOrderExcelDTO;
import com.ruoyi.system.domain.dto.PurchaseRequestExcelDTO; import com.ruoyi.system.domain.dto.PurchaseRequestExcelDTO;
import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.runner.JdUtil;
@ -138,6 +140,19 @@ public class ProductionOrderController extends BaseController {
return R.ok(null); return R.ok(null);
} }
/**
* 测试方法导入物料编码后批量匹配单重并导出
* Excel列物料编码物料名称单重
*/
@Log(title = "物料单重匹配导出", businessType = BusinessType.IMPORT)
@SaCheckPermission("system:order:import")
@PostMapping(value = "/testExportWeightByMaterialCode", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void testExportWeightByMaterialCode(@RequestPart("file") MultipartFile file, HttpServletResponse response) throws Exception {
List<ProductionOrderWeightImportDTO> importRows = ExcelUtil.importExcel(file.getInputStream(), ProductionOrderWeightImportDTO.class);
List<ProductionOrderWeightExportDTO> exportRows = iProductionOrderService.matchSingleWeightByMaterialCode(importRows);
ExcelUtil.exportExcel(exportRows, "物料单重匹配结果", ProductionOrderWeightExportDTO.class, response);
}
/** /**
* 获取组焊件bom列表 * 获取组焊件bom列表
* @param productionOrderVo * @param productionOrderVo

View File

@ -12,6 +12,7 @@ import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.FigureSave; import com.ruoyi.system.domain.FigureSave;
import com.ruoyi.system.domain.ProductionOrder;
import com.ruoyi.system.domain.ProductionPlan; import com.ruoyi.system.domain.ProductionPlan;
import com.ruoyi.system.domain.bo.ProductionPlanBo; import com.ruoyi.system.domain.bo.ProductionPlanBo;
import com.ruoyi.system.domain.vo.ProductionPlanVo; import com.ruoyi.system.domain.vo.ProductionPlanVo;
@ -20,6 +21,7 @@ import com.ruoyi.system.mapper.FigureSaveMapper;
import com.ruoyi.system.mapper.ProductionPlanMapper; import com.ruoyi.system.mapper.ProductionPlanMapper;
import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.runner.JdUtil;
import com.ruoyi.system.service.IFigureSaveService; import com.ruoyi.system.service.IFigureSaveService;
import com.ruoyi.system.service.IProductionOrderService;
import com.ruoyi.system.service.IProductionPlanService; import com.ruoyi.system.service.IProductionPlanService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
@ -51,6 +53,7 @@ import java.util.List;
public class ProductionPlanController extends BaseController { public class ProductionPlanController extends BaseController {
private final IFigureSaveService iFigureSaveService; private final IFigureSaveService iFigureSaveService;
private final IProductionPlanService iProductionPlanService; private final IProductionPlanService iProductionPlanService;
private final IProductionOrderService iProductionOrderService;
@Resource @Resource
private ProductionPlanMapper productionPlanMapper; private ProductionPlanMapper productionPlanMapper;
@Resource @Resource
@ -210,6 +213,26 @@ public class ProductionPlanController extends BaseController {
return R.ok("状态更新成功"); return R.ok("状态更新成功");
} }
@SaCheckPermission("system:plan:productionOrderNoOptions")
@Log(title = "查询生产令号及主产品,预配套使用", businessType = BusinessType.UPDATE)
@GetMapping("/productionOrderNoOptions")
public R<List<String>> productionOrderNoOptions(@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "limit", required = false) Integer limit) {
return R.ok("ok", iProductionOrderService.selectProductionOrderNoOptions(keyword, limit));
}
/**
* 根据生产令号查询所有的装配bom
*
* @param productionOrderNo 生产令号
*/
@SaCheckPermission("system:plan:selectByProCode")
@Log(title = "排产计划", businessType = BusinessType.UPDATE)
@PostMapping("/selectByProCode")
public R<List<ProductionOrder>> selectByProCode(@RequestParam("productionOrderNo") String productionOrderNo) {
List<ProductionOrder> productionOrders = iProductionOrderService.selectByProCode(productionOrderNo.trim());
return R.ok("ok", productionOrders);
}

View File

@ -46,8 +46,7 @@ public class SfcOperationPlanningMainController extends BaseController {
/** /**
* 同步工序计划数据 * 同步工序计划数据
*/ */
@SaIgnore @SaCheckPermission("system:operationPlanningMain:sync")
//@SaCheckPermission("system:operationPlanningMain:sync")
@Log(title = "工序计划数据同步", businessType = BusinessType.INSERT) @Log(title = "工序计划数据同步", businessType = BusinessType.INSERT)
@PostMapping("/sync") @PostMapping("/sync")
public R<Void> syncData() { public R<Void> syncData() {
@ -57,7 +56,7 @@ public class SfcOperationPlanningMainController extends BaseController {
/** /**
* 直接从K3查询工序计划数据不入库测试 * 直接从K3查询工序计划数据不入库测试
*/ */
@SaIgnore @SaCheckPermission("system:operationPlanningMain:queryK3")
@GetMapping("/queryFromK3") @GetMapping("/queryFromK3")
public R<List<OperationPlanResultDTO>> queryFromK3() { public R<List<OperationPlanResultDTO>> queryFromK3() {
return R.ok(iSfcOperationPlanningMainService.queryFromK3Directly()); return R.ok(iSfcOperationPlanningMainService.queryFromK3Directly());
@ -66,13 +65,13 @@ public class SfcOperationPlanningMainController extends BaseController {
/** /**
* 分页查询工序计划主及明细级联数据 * 分页查询工序计划主及明细级联数据
*/ */
@SaIgnore @SaCheckPermission("system:operationPlanningMain:list")
@GetMapping("/cascadeList") @GetMapping("/cascadeList")
public TableDataInfo<OperationPlanResultDTO> cascadeList(SfcOperationPlanningMainBo bo, PageQuery pageQuery) { public TableDataInfo<OperationPlanResultDTO> cascadeList(SfcOperationPlanningMainBo bo, PageQuery pageQuery) {
return iSfcOperationPlanningMainService.queryCascadePageList(bo, pageQuery); return iSfcOperationPlanningMainService.queryCascadePageList(bo, pageQuery);
} }
@SaIgnore @SaCheckPermission("system:operationPlanningMain:updateOperators")
@Log(title = "工序作业人员/设备更新", businessType = BusinessType.UPDATE) @Log(title = "工序作业人员/设备更新", businessType = BusinessType.UPDATE)
@PutMapping("/updateOperators") @PutMapping("/updateOperators")
public R<Void> updateOperators(@Validated @RequestBody OperationPlanningUpdateOperatorsReq req) { public R<Void> updateOperators(@Validated @RequestBody OperationPlanningUpdateOperatorsReq req) {

View File

@ -33,7 +33,10 @@ public class ProcessOrderPro extends BaseEntity {
* 是否延期0否 1是 * 是否延期0否 1是
*/ */
private Integer whetherDelay; private Integer whetherDelay;
/**
* 是否可以下载
*/
private Integer isDownload;
/** /**
* 延期天数 * 延期天数
*/ */
@ -134,4 +137,9 @@ public class ProcessOrderPro extends BaseEntity {
*/ */
private String notificationCardId; private String notificationCardId;
/**
* 工艺计划表钉钉发送次数每次发送成功 +1
*/
private Integer routePlanSendCount;
} }

View File

@ -66,6 +66,14 @@ public class ProductionOrder extends BaseEntity {
* 部件图号 * 部件图号
*/ */
private String parentDrawingNo; private String parentDrawingNo;
/**
* 主产品名称
*/
private String mainName;
/**
* 主产品图号
*/
private String mainPart;
/** /**
* 批次数量 * 批次数量
*/ */

View File

@ -42,6 +42,9 @@ public class SfcOperationPlanningMain extends BaseEntity {
* 生产令号 * 生产令号
*/ */
private String hbytSclh; private String hbytSclh;
@TableField("hbyt_xmh")
private String fHbytXmh;
/** /**
* 产品编码 * 产品编码
*/ */

View File

@ -81,6 +81,10 @@ public class ProcessOrderProBo extends BaseEntity {
*/ */
private List<String> isEnterpriseStandardList; private List<String> isEnterpriseStandardList;
/**
* 是否可以下载
*/
private Integer isDownload;
private String drawingPath; private String drawingPath;
/** /**

View File

@ -64,4 +64,14 @@ public class ProductionOrderBo extends BaseEntity {
* 批次数量 * 批次数量
*/ */
private String batchQuantity; private String batchQuantity;
/**
* 主产品名称
*/
private String mainName;
/**
* 主产品图号
*/
private String mainPart;
} }

View File

@ -1,5 +1,6 @@
package com.ruoyi.system.domain.bo; package com.ruoyi.system.domain.bo;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup; import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.core.validate.EditGroup;
@ -50,7 +51,8 @@ public class SfcOperationPlanningMainBo extends BaseEntity {
*/ */
@NotBlank(message = "生产令号不能为空", groups = { AddGroup.class, EditGroup.class }) @NotBlank(message = "生产令号不能为空", groups = { AddGroup.class, EditGroup.class })
private String hbytSclh; private String hbytSclh;
@NotBlank(message = "项目号不能为空", groups = { AddGroup.class, EditGroup.class })
private String fHbytXmh;
/** /**
* 产品编码 * 产品编码
*/ */

View File

@ -8,7 +8,7 @@ import java.util.Date;
@Data @Data
public class ConstructionDelayDTO { public class ConstructionDelayDTO {
//生产令号 //生产令号
@JsonProperty("F_HBYT_SCLH") @JsonProperty("F_HBYT_XMH")
private String F_HBYT_SCLH; private String F_HBYT_SCLH;
//单据编码 //单据编码
@JsonProperty("FBillNo") @JsonProperty("FBillNo")

View File

@ -18,6 +18,8 @@ public class OperationPlanResultDTO {
*/ */
private String batchNo; private String batchNo;
private String projectNo;
/** /**
* 产品编码 * 产品编码
*/ */

View File

@ -25,7 +25,11 @@ public class OperationPlanningDTO {
* 生产令号 * 生产令号
*/ */
@JsonProperty("F_HBYT_SCLH") @JsonProperty("F_HBYT_SCLH")
private String fHbytSclh; private String fHbytSclh;/**
* 生产令号
*/
@JsonProperty("F_HBYT_XMH")
private String fHbytXmh;
/** /**
* 生产订单编号 * 生产订单编号

View File

@ -58,6 +58,60 @@ public class ProcessDetailDTO {
*/ */
private Double wastageQty; private Double wastageQty;
/**
* 综合看板统计日请求参数 boardReportDay默认服务器当天内金蝶工序汇报汇总与本工序号匹配的完工数量合计
*/
private Double dayReportFinishQty;
/** 同上:合格数量合计 */
private Double dayReportQuaQty;
/** 同上:废品/不良数量合计 */
private Double dayReportFailQty;
/**
* 综合看板生产入库数量当前追溯数据未按工序拆分仅在<strong>该工序计划主档</strong>下工序号最大的一道工序上回填本生产订单入库数量合计其余工序为 null
*/
private Double operationInstockQty;
// ---------- 综合看板金蝶工序委外转移报表并入工序明细 boardReportDay 切分 ----------
/**
* 单据日期 &lt; boardReportDay 的委外<strong>发出</strong>数量累计多行合并
* 仅当本工序被识别为委外/外协相关且匹配到报表行时有值否则为 null
*/
private Double subcontractLeaveQtyBeforeReportDay;
/**
* 单据日期 &lt; boardReportDay 的委外<strong>接收</strong>数量累计
*/
private Double subcontractEnterQtyBeforeReportDay;
/**
* 单据日期 &lt; boardReportDay 的委外<strong>合格</strong>数量累计
*/
private Double subcontractQualifiedQtyBeforeReportDay;
/**
* 单据日期 = boardReportDay 当天的委外<strong>发出</strong>合计可多笔合并
*/
private Double subcontractLeaveQtyOnReportDay;
/**
* 单据日期 = boardReportDay 当天的委外<strong>接收</strong>合计
*/
private Double subcontractEnterQtyOnReportDay;
/**
* 单据日期 = boardReportDay 当天的委外<strong>合格</strong>合计
*/
private Double subcontractQualifiedQtyOnReportDay;
/**
* 统计日当天委外<strong>发出 + 接收</strong>之和便于前端单列展示等于 subcontractLeaveQtyOnReportDay + subcontractEnterQtyOnReportDay无当日数据时为 null
*/
private Double subcontractLeaveEnterSumOnReportDay;
/** /**
* 加工车间 * 加工车间
*/ */

View File

@ -0,0 +1,13 @@
package com.ruoyi.system.domain.dto.buildcapacity;
import lombok.Data;
@Data
public class BottleneckMaterial {
private String materialCode;
private String materialName;
private Integer canSupportQty;
}

View File

@ -0,0 +1,34 @@
package com.ruoyi.system.domain.dto.buildcapacity;
import lombok.Data;
import java.util.List;
@Data
public class BuildCapacityDTO {
/**
* 成品编码前端选中的成品/产品编码
*/
private String productCode;
/**
* 成品名称可选仅用于展示
*/
private String productName;
/**
* 最大可生产套数取所有物料行 calc.canSupportQty 的最小值
*/
private Integer maxBuildQty;
/**
* 自制物料明细物料编码非 009 开头
*/
private List<MaterialItem> selfMadeMaterials;
/**
* 采购物料明细物料编码以 009 开头
*/
private List<MaterialItem> purchaseMaterials;
}

View File

@ -0,0 +1,13 @@
package com.ruoyi.system.domain.dto.buildcapacity;
import lombok.Data;
@Data
public class CalcInfo {
private Integer availableQty;
private Integer finalAvailableQty;
private Integer canSupportQty;
}

View File

@ -0,0 +1,29 @@
package com.ruoyi.system.domain.dto.buildcapacity;
import lombok.Data;
@Data
public class MaterialItem {
private String materialCode;
private String materialName;
private Integer requiredQty;
private String material;
private Double singleWeight;
private Double totalWeight;
private String remark;
private String batchQuantity;
private StockInfo stock;
private CalcInfo calc;
private StockStatus status;
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.system.domain.dto.buildcapacity;
import lombok.Data;
@Data
public class StockInfo {
/**
* 即时库存当前可用库存台账数量
*/
private Integer onHandQty;
/**
* 采购未入库数量
*/
private Integer poNotInQty;
/**
* 生产订单未入库数量
*/
private Integer moNotInQty;
/**
* 生产订单未领料数量
*/
private Integer moNotPickQty;
}

View File

@ -0,0 +1,6 @@
package com.ruoyi.system.domain.dto.buildcapacity;
public enum StockStatus {
OK,
SHORTAGE
}

View File

@ -1,6 +1,8 @@
package com.ruoyi.system.domain.dto.excuteDrawing; package com.ruoyi.system.domain.dto.excuteDrawing;
import lombok.Data; import lombok.Data;
import java.util.List;
@Data @Data
public class ProductInfo { public class ProductInfo {
//型号名称 //型号名称
@ -11,4 +13,8 @@ public class ProductInfo {
private String number; private String number;
//变量信息 //变量信息
private DataInfo vars; private DataInfo vars;
//是否保留零件图
private boolean preservepartdrawing;
//企标图子件信息
private List<Subparts> subparts;
} }

View File

@ -108,4 +108,16 @@ public class FigureSaveVo {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date uploadTime; private Date uploadTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
} }

View File

@ -32,6 +32,12 @@ public class PlanOrderVo {
@ExcelProperty(value = "生产令号") @ExcelProperty(value = "生产令号")
@JsonProperty("F_HBYT_SCLH") @JsonProperty("F_HBYT_SCLH")
private String fHbytSclh; private String fHbytSclh;
/**
* 生产令号
*/
@ExcelProperty(value = "生产令号")
@JsonProperty("F_HBYT_XMH")
private String fHbytXmh;
/** /**
* 生产订单编号 * 生产订单编号

View File

@ -44,7 +44,11 @@ public class ProcessOrderProVo {
@ExcelProperty(value = "项目名称") @ExcelProperty(value = "项目名称")
private String productionName; private String productionName;
/**
* 是否可以下载
*/
@ExcelProperty(value = "是否可以下载")
private Integer isDownload;
/** /**
* 计划结束时间 * 计划结束时间
*/ */
@ -185,4 +189,10 @@ public class ProcessOrderProVo {
*/ */
private Long delayDay; private Long delayDay;
/**
* 工艺计划表钉钉发送次数
*/
@ExcelProperty(value = "工艺计划表发送次数")
private Integer routePlanSendCount;
} }

View File

@ -86,16 +86,16 @@ public class ProductionOrderVo {
@ExcelProperty(value = "上级部件图号") @ExcelProperty(value = "上级部件图号")
private String parentDrawingNo; private String parentDrawingNo;
/**
* 主产品图号
*/
@ExcelProperty(value = "主产品图号")
private String mainProducts;
/** /**
* 主产品名称 * 主产品名称
*/ */
@ExcelProperty(value = "主产品名称") @ExcelProperty(value = "主产品名称")
private String mainProductsName; private String mainName;
/**
* 主产品图号
*/
@ExcelProperty(value = "主产品图号")
private String mainPart;
} }

View File

@ -50,6 +50,11 @@ public class SfcOperationPlanningMainVo {
*/ */
@ExcelProperty(value = "生产令号") @ExcelProperty(value = "生产令号")
private String hbytSclh; private String hbytSclh;
/**
* 项目号
*/
@ExcelProperty(value = "项目号")
private String fHbytXmh;;
/** /**
* 产品编码 * 产品编码

View File

@ -19,6 +19,13 @@ public class FSubEntity {
@JsonProperty("FOperNumber") @JsonProperty("FOperNumber")
private Long FOperNumber; private Long FOperNumber;
/**
* 加工车间部门 {@link F_HBYT_RKCK} 相同写法JSON
* {@code "FDepartmentId": { "FNumber": "018" }}对应金蝶字段路径 {@code FDepartmentId.FNumber}
*/
@JsonProperty("FWorkShopID")
private FWorkShopID FWorkShopID;
@JsonProperty("F_HBYT_ZYRY") @JsonProperty("F_HBYT_ZYRY")
private String F_HBYT_ZYRY; private String F_HBYT_ZYRY;
@ -31,6 +38,12 @@ public class FSubEntity {
@JsonProperty("F_HBYT_RKCK") @JsonProperty("F_HBYT_RKCK")
private F_HBYT_RKCK F_HBYT_RKCK; private F_HBYT_RKCK F_HBYT_RKCK;
@Data
public static class FWorkShopID {
@JsonProperty("FNUMBER")
private String FNUMBER;
}
@Data @Data
public static class F_HBYT_RKCK { public static class F_HBYT_RKCK {
@JsonProperty("FNUMBER") @JsonProperty("FNUMBER")

View File

@ -20,9 +20,10 @@ public class Model {
@JsonProperty("FPlanFinishTime") @JsonProperty("FPlanFinishTime")
private Date FPlanFinishTime; private Date FPlanFinishTime;
@JsonProperty("FEntity") @JsonProperty("FEntity")
private List<FEntity> FEntity = new ArrayList<>(); private List<FEntity> FEntity = new ArrayList<>();
// getters and setters
} }

View File

@ -4,6 +4,10 @@ import com.ruoyi.system.domain.ProductionOrder;
import com.ruoyi.system.domain.vo.ProductionOrderVo; import com.ruoyi.system.domain.vo.ProductionOrderVo;
import com.ruoyi.common.core.mapper.BaseMapperPlus; import com.ruoyi.common.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/** /**
* 生产订单Mapper接口 * 生产订单Mapper接口
@ -14,4 +18,23 @@ import org.apache.ibatis.annotations.Delete;
public interface ProductionOrderMapper extends BaseMapperPlus<ProductionOrderMapper, ProductionOrder, ProductionOrderVo> { public interface ProductionOrderMapper extends BaseMapperPlus<ProductionOrderMapper, ProductionOrder, ProductionOrderVo> {
@Delete("DELETE FROM production_order where production_order_no = #{productionOrderNo}") @Delete("DELETE FROM production_order where production_order_no = #{productionOrderNo}")
void deleteWithProCode(String productionOrderNo); void deleteWithProCode(String productionOrderNo);
@Select("SELECT DISTINCT production_order_no " +
"FROM production_order " +
"WHERE production_order_no IS NOT NULL " +
" AND production_order_no <> '' " +
" AND (#{keyword} IS NULL OR #{keyword} = '' OR production_order_no LIKE CONCAT('%', #{keyword}, '%')) " +
"ORDER BY production_order_no DESC " +
"LIMIT #{limit}")
List<String> selectDistinctProductionOrderNos(@Param("keyword") String keyword, @Param("limit") Integer limit);
/**
* 按图号批量查询单重
*/
List<ProductionOrder> selectWeightByDrawingNos(@Param("drawingNos") List<String> drawingNos);
/**
* 按物料编码批量查询工艺路线单重
*/
List<ProductionOrder> selectDiscWeightByMaterialCodes(@Param("materialCodes") List<String> materialCodes);
} }

View File

@ -1,5 +1,6 @@
package com.ruoyi.system.runner; package com.ruoyi.system.runner;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
@ -541,6 +542,298 @@ public class JdUtil {
return 0.0; return 0.0;
} }
/**
* 金蝶报表部分数值列以字符串返回且可能含千分位逗号 1,176需规范化后再转 BigDecimal
*/
private static String normalizeKingdeeNumericString(String raw) {
if (raw == null) {
return "";
}
return raw.trim()
.replace(",", "")
.replace("", "")
.replace("\u00A0", "")
.trim();
}
private static BigDecimal getBigDecimalFromJsonArray(JsonArray array, int index) {
if (array.size() <= index || array.get(index).isJsonNull()) {
return null;
}
JsonElement el = array.get(index);
String raw;
if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isNumber()) {
raw = el.getAsJsonPrimitive().getAsString();
} else {
raw = el.getAsString();
}
if (raw == null || raw.isEmpty()) {
return null;
}
String value = normalizeKingdeeNumericString(raw);
if (value.isEmpty()) {
return null;
}
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
log.warn("报表数量解析失败(规范化后仍非法): raw={} normalized={}", raw, value);
return null;
}
}
/**
* 工序汇报汇总报表系统报表 GetSysReportData
* FieldKeys 顺序需与 {@link KingdeeOperationReportSumRowDTO} 字段一致默认 11
*
* @param formId 报表 FormId金蝶报表属性中查看
* @param fieldKeys 输出列逗号分隔
* @param beginDateTime FBeginDate 2026-04-10 00:00:00
* @param endDateTime FEndDate 2026-05-10 23:59:59
* @param processOrgNumber 加工组织 FNumber 100
* @param timeUnit Model.FTimeUnit B 按日
* @param moBillNo 可选单个生产订单号过滤 FMOBillNo
* @param limitPerPage 每页 Limit内部会分页累加
*/
public static List<KingdeeOperationReportSumRowDTO> getOperationReportSumRpt(
String formId,
String fieldKeys,
String beginDateTime,
String endDateTime,
String processOrgNumber,
String timeUnit,
String moBillNo,
int limitPerPage) {
List<KingdeeOperationReportSumRowDTO> all = new ArrayList<>();
if (formId == null || formId.isEmpty()) {
log.warn("工序汇报汇总报表 FormId 为空");
return all;
}
int limit = Math.min(Math.max(limitPerPage, 1), 10000);
K3CloudApi client = new K3CloudApi();
Gson gson = new Gson();
JsonObject template = new JsonObject();
template.addProperty("FieldKeys", fieldKeys != null && !fieldKeys.isEmpty() ? fieldKeys
: "FDEPARTMENTID,FDATE,FMOBillNo,FOpOper,FWORKCENTERID,FPROCESSID,FPRODUCTNUMBER,FMATERIALNAME,FPRDFINISHQTY,FPRDQUAQTY,FPRDFAILQTY");
template.addProperty("SchemeId", "");
template.addProperty("IsVerifyBaseDataField", "true");
JsonObject model = new JsonObject();
JsonObject fProcessOrgId = new JsonObject();
fProcessOrgId.addProperty("FNumber", processOrgNumber != null && !processOrgNumber.isEmpty() ? processOrgNumber : "100");
model.add("FProcessOrgId", fProcessOrgId);
model.addProperty("FBeginDate", beginDateTime);
model.addProperty("FEndDate", endDateTime);
model.addProperty("FTimeUnit", timeUnit != null && !timeUnit.isEmpty() ? timeUnit : "B");
template.add("Model", model);
JsonArray filterString = new JsonArray();
if (moBillNo != null && !moBillNo.trim().isEmpty()) {
JsonObject f = new JsonObject();
f.addProperty("Left", "");
f.addProperty("FieldName", "FMOBillNo");
f.addProperty("Compare", "67");
f.addProperty("Value", moBillNo.trim());
f.addProperty("Right", "");
f.addProperty("Logic", 0);
filterString.add(f);
}
template.add("FilterString", filterString);
int startRow = 0;
try {
while (true) {
JsonObject page = gson.fromJson(template.toString(), JsonObject.class);
page.addProperty("StartRow", startRow);
page.addProperty("Limit", limit);
String resultJson = client.getSysReportData(formId, page.toString());
if (resultJson == null || resultJson.isEmpty()) {
log.warn("工序汇报汇总报表返回空 formId={} startRow={}", formId, startRow);
break;
}
JsonObject root = new JsonParser().parse(resultJson).getAsJsonObject();
if (!root.has("Result")) {
log.warn("工序汇报汇总报表无 Result 节点: {}", trunc(resultJson));
break;
}
JsonObject result = root.getAsJsonObject("Result");
if (result.has("ResponseStatus")) {
JsonObject rs = result.getAsJsonObject("ResponseStatus");
if (rs.has("IsSuccess") && !rs.get("IsSuccess").getAsBoolean()) {
log.error("工序汇报汇总报表业务失败: {}", trunc(resultJson));
break;
}
}
if (!result.has("Rows")) {
break;
}
JsonArray rows = result.getAsJsonArray("Rows");
if (rows == null || rows.size() == 0) {
break;
}
for (JsonElement rowElement : rows) {
if (rowElement == null || rowElement.isJsonNull() || !rowElement.isJsonArray()) {
continue;
}
JsonArray row = rowElement.getAsJsonArray();
KingdeeOperationReportSumRowDTO dto = mapOperationReportSumRow(row);
all.add(dto);
}
if (rows.size() < limit) {
break;
}
startRow += limit;
}
} catch (Exception e) {
log.error("工序汇报汇总报表调用异常 formId={}", formId, e);
}
return all;
}
private static KingdeeOperationReportSumRowDTO mapOperationReportSumRow(JsonArray row) {
KingdeeOperationReportSumRowDTO dto = new KingdeeOperationReportSumRowDTO();
dto.setDepartmentId(getStringFromJsonArray(row, 0));
dto.setReportDate(getStringFromJsonArray(row, 1));
dto.setMoBillNo(getStringFromJsonArray(row, 2));
dto.setOpOper(getStringFromJsonArray(row, 3));
dto.setWorkCenterId(getStringFromJsonArray(row, 4));
dto.setProcessId(getStringFromJsonArray(row, 5));
dto.setProductNumber(getStringFromJsonArray(row, 6));
dto.setMaterialName(getStringFromJsonArray(row, 7));
dto.setFinishQty(getBigDecimalFromJsonArray(row, 8));
dto.setQuaQty(getBigDecimalFromJsonArray(row, 9));
dto.setFailQty(getBigDecimalFromJsonArray(row, 10));
return dto;
}
private static String trunc(String s) {
if (s == null) {
return "";
}
return s.length() > 500 ? s.substring(0, 500) + "..." : s;
}
/**
* 工序委外转移报表系统报表 GetSysReportData
* ModelFProOrgIdFStartDateFFinishDateFDocumentStatus
* Result 可能含顶层 {@code IsSuccess} ResponseStatus 并存
*/
public static List<KingdeeSubcontractTransferRowDTO> getSubcontractTransferRpt(
String formId,
String fieldKeys,
String startDateTime,
String finishDateTime,
String proOrgNumber,
String documentStatus,
String productOrderCode,
int limitPerPage) {
List<KingdeeSubcontractTransferRowDTO> all = new ArrayList<>();
if (formId == null || formId.isEmpty()) {
log.warn("工序委外转移报表 FormId 为空");
return all;
}
int limit = Math.min(Math.max(limitPerPage, 1), 10000);
K3CloudApi client = new K3CloudApi();
Gson gson = new Gson();
JsonObject template = new JsonObject();
template.addProperty("FieldKeys", fieldKeys != null && !fieldKeys.isEmpty() ? fieldKeys
: "FProductNumber,FProductName,FProviderName,FProductOrderCodes,FDates,FShiftTaskName,FLeaveQty,FEnterQty,FQualifiedQty");
template.addProperty("SchemeId", "");
template.addProperty("IsVerifyBaseDataField", "true");
JsonObject model = new JsonObject();
JsonObject fProOrgId = new JsonObject();
fProOrgId.addProperty("FNumber", proOrgNumber != null && !proOrgNumber.isEmpty() ? proOrgNumber : "100");
model.add("FProOrgId", fProOrgId);
model.addProperty("FStartDate", startDateTime);
model.addProperty("FFinishDate", finishDateTime);
model.addProperty("FDocumentStatus", documentStatus != null && !documentStatus.isEmpty() ? documentStatus : "C");
template.add("Model", model);
JsonArray filterString = new JsonArray();
if (productOrderCode != null && !productOrderCode.trim().isEmpty()) {
JsonObject f = new JsonObject();
f.addProperty("Left", "");
f.addProperty("FieldName", "FProductOrderCodes");
f.addProperty("Compare", "67");
f.addProperty("Value", productOrderCode.trim());
f.addProperty("Right", "");
f.addProperty("Logic", 0);
filterString.add(f);
}
template.add("FilterString", filterString);
int startRow = 0;
try {
while (true) {
JsonObject page = gson.fromJson(template.toString(), JsonObject.class);
page.addProperty("StartRow", startRow);
page.addProperty("Limit", limit);
String resultJson = client.getSysReportData(formId, page.toString());
if (resultJson == null || resultJson.isEmpty()) {
log.warn("工序委外转移报表返回空 formId={} startRow={}", formId, startRow);
break;
}
JsonObject root = new JsonParser().parse(resultJson).getAsJsonObject();
if (!root.has("Result")) {
log.warn("工序委外转移报表无 Result: {}", trunc(resultJson));
break;
}
JsonObject result = root.getAsJsonObject("Result");
if (result.has("IsSuccess") && !result.get("IsSuccess").getAsBoolean()) {
log.error("工序委外转移报表 IsSuccess=false: {}", trunc(resultJson));
break;
}
if (result.has("ResponseStatus")) {
JsonObject rs = result.getAsJsonObject("ResponseStatus");
if (rs.has("IsSuccess") && !rs.get("IsSuccess").getAsBoolean()) {
log.error("工序委外转移报表 ResponseStatus 失败: {}", trunc(resultJson));
break;
}
}
if (!result.has("Rows")) {
break;
}
JsonArray rows = result.getAsJsonArray("Rows");
if (rows == null || rows.size() == 0) {
break;
}
for (JsonElement rowElement : rows) {
if (rowElement == null || rowElement.isJsonNull() || !rowElement.isJsonArray()) {
continue;
}
JsonArray row = rowElement.getAsJsonArray();
all.add(mapSubcontractTransferRow(row));
}
if (rows.size() < limit) {
break;
}
startRow += limit;
}
} catch (Exception e) {
log.error("工序委外转移报表调用异常 formId={}", formId, e);
}
return all;
}
private static KingdeeSubcontractTransferRowDTO mapSubcontractTransferRow(JsonArray row) {
KingdeeSubcontractTransferRowDTO dto = new KingdeeSubcontractTransferRowDTO();
dto.setProductNumber(getStringFromJsonArray(row, 0));
dto.setProductName(getStringFromJsonArray(row, 1));
dto.setProviderName(getStringFromJsonArray(row, 2));
dto.setProductOrderCodes(getStringFromJsonArray(row, 3));
dto.setDocumentDate(getStringFromJsonArray(row, 4));
dto.setShiftTaskName(getStringFromJsonArray(row, 5));
dto.setLeaveQty(getBigDecimalFromJsonArray(row, 6));
dto.setEnterQty(getBigDecimalFromJsonArray(row, 7));
dto.setQualifiedQty(getBigDecimalFromJsonArray(row, 8));
return dto;
}
/** /**
* 查询生产订单单据体 * 查询生产订单单据体
* 查询 * 查询
@ -1597,167 +1890,6 @@ public class JdUtil {
} }
} }
//新增父级物料
public static int loadChengPinMaterialPreservation(BomDetails bomDetail, Double fbWorkTime) {
K3CloudApi client = new K3CloudApi();
// 创建一个空的JsonObject
JsonObject json = new JsonObject();
// 添加IsAutoSubmitAndAudit字段
json.addProperty("IsAutoSubmitAndAudit", "true");
// 创建Model对象并加入JsonObject
JsonObject model = new JsonObject();
json.add("Model", model);
// 添加Model字段
model.addProperty("FMATERIALID", 0);
if (!(bomDetail.getDanZhong() == null)) {
model.addProperty("F_HBYT_DZ", bomDetail.getDanZhong());
}
model.addProperty("FNumber", bomDetail.getFNumber());
model.addProperty("FName", bomDetail.getFName());
// 创建FMaterialGroup对象并加入Model
JsonObject fMaterialGroup = new JsonObject();
fMaterialGroup.addProperty("FNumber", "A000106");
model.add("FMaterialGroup", fMaterialGroup);
model.addProperty("FIsHandleReserve", true);
// 创建SubHeadEntity对象并加入Model
JsonObject subHeadEntity = new JsonObject();
model.add("SubHeadEntity", subHeadEntity);
subHeadEntity.addProperty("FErpClsID", "2");
subHeadEntity.addProperty("FFeatureItem", "1");
// 创建FCategoryID对象并加入SubHeadEntity
JsonObject fCategoryID = new JsonObject();
fCategoryID.addProperty("FNumber", "CHLB05_SYS");// 产成品
subHeadEntity.add("FCategoryID", fCategoryID);
// 创建FTaxRateId对象并加入SubHeadEntity
JsonObject fTaxRateId = new JsonObject();
fTaxRateId.addProperty("FNUMBER", "SL02_SYS");
subHeadEntity.add("FTaxRateId", fTaxRateId);
// 创建FBaseUnitId对象并加入SubHeadEntity
JsonObject fBaseUnitId = new JsonObject();
fBaseUnitId.addProperty("FNumber", "008");
subHeadEntity.add("FBaseUnitId", fBaseUnitId);
subHeadEntity.addProperty("FIsPurchase", true);
subHeadEntity.addProperty("FIsInventory", true);
// 成品不允许委外
// subHeadEntity.addProperty("FIsSubContract", true);
subHeadEntity.addProperty("FIsSale", true);
subHeadEntity.addProperty("FIsProduce", true);
// 创建SubHeadEntity1对象并加入Model
JsonObject subHeadEntity1 = new JsonObject();
model.add("SubHeadEntity1", subHeadEntity1);
JsonObject fStoreUnitId = new JsonObject();
fStoreUnitId.addProperty("FNumber", "008");
subHeadEntity1.add("FStoreUnitID", fStoreUnitId);
subHeadEntity1.addProperty("FUnitConvertDir", "1");
// 创建FStockId对象并加入SubHeadEntity1
JsonObject fStockId = new JsonObject();
// 判断是产成品还是企标
String cangKu = "CK011";
fStockId.addProperty("FNumber", cangKu);
subHeadEntity1.add("FStockId", fStockId);
// 创建FCurrencyId对象并加入SubHeadEntity1
JsonObject fCurrencyId = new JsonObject();
fCurrencyId.addProperty("FNumber", "PRE001");
subHeadEntity1.add("FCurrencyId", fCurrencyId);
subHeadEntity1.addProperty("FIsSNPRDTracy", false);
subHeadEntity1.addProperty("FSNManageType", "1");
subHeadEntity1.addProperty("FSNGenerateTime", "1");
subHeadEntity1.addProperty("FBoxStandardQty", 0.0);
// 创建FPurchaseUnitId对象并加入SubHeadEntity1
JsonObject fPurchaseUnitId = new JsonObject();
fPurchaseUnitId.addProperty("FNumber", "008");
subHeadEntity1.add("FPurchaseUnitId", fPurchaseUnitId);
// 创建SubHeadEntity3对象并加入Model
JsonObject subHeadEntity3 = new JsonObject();
model.add("SubHeadEntity3", subHeadEntity3);
// 创建FPurchasePriceUnitId对象并加入SubHeadEntity3
JsonObject fPurchasePriceUnitId = new JsonObject();
fPurchasePriceUnitId.addProperty("FNumber", "008");
subHeadEntity3.add("FPurchasePriceUnitId", fPurchasePriceUnitId);
subHeadEntity3.addProperty("FIsQuota", false);
subHeadEntity3.addProperty("FQuotaType", "1");
// 创建SubHeadEntity6对象并加入Model 检验项
JsonObject subHeadEntity6 = new JsonObject();
model.add("SubHeadEntity6", subHeadEntity6);
subHeadEntity6.addProperty("FCheckProduct", true);
subHeadEntity6.addProperty("FCheckReturn", true);
// 创建SubHeadEntity5对象并加入Model
JsonObject subHeadEntity5 = new JsonObject();
model.add("SubHeadEntity5", subHeadEntity5);
// 创建FProduceUnitId对象并加入SubHeadEntity5
JsonObject fProduceUnitId = new JsonObject();
fProduceUnitId.addProperty("FNumber", "008");
subHeadEntity5.add("FProduceUnitId", fProduceUnitId);
// 创建FProduceBillType对象并加入SubHeadEntity5
JsonObject fProduceBillType = new JsonObject();
fProduceBillType.addProperty("FNUMBER", "SCDD05_SYS");
subHeadEntity5.add("FProduceBillType", fProduceBillType);
// 创建FBOMUnitId对象并加入SubHeadEntity5
JsonObject fBOMUnitId = new JsonObject();
fBOMUnitId.addProperty("FNumber", "008");
subHeadEntity5.add("FBOMUnitId", fBOMUnitId);
subHeadEntity5.addProperty("FIsMainPrd", true);
subHeadEntity5.addProperty("FIssueType", "1");
// 创建FPickStockId对象并加入SubHeadEntity5
JsonObject fPickStockId = new JsonObject();
fPickStockId.addProperty("FNumber", "CK012");
subHeadEntity1.add("FPickStockId", fPickStockId);
subHeadEntity5.addProperty("FOverControlMode", "1");
subHeadEntity5.addProperty("FStdLaborProcessTime", fbWorkTime);
subHeadEntity5.addProperty("FStandHourUnitId", "60");
subHeadEntity5.addProperty("FBackFlushType", "1");
String jsonData = json.toString();
System.out.println(jsonData);
try {
// 业务对象标识
String formId = "BD_MATERIAL";
// 调用接口
String resultJson = client.save(formId, jsonData);
// 用于记录结果
Gson gson = new Gson();
// 对返回结果进行解析和校验
RepoRet repoRet = gson.fromJson(resultJson, RepoRet.class);
if (repoRet.getResult().getResponseStatus().isIsSuccess()) {
log.debug("接口返回结果: %s%n" + gson.toJson(repoRet.getResult()));
return 1;
} else {
MessageUtil.fail("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
log.error("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
return 0;
}
} catch (Exception e) {
MessageUtil.fail(e.getMessage());
return 0;
}
}
//查询物料 作用于更新到物料的 仓位 是否启用VMI //查询物料 作用于更新到物料的 仓位 是否启用VMI
public static List<JdEntryVmi> getEntryID2(String materialCode) { public static List<JdEntryVmi> getEntryID2(String materialCode) {
@ -3671,7 +3803,7 @@ public class JdUtil {
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
json.addProperty("FormId", "SFC_OperationPlanning"); json.addProperty("FormId", "SFC_OperationPlanning");
json.addProperty("FieldKeys", json.addProperty("FieldKeys",
"FID,FENTITY_FEntryID,FSubEntity_FDetailID,F_HBYT_SCLH,FMONumber,FProductId.FNumber,FProductName,FProSpecification,FBillNo,FOperNumber," + "FID,FENTITY_FEntryID,FSubEntity_FDetailID,F_HBYT_SCLH,F_HBYT_XMH,FMONumber,FProductId.FNumber,FProductName,FProSpecification,FBillNo,FOperNumber," +
"FOperQty,FQualifiedQty,FTransOutQty,FUnqualifiedQty,FWastageQty,FTransInQty,FDepartmentId.FName,FWorkCenterId.FName," + "FOperQty,FQualifiedQty,FTransOutQty,FUnqualifiedQty,FWastageQty,FTransInQty,FDepartmentId.FName,FWorkCenterId.FName," +
"FProcessId.FName,F_HBYT_ZYRY,FOperStatus,FSeqPlanStartTime,FSeqPlanFinishTime,FOperPlanStartTime,FOperPlanFinishTime," + "FProcessId.FName,F_HBYT_ZYRY,FOperStatus,FSeqPlanStartTime,FSeqPlanFinishTime,FOperPlanStartTime,FOperPlanFinishTime," +
"FOptCtrlCodeId.FName,FKeyOper,FActivity1Name,FOperDescription,FPlanStartTime,FPlanFinishTime,F_HBYT_SB"); "FOptCtrlCodeId.FName,FKeyOper,FActivity1Name,FOperDescription,FPlanStartTime,FPlanFinishTime,F_HBYT_SB");
@ -3688,7 +3820,7 @@ public class JdUtil {
JsonObject filterObject1 = new JsonObject(); JsonObject filterObject1 = new JsonObject();
filterObject1.addProperty("FieldName", "FPlanFinishTime"); filterObject1.addProperty("FieldName", "FPlanFinishTime");
filterObject1.addProperty("Compare", "39"); filterObject1.addProperty("Compare", "39");
filterObject1.addProperty("Value", "2026-04-15 00:00:00"); filterObject1.addProperty("Value", "2026-04-16 00:00:00");
filterObject1.addProperty("Left", ""); filterObject1.addProperty("Left", "");
filterObject1.addProperty("Right", ""); filterObject1.addProperty("Right", "");
filterObject1.addProperty("Logic", 0); filterObject1.addProperty("Logic", 0);
@ -3810,6 +3942,7 @@ public class JdUtil {
OperationPlanResultDTO resultDTO = new OperationPlanResultDTO(); OperationPlanResultDTO resultDTO = new OperationPlanResultDTO();
resultDTO.setMoNumber(moNumber); resultDTO.setMoNumber(moNumber);
resultDTO.setBatchNo(firstItem.getFHbytSclh()); // 生产令号 resultDTO.setBatchNo(firstItem.getFHbytSclh()); // 生产令号
resultDTO.setProjectNo(firstItem.getFHbytXmh());
resultDTO.setProductCode(firstItem.getFProductIdFNumber()); resultDTO.setProductCode(firstItem.getFProductIdFNumber());
resultDTO.setProductName(firstItem.getFProductName()); resultDTO.setProductName(firstItem.getFProductName());
resultDTO.setBillNo(firstItem.getFBillNo()); resultDTO.setBillNo(firstItem.getFBillNo());

View File

@ -85,7 +85,10 @@ public class JsonConverter {
processRouteDTO.getWorkCenter().equals("铆焊工段") || processRouteDTO.getWorkCenter().equals("铆焊工段") ||
processRouteDTO.getWorkCenter().equals("委外中心")) { processRouteDTO.getWorkCenter().equals("委外中心")) {
department.setFNUMBER("006"); department.setFNUMBER("006");
} else { } else if (processRouteDTO.getWorkCenter().equals("下料工段")||
processRouteDTO.getWorkCenter().equals("焊接工段")){
department.setFNUMBER("018");
}else{
department.setFNUMBER("007"); department.setFNUMBER("007");
} }
subEntity.setFDepartmentId(department); subEntity.setFDepartmentId(department);

View File

@ -6,9 +6,13 @@ import com.ruoyi.system.domain.vo.ImMaterialVo;
import com.ruoyi.system.domain.bo.ImMaterialBo; import com.ruoyi.system.domain.bo.ImMaterialBo;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.domain.dto.buildcapacity.BuildCapacityDTO;
import com.ruoyi.system.domain.dto.productionorder.ProductionOrderMaterialsDTO;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 物料检索Service接口 * 物料检索Service接口
@ -66,4 +70,16 @@ public interface IImMaterialService {
Boolean insertJDMaterial(List<ImMaterial> materialList); Boolean insertJDMaterial(List<ImMaterial> materialList);
Boolean updateByFMid(List<ImMaterial> imMaterials); Boolean updateByFMid(List<ImMaterial> imMaterials);
BuildCapacityDTO queryBuildCapacity(String productionOrderNo, String productCode, String productName);
List<BuildCapacityDTO> queryBuildCapacityByProductionOrderNo(String productionOrderNo);
ProductionOrderMaterialsDTO queryProductionOrderMaterials(String productionOrderNo, String productCode);
/**
* 分页扫描 im_material按物料编码批量解析单重production_order process_route并写回
* 仅解析到单重且与库中值不同时更新 single_weightmodify_dateupdate_time
*/
R<Map<String, Object>> refreshImMaterialSingleWeightFromOrders();
} }

View File

@ -99,6 +99,20 @@ public interface IProcessOrderProService {
*/ */
void pushDingTalkCard(Long id); void pushDingTalkCard(Long id);
/**
* 生成工艺计划表 Excel出图场景模板并发送至钉钉机器人配置的接收人
*
* @param id 项目令号主键 ID与列表/详情中的 id 一致
*/
R<Void> sendProcessRoutePlanExcel(Long id);
/**
* 查询工艺计划表钉钉已成功发送次数供前端按钮等展示
*
* @param id 项目令号主键 ID
*/
R<Integer> getRoutePlanSendCount(Long id);
/** /**
* 刷新并计算项目延期状态 * 刷新并计算项目延期状态
*/ */

View File

@ -1,65 +1,89 @@
package com.ruoyi.system.service; package com.ruoyi.system.service;
import com.ruoyi.system.domain.ProductionOrder; import com.ruoyi.system.domain.ProductionOrder;
import com.ruoyi.system.domain.bo.FigureSaveBo; import com.ruoyi.system.domain.bo.FigureSaveBo;
import com.ruoyi.system.domain.bo.ProcessOrderProBo; import com.ruoyi.system.domain.bo.ProcessOrderProBo;
import com.ruoyi.system.domain.vo.ProductionOrderVo; import com.ruoyi.system.domain.dto.productionorder.ProductionOrderWeightExportDTO;
import com.ruoyi.system.domain.bo.ProductionOrderBo; import com.ruoyi.system.domain.dto.productionorder.ProductionOrderWeightImportDTO;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.system.domain.vo.ProductionOrderVo;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.system.domain.bo.ProductionOrderBo;
import com.ruoyi.common.core.page.TableDataInfo;
import java.util.Collection; import com.ruoyi.common.core.domain.PageQuery;
import java.util.List;
import java.util.Collection;
/** import java.util.List;
* 生产订单Service接口 import java.util.Map;
*
* @author 田志阳 /**
* @date 2024-10-20 * 生产订单Service接口
*/ *
public interface IProductionOrderService { * @author 田志阳
* @date 2024-10-20
/** */
* 查询生产订单 public interface IProductionOrderService {
*/
ProductionOrderVo queryById(Long id); /**
* 查询生产订单
/** */
* 查询生产订单列表 ProductionOrderVo queryById(Long id);
*/
TableDataInfo<ProductionOrderVo> queryPageList(ProductionOrderBo bo, PageQuery pageQuery); /**
* 查询生产订单列表
/** */
* 查询生产订单列表 TableDataInfo<ProductionOrderVo> queryPageList(ProductionOrderBo bo, PageQuery pageQuery);
*/
List<ProductionOrderVo> queryList(ProductionOrderBo bo); /**
* 查询生产订单列表
/** */
* 新增生产订单 List<ProductionOrderVo> queryList(ProductionOrderBo bo);
*/
Boolean insertByBo(ProductionOrderBo bo); /**
* 新增生产订单
/** */
* 修改生产订单 Boolean insertByBo(ProductionOrderBo bo);
*/
Boolean updateByBo(ProductionOrderBo bo); /**
* 修改生产订单
/** */
* 校验并批量删除生产订单信息 Boolean updateByBo(ProductionOrderBo bo);
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); /**
* 校验并批量删除生产订单信息
void loadProductionOrder(List<ProductionOrderVo> productionOrderVoList); */
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
List<ProductionOrder> getGroupWeld(ProductionOrderVo productionOrderVo);
void loadProductionOrder(List<ProductionOrderVo> productionOrderVoList);
List<ProductionOrder> getGroupWeldSubset(ProductionOrderVo productionOrderVo);
List<ProductionOrder> getGroupWeld(ProductionOrderVo productionOrderVo);
Boolean executDrawing(ProcessOrderProBo orderPro);
List<ProductionOrder> getGroupWeldSubset(ProductionOrderVo productionOrderVo);
List<ProductionOrder> selectByProCode(String productionOrderNo);
/** Boolean executDrawing(ProcessOrderProBo orderPro);
* 是否属于外购件
*/ List<ProductionOrder> selectByProCode(String productionOrderNo);
Boolean isPurchas(String proCode,String materialCode); /**
} * 是否属于外购件
*/
Boolean isPurchas(String proCode,String materialCode);
/**
* 查询生产令号列表去重用于下拉选择
*/
List<String> selectProductionOrderNoOptions(String keyword, Integer limit);
/**
* 通过物料编码匹配系统单重未匹配数据会跳过
*/
List<ProductionOrderWeightExportDTO> matchSingleWeightByMaterialCode(List<ProductionOrderWeightImportDTO> rows);
/**
* 按物料图号/编码解析单重 production_order.drawing_no最新一条且 single_weight 非空 process_route.material_code disc_weight
* 未查到返回 null
*/
Double resolveSingleWeightByMaterialCode(String materialCode);
/**
* 批量解析单重内部 IN 分批仅返回能查到单重的编码查不到的编码不出现在 Map
*/
Map<String, Double> resolveSingleWeightByMaterialCodes(Collection<String> materialCodes);
}

View File

@ -1,16 +1,26 @@
package com.ruoyi.system.service; package com.ruoyi.system.service;
import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.dynamic.datasource.annotation.DS;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.bo.KingdeeOrderTraceQueryBo;
import com.ruoyi.system.domain.bo.KingdeeWorkCenterDataBo; import com.ruoyi.system.domain.bo.KingdeeWorkCenterDataBo;
import com.ruoyi.system.domain.vo.KingdeeMoTraceVo;
import com.ruoyi.system.domain.vo.KingdeeOrderTraceVo;
import com.ruoyi.system.domain.vo.KingdeePoTraceVo;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
@Service @Service
public class MssqlQueryService { public class MssqlQueryService {
@ -26,6 +36,208 @@ public class MssqlQueryService {
return jdbcTemplate.queryForList(sql); return jdbcTemplate.queryForList(sql);
} }
/**
* 按生产令号采购单号生产订单号产品编码均可选至少填一项查询金蝶采购与生产追溯数据
*/
@DS("sqlserver")
public KingdeeOrderTraceVo queryOrderTrace(KingdeeOrderTraceQueryBo bo) {
if (bo == null) {
bo = new KingdeeOrderTraceQueryBo();
}
List<String> sclhList = normalizeSclhList(bo.getSclh());
String purchaseBillNo = blankToNull(bo.getPurchaseBillNo());
String moBillNo = blankToNull(bo.getMoBillNo());
String materialNumber = blankToNull(bo.getMaterialNumber());
if (sclhList.isEmpty() && purchaseBillNo == null && moBillNo == null && materialNumber == null) {
throw new ServiceException("请至少填写生产令号、采购订单、生产订单或产品编码之一");
}
KingdeeOrderTraceVo vo = new KingdeeOrderTraceVo();
vo.setPurchaseList(queryPurchaseTrace(sclhList, purchaseBillNo, materialNumber));
vo.setProductionList(queryProductionTrace(sclhList, moBillNo, materialNumber));
return vo;
}
private List<KingdeePoTraceVo> queryPurchaseTrace(List<String> sclhList, String purchaseBillNo, String materialNumber) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
sql.append("PO.F_UCHN_TEXT2 AS sclh, PO.FBILLNO AS purchaseBillNo, PO.FDATE AS purchaseDate, ");
sql.append("MAT.FNUMBER AS materialNumber, MAT_L.FNAME AS materialName, PO_E.FQty AS purchaseQty, ");
sql.append("INSTOCK.FBILLNO AS instockBillNo, INSTOCK.FDATE AS instockDate, INSTOCK_E.FREALQTY AS instockQty ");
sql.append("FROM dbo.T_PUR_POORDER PO ");
sql.append("INNER JOIN dbo.T_PUR_POORDERENTRY PO_E ON PO.FID = PO_E.FID ");
sql.append("LEFT JOIN dbo.T_BD_MATERIAL MAT ON PO_E.FMATERIALID = MAT.FMATERIALID ");
sql.append("LEFT JOIN dbo.T_BD_MATERIAL_L MAT_L ON MAT.FMATERIALID = MAT_L.FMATERIALID AND MAT_L.FLOCALEID = 2052 ");
sql.append("LEFT JOIN dbo.T_STK_INSTOCKENTRY INSTOCK_E ON PO_E.FENTRYID = INSTOCK_E.FPOORDERENTRYID ");
sql.append("LEFT JOIN dbo.T_STK_INSTOCK INSTOCK ON INSTOCK_E.FID = INSTOCK.FID ");
sql.append("WHERE 1 = 1 ");
List<Object> args = new ArrayList<>();
appendIn(sql, args, "PO.F_UCHN_TEXT2", sclhList);
appendEq(sql, args, "PO.FBILLNO", purchaseBillNo);
appendEq(sql, args, "MAT.FNUMBER", materialNumber);
sql.append("ORDER BY PO.FDATE DESC");
return mapPoRows(jdbcTemplate.queryForList(sql.toString(), args.toArray()));
}
private List<KingdeeMoTraceVo> queryProductionTrace(List<String> sclhList, String moBillNo, String materialNumber) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
sql.append("MO.F_HBYT_SCLH AS sclh, MO.FBILLNO AS moBillNo, MAT.FNUMBER AS materialNumber, MAT_L.FNAME AS materialName, ");
sql.append("MO_E.FQty AS qty, MO.FDATE AS orderDate, OP.FBILLNO AS operPlanningBillNo, OP.FID AS operPlanningId, ");
sql.append("MO_E.FPlanStartDate AS planStartDate, MO_E.FPlanFinishDate AS planFinishDate, ");
sql.append("INSTOCK.FBILLNO AS instockBillNo, INSTOCK.FDATE AS instockDate, INSTOCK_E.FREALQTY AS instockQty ");
sql.append("FROM dbo.T_PRD_MO MO ");
sql.append("INNER JOIN dbo.T_PRD_MOENTRY MO_E ON MO.FID = MO_E.FID ");
sql.append("LEFT JOIN dbo.T_BD_MATERIAL MAT ON MO_E.FMATERIALID = MAT.FMATERIALID ");
sql.append("LEFT JOIN dbo.T_BD_MATERIAL_L MAT_L ON MAT.FMATERIALID = MAT_L.FMATERIALID AND MAT_L.FLOCALEID = 2052 ");
sql.append("LEFT JOIN dbo.T_SFC_OPERPLANNING OP ON MO.FID = OP.FMOID AND MO_E.FENTRYID = OP.FMOENTRYID ");
sql.append("LEFT JOIN dbo.T_PRD_INSTOCKENTRY INSTOCK_E ON MO.FID = INSTOCK_E.FMOID AND MO_E.FENTRYID = INSTOCK_E.FMOENTRYID ");
sql.append("LEFT JOIN dbo.T_PRD_INSTOCK INSTOCK ON INSTOCK_E.FID = INSTOCK.FID ");
sql.append("WHERE 1 = 1 ");
List<Object> args = new ArrayList<>();
appendIn(sql, args, "MO.F_HBYT_SCLH", sclhList);
appendEq(sql, args, "MO.FBILLNO", moBillNo);
appendEq(sql, args, "MAT.FNUMBER", materialNumber);
sql.append("ORDER BY MO.FDATE DESC");
return mapMoRows(jdbcTemplate.queryForList(sql.toString(), args.toArray()));
}
private static void appendEq(StringBuilder sql, List<Object> args, String column, String value) {
if (value != null) {
sql.append(" AND ").append(column).append(" = ? ");
args.add(value);
}
}
/**
* 生产令号 IN 查询列表为空则不追加条件
*/
private static void appendIn(StringBuilder sql, List<Object> args, String column, List<String> values) {
if (values == null || values.isEmpty()) {
return;
}
sql.append(" AND ").append(column).append(" IN (");
for (int i = 0; i < values.size(); i++) {
if (i > 0) {
sql.append(", ");
}
sql.append("?");
args.add(values.get(i));
}
sql.append(") ");
}
/**
* 合并多个传参及逗号分隔项去重保序
*/
private static List<String> normalizeSclhList(List<String> raw) {
if (raw == null || raw.isEmpty()) {
return new ArrayList<>();
}
Set<String> seen = new LinkedHashSet<>();
for (String s : raw) {
if (StringUtils.isBlank(s)) {
continue;
}
String t = s.trim();
if (t.contains(",")) {
for (String part : t.split(",")) {
String p = part.trim();
if (StringUtils.isNotBlank(p)) {
seen.add(p);
}
}
} else {
seen.add(t);
}
}
return new ArrayList<>(seen);
}
private static String blankToNull(String s) {
if (StringUtils.isBlank(s)) {
return null;
}
return s.trim();
}
private List<KingdeePoTraceVo> mapPoRows(List<Map<String, Object>> rows) {
List<KingdeePoTraceVo> list = new ArrayList<>();
for (Map<String, Object> row : rows) {
KingdeePoTraceVo v = new KingdeePoTraceVo();
v.setSclh(getString(row, "sclh"));
v.setPurchaseBillNo(getString(row, "purchaseBillNo"));
v.setPurchaseDate(toUtilDate(getCell(row, "purchaseDate")));
v.setMaterialNumber(getString(row, "materialNumber"));
v.setMaterialName(getString(row, "materialName"));
v.setPurchaseQty(getBigDecimal(row, "purchaseQty"));
v.setInstockBillNo(getString(row, "instockBillNo"));
v.setInstockDate(toUtilDate(getCell(row, "instockDate")));
v.setInstockQty(getBigDecimal(row, "instockQty"));
list.add(v);
}
return list;
}
private List<KingdeeMoTraceVo> mapMoRows(List<Map<String, Object>> rows) {
List<KingdeeMoTraceVo> list = new ArrayList<>();
for (Map<String, Object> row : rows) {
KingdeeMoTraceVo v = new KingdeeMoTraceVo();
v.setSclh(getString(row, "sclh"));
v.setMoBillNo(getString(row, "moBillNo"));
v.setMaterialNumber(getString(row, "materialNumber"));
v.setMaterialName(getString(row, "materialName"));
v.setQty(getBigDecimal(row, "qty"));
v.setOrderDate(toUtilDate(getCell(row, "orderDate")));
v.setOperPlanningBillNo(getString(row, "operPlanningBillNo"));
v.setOperPlanningId(getLong(row, "operPlanningId"));
v.setPlanStartDate(toUtilDate(getCell(row, "planStartDate")));
v.setPlanFinishDate(toUtilDate(getCell(row, "planFinishDate")));
v.setInstockBillNo(getString(row, "instockBillNo"));
v.setInstockDate(toUtilDate(getCell(row, "instockDate")));
v.setInstockQty(getBigDecimal(row, "instockQty"));
list.add(v);
}
return list;
}
private Object getCell(Map<String, Object> row, String key) {
if (row.containsKey(key)) {
return row.get(key);
}
for (Map.Entry<String, Object> e : row.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) {
return e.getValue();
}
}
return null;
}
private Date toUtilDate(Object val) {
if (val == null) {
return null;
}
if (val instanceof Date) {
return (Date) val;
}
if (val instanceof Timestamp) {
return new Date(((Timestamp) val).getTime());
}
return null;
}
private Long getLong(Map<String, Object> row, String key) {
Object val = getCell(row, key);
if (val == null) {
return null;
}
try {
return Long.parseLong(val.toString());
} catch (NumberFormatException e) {
return null;
}
}
/** /**
* 根据物料编码查询是否是VMI业务 * 根据物料编码查询是否是VMI业务
* @param materialNumber 物料编码 * @param materialNumber 物料编码
@ -147,13 +359,13 @@ public class MssqlQueryService {
} }
private String getString(Map<String, Object> row, String key) { private String getString(Map<String, Object> row, String key) {
Object val = row.get(key); Object val = getCell(row, key);
return val != null ? val.toString() : ""; return val != null ? val.toString() : "";
} }
private BigDecimal getBigDecimal(Map<String, Object> row, String key) { private BigDecimal getBigDecimal(Map<String, Object> row, String key) {
Object val = row.get(key); Object val = getCell(row, key);
if (val == null) return BigDecimal.ZERO; if (val == null) return BigDecimal.ZERO;
try { try {
return new BigDecimal(val.toString()).setScale(2, RoundingMode.HALF_UP); return new BigDecimal(val.toString()).setScale(2, RoundingMode.HALF_UP);

View File

@ -16,6 +16,7 @@ import com.ruoyi.common.core.domain.model.XcxLoginUser;
import com.ruoyi.common.enums.DeviceType; import com.ruoyi.common.enums.DeviceType;
import com.ruoyi.common.enums.LoginType; import com.ruoyi.common.enums.LoginType;
import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException; import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserException; import com.ruoyi.common.exception.user.UserException;
@ -34,6 +35,8 @@ import org.springframework.stereotype.Service;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@ -56,6 +59,15 @@ public class SysLoginService {
@Value("${user.password.lockTime}") @Value("${user.password.lockTime}")
private Integer lockTime; private Integer lockTime;
@Value("${plm.auth.app-key:}")
private String plmAuthAppKey;
@Value("${plm.auth.app-secret:}")
private String plmAuthAppSecret;
@Value("${plm.auth.username:}")
private String plmAuthUsername;
/** /**
* 登录验证 * 登录验证
* *
@ -135,6 +147,27 @@ public class SysLoginService {
recordLoginInfo(user.getUserId(), user.getUserName()); recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue(); return StpUtil.getTokenValue();
} }
public String plmToken(String appKey, String appSecret) {
if (StringUtils.isBlank(plmAuthAppKey) || StringUtils.isBlank(plmAuthAppSecret)) {
throw new ServiceException("PLM鉴权未配置");
}
if (!safeEquals(plmAuthAppKey, StringUtils.trimToEmpty(appKey))
|| !safeEquals(plmAuthAppSecret, StringUtils.trimToEmpty(appSecret))) {
recordLogininfor("plm", Constants.LOGIN_FAIL, "plm auth failed");
throw new ServiceException("PLM鉴权失败");
}
if (StringUtils.isBlank(plmAuthUsername)) {
throw new ServiceException("PLM鉴权用户未配置");
}
SysUser user = loadUserByUsername(plmAuthUsername);
LoginUser loginUser = buildLoginUser(user);
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, "plm token issued");
recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue();
}
/** /**
* 退出登录 * 退出登录
@ -330,4 +363,11 @@ public class SysLoginService {
// 登录成功 清空错误次数 // 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey); RedisUtils.deleteObject(errorKey);
} }
private static boolean safeEquals(String a, String b) {
if (a == null || b == null) {
return false;
}
return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
}
} }

View File

@ -218,7 +218,7 @@ public class BomDetailsServiceImpl implements IBomDetailsService {
)); ));
for (BomDetails bomDetail : bomDetails) { for (BomDetails bomDetail : bomDetails) {
String key = bomDetail.getPartNumber() + "-" + bomDetail.getName(); String key = bomDetail.getPartNumber() + "-" + bomDetail.getName()+System.currentTimeMillis();
MaterialBom matchedMaterialBom = materialBomMap.get(key); MaterialBom matchedMaterialBom = materialBomMap.get(key);
if (matchedMaterialBom != null) { if (matchedMaterialBom != null) {
if (matchedMaterialBom.getUnit().equals("")) { if (matchedMaterialBom.getUnit().equals("")) {

View File

@ -15,23 +15,31 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.kingdee.bos.webapi.sdk.K3CloudApi; import com.kingdee.bos.webapi.sdk.K3CloudApi;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.controller.ImMaterialController; import com.ruoyi.system.controller.ImMaterialController;
import com.ruoyi.system.domain.ImMaterial; import com.ruoyi.system.domain.ImMaterial;
import com.ruoyi.system.domain.ProductionOrder;
import com.ruoyi.system.domain.bo.ImMaterialBo; import com.ruoyi.system.domain.bo.ImMaterialBo;
import com.ruoyi.system.domain.dto.JDInventoryDTO; import com.ruoyi.system.domain.dto.JDInventoryDTO;
import com.ruoyi.system.domain.dto.JDProductionDTO; import com.ruoyi.system.domain.dto.JDProductionDTO;
import com.ruoyi.system.domain.dto.ProMoDTO; import com.ruoyi.system.domain.dto.ProMoDTO;
import com.ruoyi.system.domain.dto.PurchaseOrderDTO; import com.ruoyi.system.domain.dto.PurchaseOrderDTO;
import com.ruoyi.system.domain.dto.buildcapacity.BuildCapacityDTO;
import com.ruoyi.system.domain.dto.buildcapacity.CalcInfo;
import com.ruoyi.system.domain.dto.buildcapacity.MaterialItem;
import com.ruoyi.system.domain.dto.buildcapacity.StockInfo;
import com.ruoyi.system.domain.dto.buildcapacity.StockStatus;
import com.ruoyi.system.domain.dto.productionorder.ProductionOrderMaterialItemDTO;
import com.ruoyi.system.domain.dto.productionorder.ProductionOrderMaterialsDTO;
import com.ruoyi.system.domain.vo.ImMaterialVo; import com.ruoyi.system.domain.vo.ImMaterialVo;
import com.ruoyi.system.mapper.ImMaterialMapper; import com.ruoyi.system.mapper.ImMaterialMapper;
import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.runner.JdUtil;
import com.ruoyi.system.runner.MaterialProperties; import com.ruoyi.system.runner.MaterialProperties;
import com.ruoyi.system.service.IImMaterialService; import com.ruoyi.system.service.IImMaterialService;
import com.ruoyi.system.service.IProductionOrderService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.math.NumberUtils;
import org.redisson.api.RLock; import org.redisson.api.RLock;
import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -42,6 +50,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
@ -60,6 +69,7 @@ import static com.ruoyi.common.core.mapper.BaseMapperPlus.log;
public class ImMaterialServiceImpl implements IImMaterialService { public class ImMaterialServiceImpl implements IImMaterialService {
private final ImMaterialMapper baseMapper; private final ImMaterialMapper baseMapper;
private final IProductionOrderService productionOrderService;
private static final Logger logger = LoggerFactory.getLogger(ImMaterialController.class); private static final Logger logger = LoggerFactory.getLogger(ImMaterialController.class);
@Autowired @Autowired
@ -878,4 +888,300 @@ public class ImMaterialServiceImpl implements IImMaterialService {
} }
@Override
public BuildCapacityDTO queryBuildCapacity(String productionOrderNo, String productCode, String productName) {
// 齐套查询不查金蝶 BOM仅使用本地 production_order 中的物料行作为清单来源
BuildCapacityDTO result = new BuildCapacityDTO();
result.setProductCode(productCode);
result.setProductName(productName);
result.setSelfMadeMaterials(new ArrayList<>());
result.setPurchaseMaterials(new ArrayList<>());
result.setMaxBuildQty(0);
if (StringUtils.isBlank(productionOrderNo) || StringUtils.isBlank(productCode)) {
return result;
}
String proNo = productionOrderNo.trim();
String proCode = productCode.trim();
// 生产令号下的所有行包含成品行/子项行
List<ProductionOrder> productionOrderList = productionOrderService.selectByProCode(proNo);
if (CollectionUtils.isEmpty(productionOrderList)) {
return result;
}
if (StringUtils.isBlank(result.getProductName())) {
// 未传 productName 从本地数据中回填成品名称
result.setProductName(productionOrderList.stream()
.filter(p -> p != null && StringUtils.isNotBlank(p.getDrawingNo()) && proCode.equals(p.getDrawingNo().trim()))
.map(ProductionOrder::getDrawingName)
.filter(StringUtils::isNotBlank)
.findFirst()
.orElse(null));
}
// 逐行计算productionOrderList 有多少条就计算多少条不做父子级过滤/不做按编码合并
List<MaterialItem> allItems = new ArrayList<>();
int minCanSupport = Integer.MAX_VALUE;
for (ProductionOrder p : productionOrderList) {
if (p == null || StringUtils.isBlank(p.getDrawingNo())) {
continue;
}
String materialCode = p.getDrawingNo().trim();
int requiredQty = normalizeQtyToInt(p.getQuantity());
if (requiredQty <= 0) {
requiredQty = 1;
}
int onHandQty = normalizeToInt(Optional.ofNullable(JdUtil.getKuCun(materialCode))
.map(list -> list.stream().mapToDouble(JDInventoryDTO::getFBaseQty).sum())
.orElse(0.0));
int moNotPickQty = normalizeToInt(Optional.ofNullable(JdUtil.getWeiLingliao(materialCode))
.map(list -> list.stream().mapToDouble(JDProductionDTO::getFNoPickedQty).sum())
.orElse(0.0));
int moNotInQty = normalizeToInt(Optional.ofNullable(JdUtil.getProMoList(materialCode))
.map(list -> list.stream().mapToDouble(ProMoDTO::getFQty).sum())
.orElse(0.0));
int poNotInQty = normalizeToInt(Optional.ofNullable(JdUtil.getPurchaseOrderList(materialCode))
.map(list -> list.stream().mapToDouble(PurchaseOrderDTO::getFRemainStockINQty).sum())
.orElse(0.0));
int availableQty = onHandQty - moNotPickQty;
int finalAvailableQty = availableQty + poNotInQty + moNotInQty;
int canSupportQty = Math.max(0, finalAvailableQty / requiredQty);
StockInfo stock = new StockInfo();
stock.setOnHandQty(onHandQty);
stock.setMoNotPickQty(moNotPickQty);
stock.setMoNotInQty(moNotInQty);
stock.setPoNotInQty(poNotInQty);
CalcInfo calc = new CalcInfo();
calc.setAvailableQty(availableQty);
calc.setFinalAvailableQty(finalAvailableQty);
calc.setCanSupportQty(canSupportQty);
MaterialItem item = new MaterialItem();
item.setMaterialCode(materialCode);
item.setMaterialName(p.getDrawingName());
item.setRequiredQty(requiredQty);
item.setMaterial(p.getMaterial());
item.setSingleWeight(p.getSingleWeight());
item.setTotalWeight(p.getTotalWeight());
item.setRemark(p.getRemark());
item.setBatchQuantity(p.getBatchQuantity());
item.setStock(stock);
item.setCalc(calc);
item.setStatus(finalAvailableQty >= requiredQty ? StockStatus.OK : StockStatus.SHORTAGE);
allItems.add(item);
minCanSupport = Math.min(minCanSupport, canSupportQty);
}
if (minCanSupport == Integer.MAX_VALUE) {
minCanSupport = 0;
}
List<MaterialItem> purchaseMaterials = new ArrayList<>();
List<MaterialItem> selfMadeMaterials = new ArrayList<>();
for (MaterialItem item : allItems) {
String code = item.getMaterialCode();
if (StringUtils.isNotBlank(code) && code.startsWith("009")) {
purchaseMaterials.add(item);
} else {
selfMadeMaterials.add(item);
}
}
result.setPurchaseMaterials(purchaseMaterials);
result.setSelfMadeMaterials(selfMadeMaterials);
result.setMaxBuildQty(minCanSupport);
return result;
}
@Override
public List<BuildCapacityDTO> queryBuildCapacityByProductionOrderNo(String productionOrderNo) {
if (StringUtils.isBlank(productionOrderNo)) {
return Collections.emptyList();
}
String proNo = productionOrderNo.trim();
List<ProductionOrder> productionOrderList = productionOrderService.selectByProCode(proNo);
if (CollectionUtils.isEmpty(productionOrderList)) {
return Collections.emptyList();
}
List<ProductionOrder> topLevel = productionOrderList.stream()
.filter(p -> StringUtils.isBlank(p.getParentDrawingNo()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(topLevel)) {
topLevel = productionOrderList;
}
Map<String, ProductionOrder> productMap = new LinkedHashMap<>();
for (ProductionOrder p : topLevel) {
if (p == null || StringUtils.isBlank(p.getDrawingNo())) {
continue;
}
productMap.putIfAbsent(p.getDrawingNo().trim(), p);
}
List<BuildCapacityDTO> list = new ArrayList<>();
for (ProductionOrder p : productMap.values()) {
list.add(queryBuildCapacity(proNo, p.getDrawingNo(), p.getDrawingName()));
}
return list;
}
private static int normalizeQtyToInt(Double value) {
if (value == null) {
return 0;
}
if (value <= 0) {
return 0;
}
return (int) Math.ceil(value);
}
private static int safeInt(Integer v) {
return v == null ? 0 : v;
}
private static double safeDouble(Double v) {
return v == null ? 0D : v;
}
@Override
public ProductionOrderMaterialsDTO queryProductionOrderMaterials(String productionOrderNo, String productCode) {
ProductionOrderMaterialsDTO dto = new ProductionOrderMaterialsDTO();
dto.setProductionOrderNo(productionOrderNo);
dto.setProductCode(productCode);
dto.setMaterials(Collections.emptyList());
if (StringUtils.isBlank(productionOrderNo) || StringUtils.isBlank(productCode)) {
return dto;
}
String proNo = productionOrderNo.trim();
String proCode = productCode.trim();
List<ProductionOrder> productionOrderList = productionOrderService.selectByProCode(proNo);
if (CollectionUtils.isEmpty(productionOrderList)) {
return dto;
}
dto.setProductName(productionOrderList.stream()
.filter(p -> p != null && StringUtils.isNotBlank(p.getDrawingNo()) && proCode.equals(p.getDrawingNo().trim()))
.map(ProductionOrder::getDrawingName)
.filter(StringUtils::isNotBlank)
.findFirst()
.orElse(null));
List<ProductionOrderMaterialItemDTO> materials = productionOrderList.stream()
.filter(p -> p != null)
.filter(p -> StringUtils.isNotBlank(p.getParentDrawingNo()) && proCode.equals(p.getParentDrawingNo().trim()))
.map(p -> {
ProductionOrderMaterialItemDTO item = new ProductionOrderMaterialItemDTO();
item.setMaterialCode(p.getDrawingNo());
item.setMaterialName(p.getDrawingName());
item.setQty(p.getQuantity());
item.setMaterial(p.getMaterial());
item.setSingleWeight(p.getSingleWeight());
item.setTotalWeight(p.getTotalWeight());
item.setRemark(p.getRemark());
item.setParentDrawingNo(p.getParentDrawingNo());
item.setParentPart(p.getParentPart());
item.setBatchQuantity(p.getBatchQuantity());
return item;
})
.sorted(Comparator.comparing(ProductionOrderMaterialItemDTO::getMaterialCode, Comparator.nullsLast(String::compareTo)))
.collect(Collectors.toList());
dto.setMaterials(materials);
return dto;
}
@Override
public R<Map<String, Object>> refreshImMaterialSingleWeightFromOrders() {
final int pageSize = 1000;
long scanned = 0;
long updated = 0;
long unchanged = 0;
long noResolved = 0;
int pageNum = 1;
LambdaQueryWrapper<ImMaterial> wrapper = Wrappers.lambdaQuery();
wrapper.isNotNull(ImMaterial::getMaterialCode);
wrapper.ne(ImMaterial::getMaterialCode, "");
wrapper.orderByAsc(ImMaterial::getId);
Date now = new Date();
String modifyDay = new SimpleDateFormat("yyyy-MM-dd").format(now);
while (true) {
Page<ImMaterial> pageResult = baseMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
List<ImMaterial> records = pageResult.getRecords();
if (records.isEmpty()) {
break;
}
scanned += records.size();
List<String> codes = records.stream()
.map(ImMaterial::getMaterialCode)
.filter(StringUtils::isNotBlank)
.map(String::trim)
.distinct()
.collect(Collectors.toList());
Map<String, Double> weightMap = productionOrderService.resolveSingleWeightByMaterialCodes(codes);
for (ImMaterial row : records) {
if (StringUtils.isBlank(row.getMaterialCode())) {
continue;
}
String code = row.getMaterialCode().trim();
Double resolved = weightMap.get(code);
if (resolved == null) {
noResolved++;
continue;
}
BigDecimal newBd = BigDecimal.valueOf(resolved).stripTrailingZeros();
if (singleWeightUnchanged(row.getSingleWeight(), newBd)) {
unchanged++;
continue;
}
row.setSingleWeight(newBd);
row.setUpdateTime(now);
row.setModifyDate(modifyDay);
baseMapper.updateById(row);
updated++;
}
if (records.size() < pageSize) {
break;
}
pageNum++;
}
Map<String, Object> data = new LinkedHashMap<>();
data.put("scanned", scanned);
data.put("updated", updated);
data.put("unchanged", unchanged);
data.put("noResolvedWeight", noResolved);
return R.ok("同步完成", data);
}
private static boolean singleWeightUnchanged(BigDecimal oldBd, BigDecimal newBd) {
if (oldBd == null && newBd == null) {
return true;
}
if (oldBd == null || newBd == null) {
return false;
}
return oldBd.stripTrailingZeros().compareTo(newBd.stripTrailingZeros()) == 0;
}
private static double toRequiredQty(Double fenzi, Integer fenmu) {
double numerator = fenzi == null ? 0D : fenzi;
int denominator = fenmu == null || fenmu == 0 ? 1 : fenmu;
return numerator / denominator;
}
private static int normalizeToInt(double value) {
if (value <= 0) {
return 0;
}
return (int) Math.floor(value);
}
} }

View File

@ -41,6 +41,7 @@ import org.apache.commons.collections4.ListUtils;
import org.aspectj.bridge.MessageUtil; import org.aspectj.bridge.MessageUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -86,6 +87,8 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
private final ProcessInfoMapper processInfoMapper; private final ProcessInfoMapper processInfoMapper;
private final ProductionRouteTwoMapper productionRouteTwoMapper; private final ProductionRouteTwoMapper productionRouteTwoMapper;
private final ProcessOrderProMapper processOrderProMapper; private final ProcessOrderProMapper processOrderProMapper;
@Value("${pdf.font-path:classpath:font/arial unicode ms.ttf}")
private String pdfFontPath;
public static JdHuoZhu getFIDToProductionOrder(PlanOrderVo planOrder) { public static JdHuoZhu getFIDToProductionOrder(PlanOrderVo planOrder) {
K3CloudApi client = new K3CloudApi(); K3CloudApi client = new K3CloudApi();
@ -816,8 +819,8 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
for (ProductionOrderVo productionOrderVo : productionOrderVos) { for (ProductionOrderVo productionOrderVo : productionOrderVos) {
BomDetailsVo bomDetails = new BomDetailsVo(); BomDetailsVo bomDetails = new BomDetailsVo();
bomDetails.setTotalWeight(productionOrderVo.getProductionOrderNo()); bomDetails.setTotalWeight(productionOrderVo.getProductionOrderNo());
bomDetails.setFNumber(productionOrderVo.getMainProducts()); bomDetails.setFNumber(productionOrderVo.getMainPart());
bomDetails.setFName(productionOrderVo.getMainProductsName()); bomDetails.setFName(productionOrderVo.getMainName());
bomDetails.setPartdiagramCode(productionOrderVo.getParentDrawingNo()); bomDetails.setPartdiagramCode(productionOrderVo.getParentDrawingNo());
bomDetails.setPartdiagramName(productionOrderVo.getParentPart()); bomDetails.setPartdiagramName(productionOrderVo.getParentPart());
bomDetails.setPartNumber(productionOrderVo.getDrawingNo()); bomDetails.setPartNumber(productionOrderVo.getDrawingNo());
@ -1444,7 +1447,13 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
@Override @Override
public String generatePDFs(String rooteProdet) throws IOException { public String generatePDFs(String rooteProdet) throws IOException {
List<CombinedDTO> selecPlanRouteList = getSelecPlanRouteList(rooteProdet); List<CombinedDTO> selecPlanRouteList = getSelecPlanRouteList(rooteProdet);
String pathZIP = PDFGenerator.writeToPdf(selecPlanRouteList, rooteProdet); if (selecPlanRouteList == null || selecPlanRouteList.isEmpty()) {
throw new ServiceException("未查询到可生成PDF的工艺数据");
}
String pathZIP = PDFGenerator.writeToPdf(selecPlanRouteList, rooteProdet, Collections.singletonList(pdfFontPath));
if (StringUtils.isBlank(pathZIP)) {
throw new ServiceException("PDF压缩包生成失败");
}
return pathZIP; return pathZIP;
} }
@ -1494,7 +1503,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
public List<PlanOrderVo> getSelectProceOrder1(String rooteProdet) { public List<PlanOrderVo> getSelectProceOrder1(String rooteProdet) {
K3CloudApi client = new K3CloudApi(); K3CloudApi client = new K3CloudApi();
// 第一次查询使用 F_HBYT_SCLH // 第一次查询使用 F_HBYT_SCLH
List<PlanOrderVo> result = queryWithFieldName(client, "F_HBYT_SCLH", rooteProdet); List<PlanOrderVo> result = queryWithFieldName(client, "F_HBYT_XMH", rooteProdet);
return result; return result;
} }
@ -1504,7 +1513,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
private List<PlanOrderVo> queryWithFieldName(K3CloudApi client, String fieldName, String rooteProdet) { private List<PlanOrderVo> queryWithFieldName(K3CloudApi client, String fieldName, String rooteProdet) {
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
json.addProperty("FormId", "PRD_MO"); json.addProperty("FormId", "PRD_MO");
json.addProperty("FieldKeys", "F_HBYT_SCLH,FBillNo ,FMaterialId.FNumber,FMaterialName, F_UCHN_BaseProperty_qtr"); json.addProperty("FieldKeys", "F_HBYT_XMH,F_HBYT_SCLH,FBillNo ,FMaterialId.FNumber,FMaterialName, F_UCHN_BaseProperty_qtr");
JsonArray filterString = new JsonArray(); JsonArray filterString = new JsonArray();
JsonObject filterObject = new JsonObject(); JsonObject filterObject = new JsonObject();
@ -1513,9 +1522,18 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
filterObject.addProperty("Value", rooteProdet); filterObject.addProperty("Value", rooteProdet);
filterObject.addProperty("Left", ""); filterObject.addProperty("Left", "");
filterObject.addProperty("Right", ""); filterObject.addProperty("Right", "");
filterObject.addProperty("Logic", 0); filterObject.addProperty("Logic", 1);
filterString.add(filterObject); filterString.add(filterObject);
JsonObject filterObject1 = new JsonObject();
filterObject1.addProperty("FieldName", "F_HBYT_SCLH"); // 使用传入的 fieldName
filterObject1.addProperty("Compare", "67");
filterObject1.addProperty("Value", rooteProdet);
filterObject1.addProperty("Left", "");
filterObject1.addProperty("Right", "");
filterObject1.addProperty("Logic", 1);
filterString.add(filterObject1);
json.add("FilterString", filterString); json.add("FilterString", filterString);
json.addProperty("OrderString", ""); json.addProperty("OrderString", "");
json.addProperty("TopRowCount", 0); json.addProperty("TopRowCount", 0);

View File

@ -1,346 +1,449 @@
package com.ruoyi.system.service.impl; package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.utils.HttpUtils; import com.ruoyi.common.utils.HttpUtils;
import com.ruoyi.system.domain.FigureSave; import com.ruoyi.system.domain.FigureSave;
import com.ruoyi.system.domain.PcRigidChain; import com.ruoyi.system.domain.PcRigidChain;
import com.ruoyi.system.domain.ProcessRoute; import com.ruoyi.system.domain.ProcessRoute;
import com.ruoyi.system.domain.bo.FigureSaveBo; import com.ruoyi.system.domain.bo.FigureSaveBo;
import com.ruoyi.system.domain.bo.ProcessOrderProBo; import com.ruoyi.system.domain.bo.ProcessOrderProBo;
import com.ruoyi.system.domain.bo.ProcessRouteBo; import com.ruoyi.system.domain.bo.ProcessRouteBo;
import com.ruoyi.system.domain.dto.excuteDrawing.DataInfo; import com.ruoyi.system.domain.dto.excuteDrawing.DataInfo;
import com.ruoyi.system.domain.dto.excuteDrawing.ProductInfo; import com.ruoyi.system.domain.dto.excuteDrawing.ProductInfo;
import com.ruoyi.system.domain.dto.excuteDrawing.PwProductionBill; import com.ruoyi.system.domain.dto.excuteDrawing.PwProductionBill;
import com.ruoyi.system.domain.vo.ProcessOrderProVo; import com.ruoyi.system.domain.dto.productionorder.ProductionOrderWeightExportDTO;
import com.ruoyi.system.mapper.FigureSaveMapper; import com.ruoyi.system.domain.dto.productionorder.ProductionOrderWeightImportDTO;
import com.ruoyi.system.service.IPcRigidChainService; import com.ruoyi.system.domain.vo.ProcessOrderProVo;
import lombok.RequiredArgsConstructor; import com.ruoyi.system.mapper.FigureSaveMapper;
import lombok.extern.slf4j.Slf4j; import com.ruoyi.system.service.IPcRigidChainService;
import org.springframework.beans.factory.annotation.Autowired; import com.ruoyi.system.utils.ProductionOrderNoUtils;
import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor;
import com.ruoyi.system.domain.bo.ProductionOrderBo; import lombok.extern.slf4j.Slf4j;
import com.ruoyi.system.domain.vo.ProductionOrderVo; import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.system.domain.ProductionOrder; import org.springframework.stereotype.Service;
import com.ruoyi.system.mapper.ProductionOrderMapper; import com.ruoyi.system.domain.bo.ProductionOrderBo;
import com.ruoyi.system.service.IProductionOrderService; import com.ruoyi.system.domain.vo.ProductionOrderVo;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.ProductionOrder;
import com.ruoyi.system.mapper.ProductionOrderMapper;
import java.io.File; import com.ruoyi.system.service.IProductionOrderService;
import java.nio.file.Paths; import com.ruoyi.common.utils.StringUtils;
import java.sql.Array;
import java.util.*; import java.io.File;
import java.nio.file.Paths;
/** import java.sql.Array;
* 生产订单Service业务层处理 import java.util.*;
* import java.util.stream.Collectors;
* @author 田志阳
* @date 2024-10-20 /**
*/ * 生产订单Service业务层处理
@RequiredArgsConstructor *
@Service * @author 田志阳
@Slf4j * @date 2024-10-20
public class ProductionOrderServiceImpl implements IProductionOrderService { */
@RequiredArgsConstructor
private final ProductionOrderMapper baseMapper; @Service
private final FigureSaveMapper figureSaveMapper; @Slf4j
@Autowired public class ProductionOrderServiceImpl implements IProductionOrderService {
private IPcRigidChainService pcRigidChainService;
/** private final ProductionOrderMapper baseMapper;
* 查询生产订单 private final FigureSaveMapper figureSaveMapper;
*/ @Autowired
@Override private IPcRigidChainService pcRigidChainService;
public ProductionOrderVo queryById(Long id){ /**
return baseMapper.selectVoById(id); * 查询生产订单
} */
@Override
/** public ProductionOrderVo queryById(Long id){
* 查询生产订单列表 return baseMapper.selectVoById(id);
*/ }
@Override
public TableDataInfo<ProductionOrderVo> queryPageList(ProductionOrderBo bo, PageQuery pageQuery) { /**
LambdaQueryWrapper<ProductionOrder> lqw = buildQueryWrapper(bo); * 查询生产订单列表
Page<ProductionOrderVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw); */
return TableDataInfo.build(result); @Override
} public TableDataInfo<ProductionOrderVo> queryPageList(ProductionOrderBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<ProductionOrder> lqw = buildQueryWrapper(bo);
/** Page<ProductionOrderVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
* 查询生产订单列表 return TableDataInfo.build(result);
*/ }
@Override
public List<ProductionOrderVo> queryList(ProductionOrderBo bo) { /**
LambdaQueryWrapper<ProductionOrder> lqw = buildQueryWrapper(bo); * 查询生产订单列表
return baseMapper.selectVoList(lqw); */
} @Override
public List<ProductionOrderVo> queryList(ProductionOrderBo bo) {
private LambdaQueryWrapper<ProductionOrder> buildQueryWrapper(ProductionOrderBo bo) { LambdaQueryWrapper<ProductionOrder> lqw = buildQueryWrapper(bo);
Map<String, Object> params = bo.getParams(); return baseMapper.selectVoList(lqw);
LambdaQueryWrapper<ProductionOrder> lqw = Wrappers.lambdaQuery(); }
lqw.eq(StringUtils.isNotBlank(bo.getProductionOrderNo()), ProductionOrder::getProductionOrderNo, bo.getProductionOrderNo());
lqw.like(StringUtils.isNotBlank(bo.getDrawingNo()), ProductionOrder::getDrawingNo, bo.getDrawingNo()); private LambdaQueryWrapper<ProductionOrder> buildQueryWrapper(ProductionOrderBo bo) {
lqw.like(StringUtils.isNotBlank(bo.getDrawingName()), ProductionOrder::getDrawingName, bo.getDrawingName()); Map<String, Object> params = bo.getParams();
lqw.eq(bo.getQuantity() != null, ProductionOrder::getQuantity, bo.getQuantity()); LambdaQueryWrapper<ProductionOrder> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getMaterial()), ProductionOrder::getMaterial, bo.getMaterial()); lqw.eq(StringUtils.isNotBlank(bo.getProductionOrderNo()), ProductionOrder::getProductionOrderNo, bo.getProductionOrderNo());
lqw.eq(bo.getSingleWeight() != null, ProductionOrder::getSingleWeight, bo.getSingleWeight()); lqw.like(StringUtils.isNotBlank(bo.getDrawingNo()), ProductionOrder::getDrawingNo, bo.getDrawingNo());
lqw.eq(bo.getTotalWeight() != null, ProductionOrder::getTotalWeight, bo.getTotalWeight()); lqw.like(StringUtils.isNotBlank(bo.getDrawingName()), ProductionOrder::getDrawingName, bo.getDrawingName());
lqw.eq(StringUtils.isNotBlank(bo.getParentPart()), ProductionOrder::getParentPart, bo.getParentPart()); lqw.eq(bo.getQuantity() != null, ProductionOrder::getQuantity, bo.getQuantity());
lqw.eq(StringUtils.isNotBlank(bo.getParentDrawingNo()), ProductionOrder::getParentDrawingNo, bo.getParentDrawingNo()); lqw.eq(StringUtils.isNotBlank(bo.getMaterial()), ProductionOrder::getMaterial, bo.getMaterial());
lqw.orderByDesc(ProductionOrder::getParentPart); lqw.eq(bo.getSingleWeight() != null, ProductionOrder::getSingleWeight, bo.getSingleWeight());
return lqw; lqw.like(StringUtils.isNotBlank(bo.getMainName()), ProductionOrder::getMainName, bo.getMainName());
} lqw.eq(StringUtils.isNotBlank(bo.getMainPart()), ProductionOrder::getMainPart, bo.getMainPart());
lqw.eq(bo.getTotalWeight() != null, ProductionOrder::getTotalWeight, bo.getTotalWeight());
/** lqw.eq(StringUtils.isNotBlank(bo.getParentPart()), ProductionOrder::getParentPart, bo.getParentPart());
* 新增生产订单 lqw.eq(StringUtils.isNotBlank(bo.getParentDrawingNo()), ProductionOrder::getParentDrawingNo, bo.getParentDrawingNo());
*/
@Override lqw.orderByDesc(ProductionOrder::getParentPart);
public Boolean insertByBo(ProductionOrderBo bo) { return lqw;
ProductionOrder add = BeanUtil.toBean(bo, ProductionOrder.class); }
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0; /**
if (flag) { * 新增生产订单
bo.setId(add.getId()); */
} @Override
return flag; public Boolean insertByBo(ProductionOrderBo bo) {
} ProductionOrder add = BeanUtil.toBean(bo, ProductionOrder.class);
validEntityBeforeSave(add);
/** boolean flag = baseMapper.insert(add) > 0;
* 修改生产订单 if (flag) {
*/ bo.setId(add.getId());
@Override }
public Boolean updateByBo(ProductionOrderBo bo) { return flag;
ProductionOrder update = BeanUtil.toBean(bo, ProductionOrder.class); }
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0; /**
} * 修改生产订单
*/
/** @Override
* 保存前的数据校验 public Boolean updateByBo(ProductionOrderBo bo) {
*/ ProductionOrder update = BeanUtil.toBean(bo, ProductionOrder.class);
private void validEntityBeforeSave(ProductionOrder entity){ validEntityBeforeSave(update);
//TODO 做一些数据校验,如唯一约束 return baseMapper.updateById(update) > 0;
} }
/** /**
* 批量删除生产订单 * 保存前的数据校验
*/ */
@Override private void validEntityBeforeSave(ProductionOrder entity){
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) { //TODO 做一些数据校验,如唯一约束
if(isValid){ }
//TODO 做一些业务上的校验,判断是否需要校验
} /**
return baseMapper.deleteBatchIds(ids) > 0; * 批量删除生产订单
} */
@Override
@Override public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
public void loadProductionOrder(List<ProductionOrderVo> productionOrderVoList) { if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
List<ProductionOrder> beanList = BeanUtil.copyToList(productionOrderVoList, ProductionOrder.class); }
for (ProductionOrder productionOrder : beanList) { return baseMapper.deleteBatchIds(ids) > 0;
productionOrder.setProductionOrderNo("Tset-001-24"); }
baseMapper.insert(productionOrder);
} @Override
public void loadProductionOrder(List<ProductionOrderVo> productionOrderVoList) {
}
List<ProductionOrder> beanList = BeanUtil.copyToList(productionOrderVoList, ProductionOrder.class);
@Override for (ProductionOrder productionOrder : beanList) {
public List<ProductionOrder> getGroupWeld(ProductionOrderVo productionOrderVo) { productionOrder.setProductionOrderNo("Tset-001-24");
BeanUtil.toBean(productionOrderVo, ProductionOrder.class); baseMapper.insert(productionOrder);
LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>(); }
wrapper.eq(ProductionOrder::getMaterial, "组焊件");
return baseMapper.selectList(wrapper); }
}
@Override
/** public List<ProductionOrder> getGroupWeld(ProductionOrderVo productionOrderVo) {
* 拿到材质为组焊件的集合再筛选出parentDrawingNo和DrawingNo相等的集合 BeanUtil.toBean(productionOrderVo, ProductionOrder.class);
* @param productionOrderVo LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>();
* @return wrapper.eq(ProductionOrder::getMaterial, "组焊件");
*/ return baseMapper.selectList(wrapper);
@Override }
public List<ProductionOrder> getGroupWeldSubset(ProductionOrderVo productionOrderVo) {
List<ProductionOrder> groupWeld = getGroupWeld(productionOrderVo); /**
// TODO: 拿到材质为组焊件的集合再筛选出parentDrawingNo和DrawingNo相等的集合 * 拿到材质为组焊件的集合再筛选出parentDrawingNo和DrawingNo相等的集合
List<ProductionOrder> subsetList = new ArrayList<>(); * @param productionOrderVo
for (ProductionOrder productionOrder : groupWeld) { * @return
if (Objects.equals(productionOrder.getParentDrawingNo(), productionOrder.getDrawingNo())){ */
subsetList.add(productionOrder); @Override
} public List<ProductionOrder> getGroupWeldSubset(ProductionOrderVo productionOrderVo) {
} List<ProductionOrder> groupWeld = getGroupWeld(productionOrderVo);
return subsetList; // TODO: 拿到材质为组焊件的集合再筛选出parentDrawingNo和DrawingNo相等的集合
} List<ProductionOrder> subsetList = new ArrayList<>();
for (ProductionOrder productionOrder : groupWeld) {
if (Objects.equals(productionOrder.getParentDrawingNo(), productionOrder.getDrawingNo())){
@Override subsetList.add(productionOrder);
public Boolean executDrawing(ProcessOrderProBo orderPro) { }
//查询是否有此令号 }
LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>(); return subsetList;
wrapper.eq(ProductionOrder::getProductionOrderNo, orderPro.getProductionOrderNo()); }
ProductionOrder productionOrder = baseMapper.selectOne(wrapper);
log.info("查询生产令号:{} 是否重复...", orderPro.getProductionOrderNo()); @Override
if (productionOrder != null) { public Boolean executDrawing(ProcessOrderProBo orderPro) {
new Throwable("令号重复"); //查询是否有此令号
return false; LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>();
} wrapper.eq(ProductionOrder::getProductionOrderNo, orderPro.getProductionOrderNo());
PwProductionBill pwProductionBill = new PwProductionBill(); ProductionOrder productionOrder = baseMapper.selectOne(wrapper);
String code = orderPro.getProductionOrderNo();
String yearPart; log.info("查询生产令号:{} 是否重复...", orderPro.getProductionOrderNo());
if ("-".equals(code.substring(2, 3))) { if (productionOrder != null) {
yearPart = code.substring(3, 5); new Throwable("令号重复");
} else { return false;
yearPart = code.substring(2, 4); }
} PwProductionBill pwProductionBill = new PwProductionBill();
String dirPath = Paths.get("F:", "20" + yearPart).toString(); String code = orderPro.getProductionOrderNo();
log.info("生产令号:{} 生成路径:{}", orderPro.getProductionOrderNo(), dirPath); String yearPart = ProductionOrderNoUtils.extractYearPart(code);
// pwProductionBill.setDestpath(dirPath.replace(File.separator, "\\")); String dirPath = Paths.get("F:", "20" + yearPart).toString();
pwProductionBill.setDestpath("D:\\EVO_TECH\\EVO程序输出路径"); log.info("生产令号:{} 生成路径:{}", orderPro.getProductionOrderNo(), dirPath);
// 创建目录多级 // pwProductionBill.setDestpath(dirPath.replace(File.separator, "\\"));
File file = new File(dirPath); pwProductionBill.setDestpath("D:\\EVO_TECH\\EVO程序输出路径");
if (!file.exists()) { // 创建目录多级
file.mkdirs(); File file = new File(dirPath);
} if (!file.exists()) {
//生产令号 file.mkdirs();
pwProductionBill.setProducitonorder(orderPro.getProductionOrderNo()); }
//出图时间 //生产令号
pwProductionBill.setReleasedate(String.valueOf(orderPro.getProjectEndTime())); pwProductionBill.setProducitonorder(orderPro.getProductionOrderNo());
//产品集合 //出图时间
List<ProductInfo> productInfos = new ArrayList<>(); pwProductionBill.setReleasedate(String.valueOf(orderPro.getProjectEndTime()));
LambdaQueryWrapper<FigureSave> queryWrapper = new LambdaQueryWrapper<>(); //产品集合
queryWrapper.eq(FigureSave::getPid, orderPro.getId()); List<ProductInfo> productInfos = new ArrayList<>();
List<FigureSave> productionOrders = figureSaveMapper.selectList(queryWrapper); LambdaQueryWrapper<FigureSave> queryWrapper = new LambdaQueryWrapper<>();
ProductInfo productInfo =null; queryWrapper.eq(FigureSave::getPid, orderPro.getId());
for (FigureSave figureSave : productionOrders) { List<FigureSave> productionOrders = figureSaveMapper.selectList(queryWrapper);
productInfo.setAssembledrawing(figureSave.getFigureNumber()); ProductInfo productInfo =null;
// productInfo.setNumber(String.valueOf(figureSave.getFigureNum())); for (FigureSave figureSave : productionOrders) {
productInfo.setNumber(String.valueOf(2)); productInfo.setAssembledrawing(figureSave.getFigureNumber());
productInfo.setSourcefile("D:\\EVO_TECH\\EVO程序图\\40S程序图.dwg"); // productInfo.setNumber(String.valueOf(figureSave.getFigureNum()));
//设置变量信息 productInfo.setNumber(String.valueOf(2));
log.info("项目生产令号为:{}查询产品型号信息: {}+ 产品名称: {}",figureSave.getProductionCode(), figureSave.getFigureNumber(), figureSave.getFigureName()); productInfo.setSourcefile("D:\\EVO_TECH\\EVO程序图\\40S程序图.dwg");
PcRigidChain rigidChain = pcRigidChainService.selectPcRigidChainByTypeName(figureSave.getFigureNumber()); //设置变量信息
DataInfo datainfo = new DataInfo(); log.info("项目生产令号为:{}查询产品型号信息: {}+ 产品名称: {}",figureSave.getProductionCode(), figureSave.getFigureNumber(), figureSave.getFigureName());
if(null!=rigidChain){ PcRigidChain rigidChain = pcRigidChainService.selectPcRigidChainByTypeName(figureSave.getFigureNumber());
datainfo = new DataInfo(); DataInfo datainfo = new DataInfo();
datainfo.setV1(rigidChain.getVOne().toString()); if(null!=rigidChain){
datainfo.setV2(rigidChain.getVTwo().toString()); datainfo = new DataInfo();
datainfo.setV3(rigidChain.getVThree().toString()); datainfo.setV1(rigidChain.getVOne().toString());
datainfo.setV4(rigidChain.getVFour().toString()); datainfo.setV2(rigidChain.getVTwo().toString());
datainfo.setV5(rigidChain.getVFive().toString()); datainfo.setV3(rigidChain.getVThree().toString());
datainfo.setV6(rigidChain.getVSix().toString()); datainfo.setV4(rigidChain.getVFour().toString());
datainfo.setV7(rigidChain.getVSeven().toString()); datainfo.setV5(rigidChain.getVFive().toString());
datainfo.setV8(rigidChain.getVEight().toString()); datainfo.setV6(rigidChain.getVSix().toString());
datainfo.setV9(rigidChain.getVNine().toString()); datainfo.setV7(rigidChain.getVSeven().toString());
datainfo.setV10(rigidChain.getVTen().toString()); datainfo.setV8(rigidChain.getVEight().toString());
datainfo.setV11(rigidChain.getVEleven().toString()); datainfo.setV9(rigidChain.getVNine().toString());
datainfo.setV12(rigidChain.getVTwelve().toString()); datainfo.setV10(rigidChain.getVTen().toString());
datainfo.setV13(rigidChain.getVThirteen().toString()); datainfo.setV11(rigidChain.getVEleven().toString());
datainfo.setV14(rigidChain.getVFourteen().toString()); datainfo.setV12(rigidChain.getVTwelve().toString());
datainfo.setV15(rigidChain.getVFifteen().toString()); datainfo.setV13(rigidChain.getVThirteen().toString());
datainfo.setV16(rigidChain.getVSixteen().toString()); datainfo.setV14(rigidChain.getVFourteen().toString());
datainfo.setV17(rigidChain.getVSeveteen().toString()); datainfo.setV15(rigidChain.getVFifteen().toString());
datainfo.setV18(rigidChain.getVEighteen().toString()); datainfo.setV16(rigidChain.getVSixteen().toString());
datainfo.setV19(rigidChain.getVNineteen().toString()); datainfo.setV17(rigidChain.getVSeveteen().toString());
datainfo.setV20(rigidChain.getVTwenty().toString()); datainfo.setV18(rigidChain.getVEighteen().toString());
datainfo.setV21(rigidChain.getVTwentyOne().toString()); datainfo.setV19(rigidChain.getVNineteen().toString());
datainfo.setV22(rigidChain.getVTwentyTwo().toString()); datainfo.setV20(rigidChain.getVTwenty().toString());
datainfo.setV23(rigidChain.getVTwentyThree().toString()); datainfo.setV21(rigidChain.getVTwentyOne().toString());
datainfo.setV24(rigidChain.getVTwentyFour().toString()); datainfo.setV22(rigidChain.getVTwentyTwo().toString());
datainfo.setV25(rigidChain.getVTwentyFive().toString()); datainfo.setV23(rigidChain.getVTwentyThree().toString());
datainfo.setV26(rigidChain.getVTwentySix().toString()); datainfo.setV24(rigidChain.getVTwentyFour().toString());
datainfo.setV27(rigidChain.getVTwentySeven().toString()); datainfo.setV25(rigidChain.getVTwentyFive().toString());
datainfo.setV28(rigidChain.getVTwentyEight().toString()); datainfo.setV26(rigidChain.getVTwentySix().toString());
datainfo.setV29(rigidChain.getVTwentyNine().toString()); datainfo.setV27(rigidChain.getVTwentySeven().toString());
datainfo.setV30(rigidChain.getVThirty().toString()); datainfo.setV28(rigidChain.getVTwentyEight().toString());
datainfo.setV31(rigidChain.getVThirtyOne().toString()); datainfo.setV29(rigidChain.getVTwentyNine().toString());
datainfo.setV32(rigidChain.getVThirtyTwo().toString()); datainfo.setV30(rigidChain.getVThirty().toString());
datainfo.setV33(rigidChain.getVThirtyThree().toString()); datainfo.setV31(rigidChain.getVThirtyOne().toString());
datainfo.setV34(rigidChain.getVThirtyFour().toString()); datainfo.setV32(rigidChain.getVThirtyTwo().toString());
datainfo.setV35(rigidChain.getVThirtyFive().toString()); datainfo.setV33(rigidChain.getVThirtyThree().toString());
datainfo.setV36(rigidChain.getVThirtySix().toString()); datainfo.setV34(rigidChain.getVThirtyFour().toString());
datainfo.setV37(rigidChain.getVThirtySeven().toString()); datainfo.setV35(rigidChain.getVThirtyFive().toString());
datainfo.setV38(rigidChain.getVThirtyEight().toString()); datainfo.setV36(rigidChain.getVThirtySix().toString());
datainfo.setV39(rigidChain.getVThirtyNine().toString()); datainfo.setV37(rigidChain.getVThirtySeven().toString());
datainfo.setV40(rigidChain.getVForty().toString()); datainfo.setV38(rigidChain.getVThirtyEight().toString());
datainfo.setV41(rigidChain.getVFortyOne().toString()); datainfo.setV39(rigidChain.getVThirtyNine().toString());
datainfo.setV42(rigidChain.getVFortyTwo().toString()); datainfo.setV40(rigidChain.getVForty().toString());
datainfo.setV43(rigidChain.getVFortyThree().toString()); datainfo.setV41(rigidChain.getVFortyOne().toString());
datainfo.setV44(rigidChain.getVFortyFour().toString()); datainfo.setV42(rigidChain.getVFortyTwo().toString());
datainfo.setV45(rigidChain.getVFortyFive().toString()); datainfo.setV43(rigidChain.getVFortyThree().toString());
datainfo.setV46(rigidChain.getVFortySix().toString()); datainfo.setV44(rigidChain.getVFortyFour().toString());
datainfo.setV47(rigidChain.getVFortySeven().toString()); datainfo.setV45(rigidChain.getVFortyFive().toString());
datainfo.setV48(rigidChain.getVFortyEight().toString()); datainfo.setV46(rigidChain.getVFortySix().toString());
datainfo.setV49(rigidChain.getVFortyNine().toString()); datainfo.setV47(rigidChain.getVFortySeven().toString());
datainfo.setV50(rigidChain.getVFifty().toString()); datainfo.setV48(rigidChain.getVFortyEight().toString());
datainfo.setV51(rigidChain.getVFiftyOne().toString()); datainfo.setV49(rigidChain.getVFortyNine().toString());
datainfo.setV52(rigidChain.getVFiftyTwo().toString()); datainfo.setV50(rigidChain.getVFifty().toString());
datainfo.setV53(rigidChain.getVFiftyThree().toString()); datainfo.setV51(rigidChain.getVFiftyOne().toString());
datainfo.setV52(rigidChain.getVFiftyTwo().toString());
datainfo.setG1(rigidChain.getGOne().toString()); datainfo.setV53(rigidChain.getVFiftyThree().toString());
datainfo.setG2(rigidChain.getGTwo().toString());
datainfo.setG3(rigidChain.getGThree().toString()); datainfo.setG1(rigidChain.getGOne().toString());
datainfo.setG4(rigidChain.getGFour().toString()); datainfo.setG2(rigidChain.getGTwo().toString());
datainfo.setG5(rigidChain.getGFive().toString()); datainfo.setG3(rigidChain.getGThree().toString());
datainfo.setG6(rigidChain.getGSix().toString()); datainfo.setG4(rigidChain.getGFour().toString());
datainfo.setG7(rigidChain.getGSeven().toString()); datainfo.setG5(rigidChain.getGFive().toString());
datainfo.setG8(rigidChain.getGEight().toString()); datainfo.setG6(rigidChain.getGSix().toString());
datainfo.setG9(rigidChain.getGNine().toString()); datainfo.setG7(rigidChain.getGSeven().toString());
datainfo.setG10(rigidChain.getGTen().toString()); datainfo.setG8(rigidChain.getGEight().toString());
datainfo.setG11(rigidChain.getGEleven().toString()); datainfo.setG9(rigidChain.getGNine().toString());
datainfo.setG12(rigidChain.getGTwelve().toString()); datainfo.setG10(rigidChain.getGTen().toString());
datainfo.setG13(rigidChain.getGThirteen().toString()); datainfo.setG11(rigidChain.getGEleven().toString());
datainfo.setG14(rigidChain.getGFifteen().toString()); datainfo.setG12(rigidChain.getGTwelve().toString());
datainfo.setG15(rigidChain.getGFifteen().toString()); datainfo.setG13(rigidChain.getGThirteen().toString());
datainfo.setG16(rigidChain.getGSixteen().toString()); datainfo.setG14(rigidChain.getGFifteen().toString());
datainfo.setG17(rigidChain.getGSeveteen().toString()); datainfo.setG15(rigidChain.getGFifteen().toString());
datainfo.setG18(rigidChain.getGEighteen().toString()); datainfo.setG16(rigidChain.getGSixteen().toString());
datainfo.setG19(rigidChain.getGNineteen().toString()); datainfo.setG17(rigidChain.getGSeveteen().toString());
datainfo.setG20(rigidChain.getGTwenty().toString()); datainfo.setG18(rigidChain.getGEighteen().toString());
} datainfo.setG19(rigidChain.getGNineteen().toString());
productInfo.setVars(datainfo); datainfo.setG20(rigidChain.getGTwenty().toString());
productInfos.add(productInfo); }
} productInfo.setVars(datainfo);
pwProductionBill.setProduct(productInfos); productInfos.add(productInfo);
String dawingDate = JSONObject.toJSONString(pwProductionBill); }
String dataResult = HttpUtils.sendPost("http://192.168.5.23:9000/generatedrawingscompatible", dawingDate); pwProductionBill.setProduct(productInfos);
String dawingDate = JSONObject.toJSONString(pwProductionBill);
return null; String dataResult = HttpUtils.sendPost("http://192.168.5.23:9000/generatedrawingscompatible", dawingDate);
}
return null;
/** }
* @param productionOrderNo
* @return /**
*/ * @param productionOrderNo
@Override * @return
public List<ProductionOrder> selectByProCode(String productionOrderNo) { */
LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>(); @Override
wrapper.eq(ProductionOrder::getProductionOrderNo,productionOrderNo); public List<ProductionOrder> selectByProCode(String productionOrderNo) {
return baseMapper.selectList(wrapper); LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>();
} wrapper.eq(ProductionOrder::getProductionOrderNo,productionOrderNo);
return baseMapper.selectList(wrapper);
/** }
* 判断如果是外购产品返回true
* @param proCode /**
* @param materialCode * 判断如果是外购产品返回true
* @return * @param proCode
*/ * @param materialCode
@Override * @return
public Boolean isPurchas(String proCode, String materialCode) { */
if (proCode == null || materialCode == null) { @Override
return false; public Boolean isPurchas(String proCode, String materialCode) {
} if (proCode == null || materialCode == null) {
String p = proCode.trim(); return false;
String m = materialCode.trim(); }
LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>(); String p = proCode.trim();
wrapper.eq(ProductionOrder::getProductionOrderNo, p) String m = materialCode.trim();
.eq(ProductionOrder::getDrawingNo, m) LambdaQueryWrapper<ProductionOrder> wrapper = new LambdaQueryWrapper<>();
.isNotNull(ProductionOrder::getRemark) wrapper.eq(ProductionOrder::getProductionOrderNo, p)
.like(ProductionOrder::getRemark, "外购"); .eq(ProductionOrder::getDrawingNo, m)
Long count = baseMapper.selectCount(wrapper); .isNotNull(ProductionOrder::getRemark)
return count != null && count > 0; .like(ProductionOrder::getRemark, "外购");
} Long count = baseMapper.selectCount(wrapper);
return count != null && count > 0;
}
}
@Override
public List<String> selectProductionOrderNoOptions(String keyword, Integer limit) {
int safeLimit = limit == null ? 200 : limit;
if (safeLimit <= 0) {
safeLimit = 200;
}
if (safeLimit > 1000) {
safeLimit = 1000;
}
String kw = StringUtils.isBlank(keyword) ? null : keyword.trim();
return baseMapper.selectDistinctProductionOrderNos(kw, safeLimit);
}
private Map<String, Double> buildSingleWeightLookupMap(List<String> drawingNos) {
if (drawingNos == null || drawingNos.isEmpty()) {
return Collections.emptyMap();
}
final int batchSize = 500;
Map<String, Double> weightMap = new HashMap<>();
for (int i = 0; i < drawingNos.size(); i += batchSize) {
int end = Math.min(i + batchSize, drawingNos.size());
List<String> batch = drawingNos.subList(i, end);
List<ProductionOrder> dbRows = baseMapper.selectWeightByDrawingNos(batch);
for (ProductionOrder dbRow : dbRows) {
if (StringUtils.isNotBlank(dbRow.getDrawingNo()) && dbRow.getSingleWeight() != null) {
weightMap.put(dbRow.getDrawingNo().trim(), dbRow.getSingleWeight().doubleValue());
}
}
}
List<String> missingCodes = drawingNos.stream()
.filter(code -> !weightMap.containsKey(code))
.collect(Collectors.toList());
for (int i = 0; i < missingCodes.size(); i += batchSize) {
int end = Math.min(i + batchSize, missingCodes.size());
List<String> batch = missingCodes.subList(i, end);
List<ProductionOrder> routeRows = baseMapper.selectDiscWeightByMaterialCodes(batch);
for (ProductionOrder routeRow : routeRows) {
if (StringUtils.isNotBlank(routeRow.getDrawingNo()) && routeRow.getSingleWeight() != null) {
weightMap.put(routeRow.getDrawingNo().trim(), routeRow.getSingleWeight().doubleValue());
}
}
}
return weightMap;
}
@Override
public Map<String, Double> resolveSingleWeightByMaterialCodes(Collection<String> materialCodes) {
if (materialCodes == null || materialCodes.isEmpty()) {
return Collections.emptyMap();
}
List<String> drawingNos = materialCodes.stream()
.filter(StringUtils::isNotBlank)
.map(String::trim)
.distinct()
.collect(Collectors.toList());
if (drawingNos.isEmpty()) {
return Collections.emptyMap();
}
return buildSingleWeightLookupMap(drawingNos);
}
@Override
public List<ProductionOrderWeightExportDTO> matchSingleWeightByMaterialCode(List<ProductionOrderWeightImportDTO> rows) {
if (rows == null || rows.isEmpty()) {
return Collections.emptyList();
}
List<String> drawingNos = rows.stream()
.map(ProductionOrderWeightImportDTO::getMaterialCode)
.filter(StringUtils::isNotBlank)
.map(String::trim)
.distinct()
.collect(Collectors.toList());
if (drawingNos.isEmpty()) {
return Collections.emptyList();
}
Map<String, Double> weightMap = buildSingleWeightLookupMap(drawingNos);
List<ProductionOrderWeightExportDTO> result = new ArrayList<>();
for (ProductionOrderWeightImportDTO row : rows) {
if (row == null || StringUtils.isBlank(row.getMaterialCode())) {
continue;
}
String code = row.getMaterialCode().trim();
Double dbWeight = weightMap.get(code);
ProductionOrderWeightExportDTO dto = new ProductionOrderWeightExportDTO();
dto.setMaterialCode(code);
dto.setMaterialName(row.getMaterialName());
dto.setSingleWeight(dbWeight);
result.add(dto);
}
return result;
}
@Override
public Double resolveSingleWeightByMaterialCode(String materialCode) {
if (StringUtils.isBlank(materialCode)) {
return null;
}
String code = materialCode.trim();
return buildSingleWeightLookupMap(Collections.singletonList(code)).get(code);
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.dingding.relay.DingMessageRelayService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@ -30,18 +31,19 @@ import com.ruoyi.system.runner.updatePcessPlanConver;
import com.kingdee.bos.webapi.entity.RepoRet; import com.kingdee.bos.webapi.entity.RepoRet;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import java.util.Date; import java.util.Date;
import com.ruoyi.system.service.ISfcOperationPlanningMainService; import com.ruoyi.system.service.ISfcOperationPlanningMainService;
import com.ruoyi.system.service.support.OperationPlanningResultAssembler;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.Comparator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
/** /**
@ -57,9 +59,10 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
private final SfcOperationPlanningMainMapper baseMapper; private final SfcOperationPlanningMainMapper baseMapper;
private final SfcOperationPlanningDetailMapper detailMapper; private final SfcOperationPlanningDetailMapper detailMapper;
private final DingMessageRelayService dingMessageRelayService;
/** /**
* 同步K3工序计划数据 * 同步K3工序计划数据全量新增与更新
*/ */
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -81,7 +84,12 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
mainEntity.setFid(resultDTO.getFid()); mainEntity.setFid(resultDTO.getFid());
mainEntity.setBillNo(resultDTO.getBillNo()); mainEntity.setBillNo(resultDTO.getBillNo());
mainEntity.setMoNumber(resultDTO.getMoNumber()); mainEntity.setMoNumber(resultDTO.getMoNumber());
mainEntity.setHbytSclh(resultDTO.getBatchNo()); if (StringUtils.isNotBlank(resultDTO.getBatchNo())) {
mainEntity.setHbytSclh(resultDTO.getBatchNo());
} else if (mainEntity.getId() == null && StringUtils.isNotBlank(resultDTO.getProjectNo())) {
mainEntity.setHbytSclh(resultDTO.getProjectNo());
}
mainEntity.setFHbytXmh(resultDTO.getProjectNo());
mainEntity.setProductCode(resultDTO.getProductCode()); mainEntity.setProductCode(resultDTO.getProductCode());
mainEntity.setProductName(resultDTO.getProductName()); mainEntity.setProductName(resultDTO.getProductName());
mainEntity.setProSpecification(resultDTO.getProSpecification()); mainEntity.setProSpecification(resultDTO.getProSpecification());
@ -104,19 +112,8 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
List<ProcessDetailDTO> processList = resultDTO.getProcessList(); List<ProcessDetailDTO> processList = resultDTO.getProcessList();
if (processList != null && !processList.isEmpty()) { if (processList != null && !processList.isEmpty()) {
for (ProcessDetailDTO detailDTO : processList) { for (ProcessDetailDTO detailDTO : processList) {
// 查询是否已经存在该明细记录 (根据 fid entity_entry_id oper_number) // 先按 k3_detail_id 再按 entity_entry_id+工序号兜底避免金蝶字段空/有切换时漏查导致重复 insert 触发唯一约束
SfcOperationPlanningDetail detailEntity; SfcOperationPlanningDetail detailEntity = findDetailForSync(resultDTO.getFid(), detailDTO);
if (detailDTO.getK3DetailId() != null) {
detailEntity = detailMapper.selectOne(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.eq(SfcOperationPlanningDetail::getFid, resultDTO.getFid())
.eq(SfcOperationPlanningDetail::getK3DetailId, detailDTO.getK3DetailId()));
} else {
detailEntity = detailMapper.selectOne(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.eq(SfcOperationPlanningDetail::getFid, resultDTO.getFid())
.eq(SfcOperationPlanningDetail::getEntityEntryId, detailDTO.getEntityEntryId())
.eq(detailDTO.getOperNo() != null, SfcOperationPlanningDetail::getOperNumber, detailDTO.getOperNo()));
}
if (detailEntity == null) { if (detailEntity == null) {
detailEntity = new SfcOperationPlanningDetail(); detailEntity = new SfcOperationPlanningDetail();
} }
@ -159,7 +156,6 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
detailEntity.setActivityName(detailDTO.getActivity1Name()); detailEntity.setActivityName(detailDTO.getActivity1Name());
detailEntity.setOperDescription(detailDTO.getOperDescription()); detailEntity.setOperDescription(detailDTO.getOperDescription());
detailEntity.setEquipment(detailDTO.getEquipment()); detailEntity.setEquipment(detailDTO.getEquipment());
detailEntity.setK3DetailId(detailDTO.getK3DetailId());
if (detailEntity.getId() == null) { if (detailEntity.getId() == null) {
detailMapper.insert(detailEntity); detailMapper.insert(detailEntity);
@ -172,6 +168,51 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
return true; return true;
} }
/**
* 同步时定位已有明细行优先 k3_detail_id未命中再用 entity_entry_id及工序号匹配避免互斥分支漏查
*/
private SfcOperationPlanningDetail findDetailForSync(Long fid, ProcessDetailDTO detailDTO) {
if (fid == null) {
return null;
}
if (detailDTO.getK3DetailId() != null) {
List<SfcOperationPlanningDetail> byK3 = detailMapper.selectList(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.eq(SfcOperationPlanningDetail::getFid, fid)
.eq(SfcOperationPlanningDetail::getK3DetailId, detailDTO.getK3DetailId())
.last("LIMIT 2"));
if (!byK3.isEmpty()) {
if (byK3.size() > 1) {
log.warn("工序计划明细 k3_detail_id 重复 fid={} k3DetailId={} 使用首条", fid, detailDTO.getK3DetailId());
}
return byK3.get(0);
}
}
if (detailDTO.getEntityEntryId() != null) {
LambdaQueryWrapper<SfcOperationPlanningDetail> w = Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.eq(SfcOperationPlanningDetail::getFid, fid)
.eq(SfcOperationPlanningDetail::getEntityEntryId, detailDTO.getEntityEntryId());
if (detailDTO.getOperNo() != null) {
w.eq(SfcOperationPlanningDetail::getOperNumber, detailDTO.getOperNo().longValue());
}
List<SfcOperationPlanningDetail> list = detailMapper.selectList(w);
if (list.isEmpty()) {
return null;
}
if (list.size() > 1) {
if (detailDTO.getK3DetailId() != null) {
for (SfcOperationPlanningDetail row : list) {
if (detailDTO.getK3DetailId().equals(row.getK3DetailId())) {
return row;
}
}
}
log.warn("工序计划明细命中多条 fid={} entityEntryId={} operNo={} 使用首条", fid, detailDTO.getEntityEntryId(), detailDTO.getOperNo());
}
return list.get(0);
}
return null;
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Boolean updateOperators(OperationPlanningUpdateOperatorsReq req) { public Boolean updateOperators(OperationPlanningUpdateOperatorsReq req) {
@ -290,9 +331,95 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
detail.setEquipment(item.getEquipment()); // 同步更新设备字段 detail.setEquipment(item.getEquipment()); // 同步更新设备字段
detailMapper.updateById(detail); detailMapper.updateById(detail);
} }
sendDispatchNotice(main, details, updateMap);
return true; return true;
} }
private void sendDispatchNotice(SfcOperationPlanningMain main,
List<SfcOperationPlanningDetail> details,
Map<Long, OperationPlanningUpdateOperatorItem> updateMap) {
List<String> receivers = resolveDispatchReceivers(updateMap);
if (receivers.isEmpty()) {
log.warn("派工通知未发送:本次派工未提供有效人员名字");
return;
}
String title = "工序派工通知";
String markdown = buildDispatchMarkdown(main, details, updateMap);
try {
dingMessageRelayService.sendMarkdownToUser(title, markdown, receivers);
} catch (Exception e) {
// 通知发送失败不影响派工更新主流程
log.error("派工通知发送失败, fid={}", main == null ? null : main.getFid(), e);
}
}
private List<String> resolveDispatchReceivers(Map<Long, OperationPlanningUpdateOperatorItem> updateMap) {
if (updateMap == null || updateMap.isEmpty()) {
return new ArrayList<>();
}
Set<String> users = new LinkedHashSet<>();
for (OperationPlanningUpdateOperatorItem item : updateMap.values()) {
if (item == null || StringUtils.isBlank(item.getOperator())) {
continue;
}
String normalized = item.getOperator().replace("", ",").trim();
if (StringUtils.isBlank(normalized)) {
continue;
}
String[] parts = normalized.split("");
for (String part : parts) {
String user = part == null ? null : part.trim();
if (StringUtils.isNotBlank(user)) {
users.add(user);
}
}
}
return new ArrayList<>(users);
}
private String buildDispatchMarkdown(SfcOperationPlanningMain main,
List<SfcOperationPlanningDetail> details,
Map<Long, OperationPlanningUpdateOperatorItem> updateMap) {
StringBuilder sb = new StringBuilder(512);
sb.append("### 工序派工通知\n\n")
.append("- 派工时间: ").append(DateUtil.formatDateTime(new Date())).append("\n")
.append("- 生产执行单号: ").append(main.getHbytSclh() == null ? "-" : main.getHbytSclh()).append("\n")
.append("- 项目号: ").append(main.getFHbytXmh() == null ? "-" : main.getFHbytXmh()).append("\n")
.append("- 产品编码: ").append(main.getProductCode() == null ? "-" : main.getProductCode()).append("\n")
.append("- 产品名称: ").append(main.getProductName() == null ? "-" : main.getProductName()).append("\n")
.append("---\n");
for (SfcOperationPlanningDetail detail : details) {
OperationPlanningUpdateOperatorItem item = updateMap.get(detail.getK3DetailId());
if (item == null) {
continue;
}
String operator = item.getOperator();
if (operator != null) {
operator = operator.trim();
}
sb.append("**工序: ").append(detail.getProcessName() == null ? "-" : detail.getProcessName())
.append("**\n")
.append("- 工序号: ").append(detail.getOperNumber() == null ? "-" : detail.getOperNumber()).append("\n")
.append("- 工序数量: ").append(detail.getOperQty() == null ? "-" : detail.getOperQty()).append("\n")
.append("- 转入数量: ").append(detail.getTransInQty() == null ? "-" : detail.getTransInQty()).append("\n")
.append("- 车间/工作中心: ")
.append(detail.getWorkCenterName() == null ? "-" : detail.getWorkCenterName()).append("\n")
.append("- 作业: ").append(detail.getProcessName() == null ? "-" : detail.getProcessName()).append("\n")
.append("- 作业人员: ").append(StringUtils.isBlank(operator) ? "-" : operator).append("\n")
.append("- 工序控制码: ").append(StringUtils.isBlank(detail.getOptCtrlCode()) ? "-" : detail.getOptCtrlCode()).append("\n")
.append("- 工序说明: ").append(StringUtils.isBlank(detail.getOperDescription()) ? "-" : detail.getOperDescription()).append("\n")
.append("- 设备/厂家: ").append(StringUtils.isBlank(item.getEquipment()) ? "-" : item.getEquipment().trim()).append("\n")
.append("- 工序计划开始时间: ")
.append(detail.getSeqPlanStartTime() == null ? "-" : DateUtil.formatDateTime(detail.getSeqPlanStartTime())).append("\n")
.append("- 工序计划完成时间: ")
.append(detail.getSeqPlanFinishTime() == null ? "-" : DateUtil.formatDateTime(detail.getSeqPlanFinishTime())).append("\n")
.append("---\n");
}
return sb.toString();
}
/** /**
* 查询工序计划主 * 查询工序计划主
*/ */
@ -412,137 +539,7 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
.collect(Collectors.groupingBy(SfcOperationPlanningDetail::getMainId)); .collect(Collectors.groupingBy(SfcOperationPlanningDetail::getMainId));
// 6. 组装返回给前端的层级结构 // 6. 组装返回给前端的层级结构
List<OperationPlanResultDTO> resultList = new ArrayList<>(); List<OperationPlanResultDTO> resultList = OperationPlanningResultAssembler.assemble(mainList, detailMap);
for (SfcOperationPlanningMain main : mainList) {
OperationPlanResultDTO resultDTO = new OperationPlanResultDTO();
resultDTO.setMoNumber(main.getMoNumber());
resultDTO.setBatchNo(main.getHbytSclh());
resultDTO.setProductCode(main.getProductCode());
resultDTO.setProductName(main.getProductName());
resultDTO.setBillNo(main.getBillNo());
resultDTO.setProSpecification(main.getProSpecification());
resultDTO.setPlanStartTime(main.getPlanStartTime() != null ? DateUtil.formatDateTime(main.getPlanStartTime()) : null);
resultDTO.setPlanFinishTime(main.getPlanFinishTime() != null ? DateUtil.formatDateTime(main.getPlanFinishTime()) : null);
resultDTO.setFid(main.getFid());
// 获取原生Date以便于后续排序使用
resultDTO.setRawPlanStartTime(main.getPlanStartTime());
// 组装关联的子表数据
List<ProcessDetailDTO> processList = new ArrayList<>();
List<SfcOperationPlanningDetail> relatedDetails = detailMap.get(main.getId());
double totalPlanQty = 0.0;
double totalOkQty = 0.0;
int finishedCount = 0;
String mainWorkCenter = "";
if (relatedDetails != null) {
// 计算该单据所有工段的总进度数据
mainWorkCenter = relatedDetails.get(0).getWorkCenterName();
for (SfcOperationPlanningDetail detail : relatedDetails) {
if (detail.getOperQty() != null) totalPlanQty += detail.getOperQty().doubleValue();
if (detail.getQualifiedQty() != null) totalOkQty += detail.getQualifiedQty().doubleValue();
if ("4".equals(detail.getOperStatus())) {
finishedCount++;
}
}
for (SfcOperationPlanningDetail detail : relatedDetails) {
ProcessDetailDTO detailDTO = new ProcessDetailDTO();
detailDTO.setOperNo(detail.getOperNumber() != null ? detail.getOperNumber().intValue() : null);
detailDTO.setProcessName(detail.getProcessName());
detailDTO.setWorkCenter(detail.getWorkCenterName());
detailDTO.setPlanQty(detail.getOperQty() != null ? detail.getOperQty().doubleValue() : null);
detailDTO.setInQty(detail.getTransInQty() != null ? detail.getTransInQty().doubleValue() : null);
detailDTO.setOkQty(detail.getQualifiedQty() != null ? detail.getQualifiedQty().doubleValue() : null);
detailDTO.setNgQty(detail.getUnqualifiedQty() != null ? detail.getUnqualifiedQty().doubleValue() : null);
detailDTO.setTransOutQty(detail.getTransOutQty() != null ? detail.getTransOutQty().doubleValue() : null);
detailDTO.setWastageQty(detail.getWastageQty() != null ? detail.getWastageQty().doubleValue() : null);
detailDTO.setDepartment(detail.getDepartmentName());
detailDTO.setOperator(detail.getOperator());
if (detailDTO.getPlanQty() != null && detailDTO.getInQty() != null && detailDTO.getPlanQty() > 0) {
BigDecimal progress = BigDecimal.valueOf(detailDTO.getInQty())
.multiply(BigDecimal.valueOf(100))
.divide(BigDecimal.valueOf(detailDTO.getPlanQty()), 1, RoundingMode.HALF_UP);
if (progress.compareTo(BigDecimal.ZERO) < 0) {
progress = BigDecimal.ZERO;
} else if (progress.compareTo(BigDecimal.valueOf(100)) > 0) {
progress = BigDecimal.valueOf(100);
}
detailDTO.setProgress(progress.doubleValue());
}
// 状态映射
String statusStr = detail.getOperStatus();
if ("1".equals(statusStr)) detailDTO.setK3Status("计划");
else if ("2".equals(statusStr)) detailDTO.setK3Status("计划下达");
else if ("3".equals(statusStr)) detailDTO.setK3Status("开工");
else if ("4".equals(statusStr)) detailDTO.setK3Status("已完工");
else if ("5".equals(statusStr)) detailDTO.setK3Status("结案");
else if ("6".equals(statusStr)) detailDTO.setK3Status("结算");
else detailDTO.setK3Status(statusStr);
double inQty = detailDTO.getInQty() == null ? 0D : detailDTO.getInQty();
double okQty = detailDTO.getOkQty() == null ? 0D : detailDTO.getOkQty();
double ngQty = detailDTO.getNgQty() == null ? 0D : detailDTO.getNgQty();
double transOutQty = detailDTO.getTransOutQty() == null ? 0D : detailDTO.getTransOutQty();
if ("5".equals(statusStr) || "6".equals(statusStr)) {
detailDTO.setStatus("已关闭");
} else if (inQty == 0D) {
detailDTO.setStatus("未开工");
} else if (transOutQty > 0D) {
detailDTO.setStatus("已转出");
} else if (okQty + ngQty >= inQty) {
detailDTO.setStatus("工序完成");
} else if (okQty + ngQty > 0D) {
detailDTO.setStatus("质检中");
} else {
detailDTO.setStatus("生产中");
}
detailDTO.setSeqPlanStartTime(detail.getSeqPlanStartTime() != null ? DateUtil.formatDateTime(detail.getSeqPlanStartTime()) : null);
detailDTO.setSeqPlanFinishTime(detail.getSeqPlanFinishTime() != null ? DateUtil.formatDateTime(detail.getSeqPlanFinishTime()) : null);
detailDTO.setOperPlanStartTime(detail.getOperPlanStartTime() != null ? DateUtil.formatDateTime(detail.getOperPlanStartTime()) : null);
detailDTO.setOperPlanFinishTime(detail.getOperPlanFinishTime() != null ? DateUtil.formatDateTime(detail.getOperPlanFinishTime()) : null);
detailDTO.setOptCtrlCode(detail.getOptCtrlCode());
detailDTO.setKeyOper(detail.getKeyOper());
detailDTO.setActivity1Name(detail.getActivityName());
detailDTO.setOperDescription(detail.getOperDescription());
detailDTO.setEquipment(detail.getEquipment());
detailDTO.setK3DetailId(detail.getK3DetailId());
detailDTO.setEntityEntryId(detail.getEntityEntryId());
processList.add(detailDTO);
}
resultDTO.setWorkCenter(mainWorkCenter);
resultDTO.setOverallPlanQty(totalPlanQty);
resultDTO.setOverallOkQty(totalOkQty);
double progress = 0.0;
if (totalPlanQty > 0) {
progress = (totalOkQty / totalPlanQty) * 100;
}
resultDTO.setOverallProgress(Math.min(100.0, Math.max(0.0, progress)));
// 计算总体状态
if (relatedDetails.isEmpty() || (finishedCount == 0 && totalOkQty == 0)) {
resultDTO.setOverallStatus("未开工");
} else if (finishedCount == relatedDetails.size()) {
resultDTO.setOverallStatus("已完工");
} else {
resultDTO.setOverallStatus("生产中");
}
}
resultDTO.setProcessList(processList);
resultList.add(resultDTO);
}
// 对当前页结果进行排序先按生产令号升序再按计划开始时间升序
resultList.sort(Comparator
.comparing(OperationPlanResultDTO::getBatchNo, Comparator.nullsLast(String::compareTo))
.thenComparing(OperationPlanResultDTO::getRawPlanStartTime, Comparator.nullsLast(Date::compareTo)));
// 构造返回带 total TableDataInfo // 构造返回带 total TableDataInfo
TableDataInfo<OperationPlanResultDTO> rspData = new TableDataInfo<>(); TableDataInfo<OperationPlanResultDTO> rspData = new TableDataInfo<>();
@ -559,7 +556,13 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
lqw.eq(bo.getFid() != null, SfcOperationPlanningMain::getFid, bo.getFid()); lqw.eq(bo.getFid() != null, SfcOperationPlanningMain::getFid, bo.getFid());
lqw.eq(StringUtils.isNotBlank(bo.getBillNo()), SfcOperationPlanningMain::getBillNo, bo.getBillNo()); lqw.eq(StringUtils.isNotBlank(bo.getBillNo()), SfcOperationPlanningMain::getBillNo, bo.getBillNo());
lqw.eq(StringUtils.isNotBlank(bo.getMoNumber()), SfcOperationPlanningMain::getMoNumber, bo.getMoNumber()); lqw.eq(StringUtils.isNotBlank(bo.getMoNumber()), SfcOperationPlanningMain::getMoNumber, bo.getMoNumber());
lqw.eq(StringUtils.isNotBlank(bo.getHbytSclh()), SfcOperationPlanningMain::getHbytSclh, bo.getHbytSclh()); if (StringUtils.isNotBlank(bo.getHbytSclh())) {
String keyword = bo.getHbytSclh().trim();
lqw.and(w -> w.eq(SfcOperationPlanningMain::getHbytSclh, keyword)
.or()
.like(SfcOperationPlanningMain::getFHbytXmh, keyword));
}
lqw.like(StringUtils.isNotBlank(bo.getFHbytXmh()), SfcOperationPlanningMain::getFHbytXmh, bo.getFHbytXmh());
lqw.eq(StringUtils.isNotBlank(bo.getProductCode()), SfcOperationPlanningMain::getProductCode, bo.getProductCode()); lqw.eq(StringUtils.isNotBlank(bo.getProductCode()), SfcOperationPlanningMain::getProductCode, bo.getProductCode());
lqw.like(StringUtils.isNotBlank(bo.getProductName()), SfcOperationPlanningMain::getProductName, bo.getProductName()); lqw.like(StringUtils.isNotBlank(bo.getProductName()), SfcOperationPlanningMain::getProductName, bo.getProductName());
lqw.eq(StringUtils.isNotBlank(bo.getProSpecification()), SfcOperationPlanningMain::getProSpecification, bo.getProSpecification()); lqw.eq(StringUtils.isNotBlank(bo.getProSpecification()), SfcOperationPlanningMain::getProSpecification, bo.getProSpecification());

View File

@ -21,6 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="unit" column="unit"/> <result property="unit" column="unit"/>
<result property="quantity" column="quantity"/> <result property="quantity" column="quantity"/>
<result property="isEnterpriseStandard" column="is_enterprise_standard"/> <result property="isEnterpriseStandard" column="is_enterprise_standard"/>
<result property="isDownload" column="is_download"/>
<result property="drawingPath" column="drawing_path"/> <result property="drawingPath" column="drawing_path"/>
<result property="bomStatus" column="bom_status"/> <result property="bomStatus" column="bom_status"/>
<result property="routeStatus" column="route_status"/> <result property="routeStatus" column="route_status"/>
@ -35,6 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="drawingTime" column="drawing_time"/> <result property="drawingTime" column="drawing_time"/>
<result property="auditCardId" column="audit_card_id"/> <result property="auditCardId" column="audit_card_id"/>
<result property="notificationCardId" column="notification_card_id"/> <result property="notificationCardId" column="notification_card_id"/>
<result property="routePlanSendCount" column="route_plan_send_count"/>
</resultMap> </resultMap>
<!-- 根据项目编号查询 --> <!-- 根据项目编号查询 -->
<select id="selectByProjectNumber" resultType="com.ruoyi.system.domain.ProcessOrderPro"> <select id="selectByProjectNumber" resultType="com.ruoyi.system.domain.ProcessOrderPro">

View File

@ -20,8 +20,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateBy" column="update_by"/> <result property="updateBy" column="update_by"/>
<result property="createTime" column="create_time"/> <result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/> <result property="updateTime" column="update_time"/>
<result property="mainName" column="main_name"/>
<result property="mainPart" column="main_part"/>
<result property="batchQuantity" column="batch_quantity"/> <result property="batchQuantity" column="batch_quantity"/>
</resultMap> </resultMap>
<select id="selectWeightByDrawingNos" resultType="com.ruoyi.system.domain.ProductionOrder">
select p.drawing_no as drawingNo,
p.single_weight as singleWeight
from production_order p
inner join (
select drawing_no, max(id) as max_id
from production_order
where drawing_no in
<foreach collection="drawingNos" item="drawingNo" open="(" separator="," close=")">
#{drawingNo}
</foreach>
group by drawing_no
) t on p.id = t.max_id
where p.single_weight is not null
</select>
<select id="selectDiscWeightByMaterialCodes" resultType="com.ruoyi.system.domain.ProductionOrder">
select p.material_code as drawingNo,
p.disc_weight as singleWeight
from process_route p
inner join (
select material_code, max(id) as max_id
from process_route
where material_code in
<foreach collection="materialCodes" item="materialCode" open="(" separator="," close=")">
#{materialCode}
</foreach>
group by material_code
) t on p.id = t.max_id
where p.disc_weight is not null
</select>
</mapper> </mapper>

View File

@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="billNo" column="bill_no"/> <result property="billNo" column="bill_no"/>
<result property="moNumber" column="mo_number"/> <result property="moNumber" column="mo_number"/>
<result property="hbytSclh" column="hbyt_sclh"/> <result property="hbytSclh" column="hbyt_sclh"/>
<result property="fHbytXmh" column="hbyt_xmh"/>
<result property="productCode" column="product_code"/> <result property="productCode" column="product_code"/>
<result property="productName" column="product_name"/> <result property="productName" column="product_name"/>
<result property="proSpecification" column="pro_specification"/> <result property="proSpecification" column="pro_specification"/>

View File

@ -94,7 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND d.work_center_name = #{workCenterName} AND d.work_center_name = #{workCenterName}
</if> </if>
GROUP BY m.mo_number GROUP BY m.mo_number
ORDER BY m.plan_start_time DESC, progress DESC ORDER BY MAX(m.plan_start_time) DESC, progress DESC
LIMIT #{topN} LIMIT #{topN}
</select> </select>