jindie xiangguangognneng

This commit is contained in:
tzy 2026-01-19 19:31:06 +08:00
parent a83e250c37
commit eafb53c54d
31 changed files with 1180 additions and 125 deletions

View File

@ -35,7 +35,7 @@
<dynamic-ds.version>3.5.2</dynamic-ds.version> <dynamic-ds.version>3.5.2</dynamic-ds.version>
<alibaba-ttl.version>2.14.2</alibaba-ttl.version> <alibaba-ttl.version>2.14.2</alibaba-ttl.version>
<xxl-job.version>2.4.0</xxl-job.version> <xxl-job.version>2.4.0</xxl-job.version>
<lombok.version>1.18.26</lombok.version> <lombok.version>1.18.30</lombok.version>
<bouncycastle.version>1.72</bouncycastle.version> <bouncycastle.version>1.72</bouncycastle.version>
<!-- 离线IP地址定位库 --> <!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>2.7.0</ip2region.version>

View File

@ -110,10 +110,20 @@
<dependency> <dependency>
<groupId>com.aliyun</groupId> <groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId> <artifactId>dingtalk</artifactId>
<version>2.2.41</version> <version>2.0.15</version>
</dependency> </dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>

View File

@ -0,0 +1,107 @@
package com.ruoyi.web.controller.dingtalk;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.dingding.DingUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@RestController
@RequestMapping("/ding/talk")
public class DingTalkController {
/**
* 获取参数配置列表
*/
@GetMapping("/list")
public R<String> uploadMedia(String filePath) {
String appKey = "dingebfrpzqko25vo8w6";
String appSecret = "M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk";
// 如果未提供 filePath使用默认值
if (filePath == null || filePath.isEmpty()) {
filePath = "E:/新建文件夹 (2)/4eae3472-de71-4f4b-97cc-4212a88e17c8.png";
}
String accessToken = DingUtil.getAccessToken(appKey, appSecret);
// type 固定为 image也可以根据文件名后缀判断
String mediaId = DingUtil.uploadMedia(accessToken, "image", filePath);
return R.ok("上传成功", mediaId);
}
/**
* 发送互动卡片消息 (IM 1.0)
*/
@GetMapping("/sendCard")
public R<String> sendCard() {
String appKey = "dingebfrpzqko25vo8w6";
String appSecret = "M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk";
String robotCode = "dingebfrpzqko25vo8w6";
String openConversationId = "cidCzwPP4a1xhnl9c8hrGXFuw==";
String cardTemplateId = "2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema";
// 获取 AccessToken
String accessToken = DingUtil.getAccessToken(appKey, appSecret);
// 生成唯一 outTrackId
String outTrackId = "group-card-" + System.currentTimeMillis();
// 构造卡片数据
Map<String, String> cardDataMap = new HashMap<>();
cardDataMap.put("drawing_time", "2026/1/13 11:03");
cardDataMap.put("production_order_no", "CP-026-054-023");
cardDataMap.put("drawing_by", "毕敏霞");
cardDataMap.put("bom_uptime", "2025/12/13 11:03");
cardDataMap.put("drawing_type", "60R");
cardDataMap.put("project_end_time", "2026/2/13 11:03");
cardDataMap.put("route_uptime", "2025/12/13 11:03");
cardDataMap.put("material_count", "2312");
cardDataMap.put("route_count", "100");
cardDataMap.put("plan_uptime", "2024/12/13 11:03");
cardDataMap.put("bom_count", "121");
cardDataMap.put("img_id", "@lALPM137gRk79qPNA9fNBT4");
String trackId = DingUtil.createAndDeliverCard(
accessToken,
cardTemplateId,
robotCode,
openConversationId,
outTrackId,
cardDataMap
);
return R.ok("发送卡片成功", trackId);
}
/**
* 机器人发送群聊消息
*/
@GetMapping("/sendRobotMessage")
public R<String> sendRobotMessage() {
String appKey = "dingebfrpzqko25vo8w6";
String appSecret = "M_hoza71hmEqsbbeXM-7MBg67EINqi9C6mKj4zWKQysXN-68GCYYc5DZoV0hXVvk";
//机器人编码
String robotCode = "dingebfrpzqko25vo8w6";
//群回话的id
String openConversationId = "cidCzwPP4a1xhnl9c8hrGXFuw==";
//消息类型
String msgKey = "sampleText";
// 获取 AccessToken
String accessToken = DingUtil.getAccessToken(appKey, appSecret);
Map<String, Object> msgContent = new HashMap<>();
msgContent.put("content", "Hello");
// 调用机器人发送消息方法
String processQueryKey = DingUtil.robotGroupSend(accessToken, msgContent, msgKey, openConversationId, robotCode, null);
return R.ok("发送成功", processQueryKey);
}
}

View File

@ -65,7 +65,7 @@ spring:
sqlserver: sqlserver:
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.8:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true url: jdbc:sqlserver://192.168.5.8:14330;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
username: sa username: sa
password: 1a! password: 1a!
hikari: hikari:

View File

@ -68,7 +68,7 @@ spring:
sqlserver: sqlserver:
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.8:1433;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true url: jdbc:sqlserver://192.168.5.8:14330;databaseName=AIS20241010133631;encrypt=false;trustServerCertificate=true
username: sa username: sa
password: 1a! password: 1a!
# oracle: # oracle:

View File

@ -290,4 +290,4 @@ management:
logfile: logfile:
external-file: ./logs/sys-console.log external-file: ./logs/sys-console.log
dingtalk: dingtalk:

View File

@ -1,6 +1,5 @@
#??ID-PROD #??ID-PROD
X-KDApi-AcctID = 670768a85463de X-KDApi-AcctID = 695f86f96090b2
#X-KDApi-AcctID = 6723465a38c722
X-KDApi-UserName = Administrator X-KDApi-UserName = Administrator
#??IDID #??IDID
X-KDApi-AppID = 288012_Rc0C0zCG2lga0/Vs2Y4pzYSL6hQcWOko X-KDApi-AppID = 288012_Rc0C0zCG2lga0/Vs2Y4pzYSL6hQcWOko

View File

@ -266,6 +266,18 @@
<artifactId>commons-net</artifactId> <artifactId>commons-net</artifactId>
<version>3.6</version> <version>3.6</version>
</dependency> </dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>2.0.15</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,35 @@
package com.ruoyi.common.dingding;
import lombok.Data;
/**
* 创建群聊返回参数DTO
*/
@Data
public class DingChatCreateResult {
/**
* 返回码
*/
private Long errcode;
/**
* 返回码描述
*/
private String errmsg;
/**
* 群会话的ID (旧版)
* 后续版本中chatid将不再使用请将openConversationId作为群会话唯一标识
*/
private String chatid;
/**
* 群会话的ID
*/
private String openConversationId;
/**
* 会话类型2企业群
*/
private Long conversationTag;
}

View File

@ -0,0 +1,401 @@
package com.ruoyi.common.dingding;
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardHeaders;
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest;
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardResponse;
import com.aliyun.dingtalkim_1_0.Client;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders;
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendRequest;
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendResponse;
import com.aliyun.tea.TeaConverter;
import com.aliyun.tea.TeaPair;
import com.aliyun.teaopenapi.models.Config;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiChatCreateRequest;
import com.dingtalk.api.response.OapiChatCreateResponse;
import com.dingtalk.api.request.OapiMediaUploadRequest;
import com.dingtalk.api.response.OapiMediaUploadResponse;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DingUtil {
public static final String CUSTOM_ROBOT_TOKEN = "1f3711d72605f77e8ba8e3e3fe0d98989361a7bfb9c30b51a23520eeb783652e";
public static final String USER_ID = "4345285524672471";
public static final String SECRET = "SECae018c965ccba100318cc2cd5ef7df2e7a3d0379cf96ef39614fd9558209f18c";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
// 固定模板和群 ID
private static final String CARD_TEMPLATE_ID = "2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema";
private static final String OPEN_CONVERSATION_ID = "cidCzwPP4a1xhnl9c8hrGXFuw==";
/**
* 发送钉钉文本消息
*
* @param content 消息内容
* @param atUserIds 需要@的用户ID列表可选传null则不@特定人
* @param isAtAll 是否@所有人
*/
public static void sendText(String content, List<String> atUserIds, boolean isAtAll) {
try {
Long timestamp = System.currentTimeMillis();
String stringToSign = timestamp + "\n" + SECRET;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(SECRET.getBytes("UTF-8"), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
// sign字段和timestamp字段必须拼接到请求URL上
String serverUrl = "https://oapi.dingtalk.com/robot/send?sign=" + sign + "&timestamp=" + timestamp;
DingTalkClient client = new DefaultDingTalkClient(serverUrl);
OapiRobotSendRequest req = new OapiRobotSendRequest();
// 设置消息类型
req.setMsgtype("text");
// 定义文本内容
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(content);
req.setText(text);
// 定义 @ 对象
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
if (atUserIds != null && !atUserIds.isEmpty()) {
at.setAtUserIds(atUserIds);
}
if (isAtAll) {
at.setIsAtAll(true);
}
req.setAt(at);
OapiRobotSendResponse rsp = client.execute(req, CUSTOM_ROBOT_TOKEN);
System.out.println("DingTalk Response: " + rsp.getBody());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("发送钉钉消息失败", e);
}
}
/**
* 使用 Token 初始化账号Client (IM)
* @return Client
* @throws Exception
*/
public static com.aliyun.dingtalkim_1_0.Client createImClient() throws Exception {
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
return new com.aliyun.dingtalkim_1_0.Client(config);
}
/**
* 使用 Token 初始化账号Client (OAuth2)
* @return Client
* @throws Exception
*/
public static com.aliyun.dingtalkoauth2_1_0.Client createOauthClient() throws Exception {
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
return new com.aliyun.dingtalkoauth2_1_0.Client(config);
}
/**
* 获取企业内部应用的accessToken
* @param appKey
* @param appSecret
* @return
*/
public static String getAccessToken(String appKey, String appSecret) {
try {
com.aliyun.dingtalkoauth2_1_0.Client client = createOauthClient();
GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
.setAppKey(appKey)
.setAppSecret(appSecret);
GetAccessTokenResponse response = client.getAccessToken(getAccessTokenRequest);
return response.getBody().getAccessToken();
} catch (com.aliyun.tea.TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code message 属性可帮助开发定位问题
System.err.println("TeaException: " + err.code + ", " + err.message);
}
throw new RuntimeException(err);
} catch (Exception _err) {
com.aliyun.tea.TeaException err = new com.aliyun.tea.TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
// err 中含有 code message 属性可帮助开发定位问题
System.err.println("Exception: " + err.code + ", " + err.message);
}
throw new RuntimeException(_err);
}
}
/**
* 上传媒体文件
* @param accessToken
* @param type 媒体文件类型image, voice, video, file
* @param filePath 文件路径
* @return media_id
*/
public static String uploadMedia(String accessToken, String type, String filePath) {
try {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/media/upload");
OapiMediaUploadRequest req = new OapiMediaUploadRequest();
req.setType(type);
com.taobao.api.FileItem item = new com.taobao.api.FileItem(filePath);
req.setMedia(item);
OapiMediaUploadResponse rsp = client.execute(req, accessToken);
if (rsp.isSuccess()) {
return rsp.getMediaId();
} else {
throw new RuntimeException("上传媒体文件失败: " + rsp.getErrmsg());
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("上传媒体文件异常", e);
}
}
/**
* 创建群聊
* @param accessToken
* @param name 群名称
* @param owner 群主userid
* @param userIdList 群成员userid列表
* @return DingChatCreateResult
*/
public static DingChatCreateResult createChat(String accessToken, String name, String owner, List<String> userIdList) {
try {
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/chat/create");
OapiChatCreateRequest req = new OapiChatCreateRequest();
//群名称
req.setName(name);
//群主的userId
req.setOwner(owner);
//群成员列表
req.setUseridlist(userIdList);
/*
新成员是否可查看100条历史消息1可查看 0不可查看
*/
req.setShowHistoryType(1L);
/*
群是否可以被搜索0默认不可搜索 1可搜索
*/
req.setSearchable(0L);
//入群是否需要验证
req.setValidationType(0L);
//@all 使用范围
req.setMentionAllAuthority(0L);
//群管理类型1仅群主可管理
req.setManagementType(1L);
//是否开启群禁言
req.setChatBannedType(0L);
OapiChatCreateResponse rsp = client.execute(req, accessToken);
if (rsp.isSuccess()) {
DingChatCreateResult result = new DingChatCreateResult();
result.setErrcode(rsp.getErrcode());
result.setErrmsg(rsp.getErrmsg());
result.setChatid(rsp.getChatid());
result.setOpenConversationId(rsp.getOpenConversationId());
result.setConversationTag(rsp.getConversationTag());
return result;
} else {
throw new RuntimeException("创建群聊失败: " + rsp.getErrmsg());
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("创建群聊异常", e);
}
}
/**
* 机器人发送群聊消息支持 Map 直接传入
* @param accessToken AccessToken
* @param msgContent 消息内容 Map
* @param msgKey 消息模板 Key
* @param openConversationId ID
* @param robotCode 机器人 Code
* @param coolAppCode 酷应用 Code (可选)
* @return processQueryKey
*/
public static String robotGroupSend(
String accessToken,
Map<String, Object> msgContent,
String msgKey,
String openConversationId,
String robotCode,
String coolAppCode) {
try {
// 1. 初始化 SDK 客户端
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.protocol = "https";
config.regionId = "central";
com.aliyun.dingtalkrobot_1_0.Client client = new com.aliyun.dingtalkrobot_1_0.Client(config);
// 2. 设置请求头
OrgGroupSendHeaders headers = new OrgGroupSendHeaders();
headers.xAcsDingtalkAccessToken = accessToken;
// 3. Map JSON 字符串
String msgParam = OBJECT_MAPPER.writeValueAsString(msgContent);
// 4. 构建消息请求对象
OrgGroupSendRequest request = new OrgGroupSendRequest()
.setMsgParam(msgParam)
.setMsgKey(msgKey)
.setOpenConversationId(openConversationId)
.setRobotCode(robotCode);
if (coolAppCode != null && !coolAppCode.isEmpty()) {
request.setCoolAppCode(coolAppCode);
}
// 5. 发送消息
OrgGroupSendResponse response = client.orgGroupSendWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
return response.getBody().getProcessQueryKey();
} catch (com.aliyun.tea.TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
System.err.println("TeaException: " + err.code + ", " + err.message);
}
throw new RuntimeException(err);
} catch (Exception e) {
com.aliyun.tea.TeaException err = new com.aliyun.tea.TeaException(e.getMessage(), e);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
System.err.println("Exception: " + err.code + ", " + err.message);
}
throw new RuntimeException(e);
}
}
/**
* 发送互动消息卡片 (IM 1.0)
* @param accessToken AccessToken
* @param cardTemplateId 卡片模板 ID
* @param robotCode 机器人 Code
* @param openConversationId ID (可选如果发送到群)
* @param outTrackId 外部跟踪 ID
* @param cardDataMap 卡片数据 Map (key-value)
* @return outTrackId
*/
public static String createAndDeliverCard(String accessToken, String cardTemplateId, String robotCode,
String openConversationId, String outTrackId,
Map<String, String> cardDataMap) {
try {
com.aliyun.dingtalkim_1_0.Client client = createImClient();
SendInteractiveCardHeaders sendInteractiveCardHeaders = new SendInteractiveCardHeaders();
sendInteractiveCardHeaders.xAcsDingtalkAccessToken = accessToken;
SendInteractiveCardRequest.SendInteractiveCardRequestCardOptions cardOptions = new SendInteractiveCardRequest.SendInteractiveCardRequestCardOptions()
.setSupportForward(true);
// 转换数据为 Tea 格式 Map (虽然直接传 Map<String, String> 也是兼容的但为了稳妥可以使用 buildMap)
// 这里直接使用传入的 cardDataMap
SendInteractiveCardRequest.SendInteractiveCardRequestCardData cardData = new SendInteractiveCardRequest.SendInteractiveCardRequestCardData()
.setCardParamMap(cardDataMap);
SendInteractiveCardRequest sendInteractiveCardRequest = new SendInteractiveCardRequest()
.setConversationType(1) // 1: 群聊
.setCardData(cardData)
.setCardTemplateId(cardTemplateId)
.setUserIdType(1)
.setOutTrackId(outTrackId)
.setPullStrategy(false)
.setRobotCode(robotCode)
.setOpenConversationId(openConversationId)
.setCardOptions(cardOptions);
client.sendInteractiveCardWithOptions(sendInteractiveCardRequest, sendInteractiveCardHeaders, new com.aliyun.teautil.models.RuntimeOptions());
return outTrackId;
} catch (com.aliyun.tea.TeaException err) {
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
System.err.println("TeaException: " + err.code + ", " + err.message);
}
throw new RuntimeException(err);
} catch (Exception _err) {
com.aliyun.tea.TeaException err = new com.aliyun.tea.TeaException(_err.getMessage(), _err);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
System.err.println("Exception: " + err.code + ", " + err.message);
}
throw new RuntimeException(_err);
}
}
/**
* @description 由于接口要求卡片数据的键值对均为 string 类型需要对卡片数据进行预处理
*/
public static Map<String, String> convertJsonValuesToString(Map<String, Object> obj) {
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, Object> entry : obj.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof String) {
result.put(key, (String) value);
} else {
try {
result.put(key, OBJECT_MAPPER.writeValueAsString(value));
} catch (JsonProcessingException e) {
result.put(key, "");
}
}
}
return result;
}
/**
* 推送群卡片高级版
* @param cardParamMap 模板变量映射例如 production_order_nobom_count
* @return outTrackId 返回卡片幂等 ID
*/
public static String pushGroupCard(Map<String, String> cardParamMap) {
try {
// 获取 AccessToken
String accessToken = getAccessToken(CUSTOM_ROBOT_TOKEN, SECRET); // 注意这里可能需要改为 AppKey/AppSecret
// 生成唯一 outTrackId
String outTrackId = "track-" + System.currentTimeMillis();
// 调用已写好的 createAndDeliverCard 方法
return createAndDeliverCard(
accessToken,
CARD_TEMPLATE_ID,
CUSTOM_ROBOT_TOKEN, // 这里的 robotCode 可能需要根据实际情况调整
OPEN_CONVERSATION_ID,
outTrackId,
cardParamMap
);
} catch (Exception e) {
throw new RuntimeException("推送群卡片失败", e);
}
}
public static void main(String[] args) {
// 测试发送
sendText("测试消息:刘开~~~,刘开~~~刘开~~~刘开~~~刘开~~~", Arrays.asList(USER_ID), false);
}
}

View File

@ -579,7 +579,7 @@ public class FtpUtil {
} }
/** /**
* 下载FTP指定目录中的所有文件到本地 * 下载FTP指定目录中的所有文件到本地支持文件名过滤
* *
* @param ftpHost FTP服务器IP * @param ftpHost FTP服务器IP
* @param ftpUserName FTP用户名 * @param ftpUserName FTP用户名
@ -587,11 +587,12 @@ public class FtpUtil {
* @param ftpPort FTP端口 * @param ftpPort FTP端口
* @param remoteDir 远程目录路径 * @param remoteDir 远程目录路径
* @param localDir 本地保存目录 * @param localDir 本地保存目录
* @param fileNameFilter 文件名过滤字符串只下载包含此字符串的文件传null或空字符串则下载所有
* @return 下载结果 * @return 下载结果
*/ */
public static R<String> downloadFtpDirectoryFiles(String ftpHost, String ftpUserName, public static R<String> downloadFtpDirectoryFiles(String ftpHost, String ftpUserName,
String ftpPassword, int ftpPort, String ftpPassword, int ftpPort,
String remoteDir, String localDir) { String remoteDir, String localDir, String fileNameFilter) {
FTPClient ftpClient = null; FTPClient ftpClient = null;
try { try {
// 1. 连接FTP服务器 // 1. 连接FTP服务器
@ -601,13 +602,18 @@ public class FtpUtil {
} }
// 2. 设置FTP参数 // 2. 设置FTP参数
ftpClient.setControlEncoding("UTF-8"); // 强制使用GBK编码避免Windows FTP服务器UTF-8兼容性问题导致的文件名乱码或451错误
ftpClient.setControlEncoding("GBK");
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode(); ftpClient.enterLocalPassiveMode();
// 3. 切换到远程目录 // 3. 切换到远程目录
if (!ftpClient.changeWorkingDirectory(remoteDir)) { if (!ftpClient.changeWorkingDirectory(remoteDir)) {
return R.fail("远程目录不存在: " + remoteDir); // 如果切换失败尝试用ISO-8859-1转码再试
String isoPath = new String(remoteDir.getBytes("GBK"), "ISO-8859-1");
if (!ftpClient.changeWorkingDirectory(isoPath)) {
return R.fail("远程目录不存在: " + remoteDir);
}
} }
// 4. 创建本地目录 // 4. 创建本地目录
@ -626,7 +632,7 @@ public class FtpUtil {
// 6. 遍历下载文件 // 6. 遍历下载文件
int successCount = 0; int successCount = 0;
int totalCount = files.length; int totalCount = 0; // 仅统计符合过滤条件的文件
StringBuilder errorMsg = new StringBuilder(); StringBuilder errorMsg = new StringBuilder();
for (FTPFile file : files) { for (FTPFile file : files) {
@ -636,6 +642,13 @@ public class FtpUtil {
if (fileName.startsWith(".") || file.isDirectory()) { if (fileName.startsWith(".") || file.isDirectory()) {
continue; continue;
} }
// 过滤文件名
if (fileNameFilter != null && !fileNameFilter.isEmpty() && !fileName.contains(fileNameFilter)) {
continue;
}
totalCount++; // 计入待下载总数
try { try {
// 构建本地文件路径 // 构建本地文件路径
@ -644,19 +657,37 @@ public class FtpUtil {
// 下载文件 // 下载文件
try (OutputStream os = new FileOutputStream(localFile)) { try (OutputStream os = new FileOutputStream(localFile)) {
// 尝试直接下载
boolean downloadSuccess = ftpClient.retrieveFile(fileName, os); boolean downloadSuccess = ftpClient.retrieveFile(fileName, os);
// 如果失败尝试转码下载 (GBK -> ISO-8859-1)
if (!downloadSuccess) {
String isoFileName = new String(fileName.getBytes("GBK"), "ISO-8859-1");
downloadSuccess = ftpClient.retrieveFile(isoFileName, os);
}
if (downloadSuccess) { if (downloadSuccess) {
successCount++; successCount++;
log.info("文件下载成功: {}", fileName); log.info("文件下载成功: {}", fileName);
} else { } else {
errorMsg.append("文件下载失败: ").append(fileName).append("; "); errorMsg.append("文件下载失败: ").append(fileName).append(" - ").append(ftpClient.getReplyString()).append("; ");
log.error("文件下载失败: {}", fileName); log.error("文件下载失败: {} - 响应: {}", fileName, ftpClient.getReplyString());
// 删除下载失败的空文件
os.close();
if (localFile.exists()) {
localFile.delete();
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
errorMsg.append("文件下载异常: ").append(fileName).append(" - ").append(e.getMessage()).append("; "); errorMsg.append("文件下载异常: ").append(fileName).append(" - ").append(e.getMessage()).append("; ");
log.error("文件下载异常: {}", fileName, e); log.error("文件下载异常: {}", fileName, e);
// 删除异常的空文件
File localFile = new File(localDir + File.separator + fileName);
if (localFile.exists()) {
localFile.delete();
}
} }
} }
@ -684,6 +715,15 @@ public class FtpUtil {
} }
} }
/**
* 下载FTP指定目录中的所有文件到本地兼容旧方法调用默认下载所有
*/
public static R<String> downloadFtpDirectoryFiles(String ftpHost, String ftpUserName,
String ftpPassword, int ftpPort,
String remoteDir, String localDir) {
return downloadFtpDirectoryFiles(ftpHost, ftpUserName, ftpPassword, ftpPort, remoteDir, localDir, null);
}
/** /**
* 主方法 - 用于测试FTP目录文件下载功能 * 主方法 - 用于测试FTP目录文件下载功能
*/ */
@ -695,7 +735,7 @@ public class FtpUtil {
int ftpPort = 21; int ftpPort = 21;
// 远程目录和本地目录配置 // 远程目录和本地目录配置
String remoteDir = "/FB-25-039-FS-01/40S-R-2720-T(FS039-25)-PDF"; // 远程目录路径 String remoteDir = "2026/SH-26-001-LT/T40P1-L-1440-D-PDF"; // 远程目录路径
String localDir = "F:/DownloadedFiles"; // 本地保存目录 String localDir = "F:/DownloadedFiles"; // 本地保存目录
System.out.println("开始下载FTP文件..."); System.out.println("开始下载FTP文件...");

View File

@ -1701,10 +1701,10 @@ public class BomDetailsController extends BaseController {
} }
subHeadEntity5.addProperty("FIssueType", "1"); subHeadEntity5.addProperty("FIssueType", "1");
// 创建FPickStockId对象并加入SubHeadEntity5 /*// 创建FPickStockId对象并加入SubHeadEntity5
JsonObject fPickStockId = new JsonObject(); JsonObject fPickStockId = new JsonObject();
fPickStockId.addProperty("FNumber", " 010"); fPickStockId.addProperty("FNumber", " 010");
subHeadEntity1.add("FPickStockId", fPickStockId); subHeadEntity1.add("FPickStockId", fPickStockId);*/
subHeadEntity5.addProperty("FOverControlMode", "1"); subHeadEntity5.addProperty("FOverControlMode", "1");
// 标准人员实作工时 // 标准人员实作工时

View File

@ -11,10 +11,7 @@ import com.ruoyi.common.utils.JdUtils;
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.bo.ImProductionPlanBo; import com.ruoyi.system.domain.bo.ImProductionPlanBo;
import com.ruoyi.system.domain.dto.JDInventoryDTO; import com.ruoyi.system.domain.dto.*;
import com.ruoyi.system.domain.dto.JDProductionDTO;
import com.ruoyi.system.domain.dto.ProMoDTO;
import com.ruoyi.system.domain.dto.PurchaseOrderDTO;
import com.ruoyi.system.domain.vo.ImMaterialVo; import com.ruoyi.system.domain.vo.ImMaterialVo;
import com.ruoyi.system.domain.vo.InventoryInfoVO; import com.ruoyi.system.domain.vo.InventoryInfoVO;
import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.runner.JdUtil;
@ -95,18 +92,27 @@ public class IndexController {
.sum()) .sum())
.orElse(0.0); .orElse(0.0);
}, executorService); }, executorService);
CompletableFuture<Double> salesOrderFuture = CompletableFuture.supplyAsync(() -> {
log.info("开始查询销售订单未入库数量: {}", materialCode);
return Optional.ofNullable(JdUtil.getSalesOrderList(materialCode))
.map(list -> list.stream()
.mapToDouble(SalesOrderDTO::getFBaseRemainOutQty)
.sum())
.orElse(0.0);
}, executorService);
// 等待所有任务完成并获取结果 // 等待所有任务完成并获取结果
CompletableFuture<InventoryInfoVO> resultFuture = CompletableFuture.allOf(inventoryFuture, noPickedFuture, productionFuture, purchaseFuture) CompletableFuture<InventoryInfoVO> resultFuture = CompletableFuture.allOf(inventoryFuture, noPickedFuture, productionFuture, purchaseFuture,salesOrderFuture)
.thenApply(v -> { .thenApply(v -> {
try { try {
double inventoryQty = inventoryFuture.get(); double inventoryQty = inventoryFuture.get();
double fNoPickedQty = noPickedFuture.get(); double fNoPickedQty = noPickedFuture.get();
double productionQty = productionFuture.get(); double productionQty = productionFuture.get();
double purchaseQty = purchaseFuture.get(); double purchaseQty = purchaseFuture.get();
double salesOrder = salesOrderFuture.get();
// 计算预计可用库存 即时库存+生产未入库+(采购申请-采购未入库)-子项未领料 // 计算预计可用库存 即时库存+生产未入库+(采购申请-采购未入库)-子项未领料-销售实际未出库数量
double keyong = inventoryQty + productionQty + purchaseQty - fNoPickedQty; double keyong = inventoryQty + productionQty + purchaseQty - fNoPickedQty-salesOrder;
InventoryInfoVO inventoryInfoVO = new InventoryInfoVO(); InventoryInfoVO inventoryInfoVO = new InventoryInfoVO();
inventoryInfoVO.setMaterialCode(materialCode); inventoryInfoVO.setMaterialCode(materialCode);
inventoryInfoVO.setKucun(String.valueOf(inventoryQty)); inventoryInfoVO.setKucun(String.valueOf(inventoryQty));
@ -114,6 +120,7 @@ public class IndexController {
inventoryInfoVO.setMaterialName(String.valueOf(purchaseQty)); inventoryInfoVO.setMaterialName(String.valueOf(purchaseQty));
inventoryInfoVO.setStockName(String.valueOf(productionQty)); inventoryInfoVO.setStockName(String.valueOf(productionQty));
inventoryInfoVO.setStockUnit(String.valueOf(keyong)); inventoryInfoVO.setStockUnit(String.valueOf(keyong));
inventoryInfoVO.setSalesOrder(String.valueOf(salesOrder));
return inventoryInfoVO; return inventoryInfoVO;
} catch (Exception e) { } catch (Exception e) {

View File

@ -456,29 +456,9 @@ public class KingdeeWorkCenterDataController extends BaseController {
@PostMapping("/getKingdeeDelayData") @PostMapping("/getKingdeeDelayData")
public R<List<KingdeeWorkCenterDataBo>> getKingdeeDelayData(@RequestParam(value = "workCenter") String workCenter) { public R<List<KingdeeWorkCenterDataBo>> getKingdeeDelayData(@RequestParam(value = "workCenter") String workCenter) {
try { try {
/* K3CloudApi client = new K3CloudApi();
JsonObject parameter = new JsonObject();
List<KingdeeWorkCenterDataBo> kingdeeWorkCenterDataVos = new ArrayList<>();
parameter.addProperty("FWorkCenterName", workCenter);
Object[] parameters = new Object[]{parameter.toString()};
String execute = client.execute("Ljint.Kingdee.YiTe.KanBan.WebApi.ProduceWebApi.ExecuteService,Ljint.Kingdee.YiTe.KanBan.WebApi", parameters);
log.info("金蝶接口:" + workCenter + "===> 返回数据: {}", execute);
// 解析响应
JSONObject response = JSONObject.parseObject(execute);
if (!"true".equals(response.getString("IsSuccess"))) {
String errorMsg = response.getString("Message");
return R.fail("获取工段数据失败:" + errorMsg);
}*/
// 获取明天的日期字符串 (格式: yyyy-MM-dd) // 获取明天的日期字符串 (格式: yyyy-MM-dd)
String yesterday = DateUtil.format(DateUtil.yesterday(), "yyyy-MM-dd"); String yesterday = DateUtil.format(DateUtil.yesterday(), "yyyy-MM-dd");
/* // 获取数据数组
JSONArray dataArray = response.getJSONArray("data");
if (dataArray == null || dataArray.isEmpty()) {
return R.ok("无数据");
}
*/
List<KingdeeWorkCenterDataBo> kingdeeProduceData = mssqlQueryService.getKingdeeProduceData(workCenter); List<KingdeeWorkCenterDataBo> kingdeeProduceData = mssqlQueryService.getKingdeeProduceData(workCenter);
List<KingdeeWorkCenterDataBo> kingdeeWorkCenterDataVos = new ArrayList<>(); List<KingdeeWorkCenterDataBo> kingdeeWorkCenterDataVos = new ArrayList<>();
for (KingdeeWorkCenterDataBo kingnum : kingdeeProduceData) { for (KingdeeWorkCenterDataBo kingnum : kingdeeProduceData) {

View File

@ -3,13 +3,20 @@ package com.ruoyi.system.controller;
import java.io.*; import java.io.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException; 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.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;
@ -113,6 +120,11 @@ public class ProcessOrderProController extends BaseController {
return R.ok(iProcessOrderProService.queryById(id)); return R.ok(iProcessOrderProService.queryById(id));
} }
/**
* 钉钉数据同步 Webhook 地址
*/
private static final String DINGTALK_WEBHOOK_URL = "https://connector.dingtalk.com/webhook/flow/103694935fdc210503b10006";
/** /**
* 新增项目令号 * 新增项目令号
*/ */
@ -121,7 +133,24 @@ public class ProcessOrderProController extends BaseController {
@RepeatSubmit() @RepeatSubmit()
@PostMapping() @PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ProcessOrderProBo bo) { public R<Void> add(@Validated(AddGroup.class) @RequestBody ProcessOrderProBo bo) {
return toAjax(iProcessOrderProService.insertByBo(bo)); boolean result = iProcessOrderProService.insertByBo(bo);
if (result) {
// 异步同步数据到钉钉 Webhook
CompletableFuture.runAsync(() -> {
try {
// 将业务对象转换为 JSON 字符串
String jsonBody = JSONUtil.toJsonStr(bo);
System.out.println(jsonBody);
// 发送 POST 请求
String response = HttpUtil.post(DINGTALK_WEBHOOK_URL, jsonBody);
System.out.println("钉钉Webhook响应: " + response);
} catch (Exception e) {
// 仅记录日志不影响主流程
e.printStackTrace();
}
});
}
return toAjax(result);
} }
/** /**
@ -693,6 +722,8 @@ public class ProcessOrderProController extends BaseController {
// 单重 // 单重
vo.setBatchQuantity(getCellValueAsLong(row.getCell(18))); // 批次数量 vo.setBatchQuantity(getCellValueAsLong(row.getCell(18))); // 批次数量
vo.setUnitQuantity(getCellValueAsDouble(row.getCell(17))); // 批次数量 vo.setUnitQuantity(getCellValueAsDouble(row.getCell(17))); // 批次数量
vo.setXuEndTime(getCellValueAsDate(row.getCell(21))); // 批次数量
vo.setXuStartTime(getCellValueAsDate(row.getCell(20))); // 批次数量
resultList.add(vo); resultList.add(vo);
} }
@ -832,6 +863,42 @@ public class ProcessOrderProController extends BaseController {
} }
} }
/**
* 获取单元格的Date值
*/
private Date getCellValueAsDate(XSSFCell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case NUMERIC:
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
}
return null;
case STRING:
try {
String dateStr = cell.getStringCellValue();
if (dateStr == null || dateStr.trim().isEmpty()) {
return null;
}
// 尝试解析字符串日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
return sdf.parse(dateStr);
} catch (ParseException e) {
sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(dateStr);
}
} catch (Exception e) {
return null;
}
default:
return null;
}
}
/** /**
* 获取单元格的Long值 * 获取单元格的Long值
*/ */
@ -1672,17 +1739,70 @@ public class ProcessOrderProController extends BaseController {
try { try {
ProcessOrderPro orderPro = processOrderProMapper.selectById(id); ProcessOrderPro orderPro = processOrderProMapper.selectById(id);
// 下载Excel文件 // 下载Excel文件
SmbUtil.downloadExcelFiles(orderPro.getProductionOrderNo());
// 1. 解析年份
String productionCode = orderPro.getProductionOrderNo();
String year = "2025"; // 默认
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路径和本地路径
// 假设Excel文件在 /2026/SH-26-001-LT/ 目录下
String ftpPath = "/" + year + "/" + productionCode;
String localPath = "D:\\file\\";
// 3. 下载文件 (只下载汇总表.xlsx)
FtpUtil.downloadFtpDirectoryFiles("192.168.5.18", "admin", "hbyt2025", 21, ftpPath, localPath, "汇总表.xlsx");
// 构建文件路径 // 构建文件路径
String excelName = "D:\\file\\" + orderPro.getProductionOrderNo() + "汇总表.xlsx"; String excelName = "D:\\file\\" + orderPro.getProductionOrderNo() + "汇总表.xlsx";
String rawDataFile = "D:\\file\\RawDataTable.xlsx"; String rawDataFile = "D:\\file\\RawDataTable.xlsx";
File file = new File(excelName); File file = new File(excelName);
if (!file.exists()) { if (!file.exists() || file.length() == 0) {
throw new ServiceException("项目 " + orderPro.getProductionOrderNo() + " 未出图"); // 如果文件不存在或为空下载失败抛出业务异常
throw new ServiceException("项目 " + orderPro.getProductionOrderNo() + " 汇总表下载失败或不存在");
} }
// 1. 读取第一个sheet的数据list - 使用POI直接读取以保留空格 // 1. 读取第一个sheet的数据list - 使用POI直接读取以保留空格
List<ProductionOrderVo> allDataList = readExcelWithPOI(excelName,orderPro.getProductionOrderNo()); List<ProductionOrderVo> allDataList = readExcelWithPOI(excelName,orderPro.getProductionOrderNo());
List<ProcessRoute> routeList = readExcelPOIRoute(excelName,orderPro.getProductionOrderNo()); List<ProcessRoute> routeList = readExcelPOIRoute(excelName,orderPro.getProductionOrderNo());
List<ProcessRoute> routeList2 = processRouteService.selectByProjectNumber(orderPro.getProductionOrderNo());
// 将routeList2中有但routeList中没有的记录添加到routeList
if (routeList2 != null && !routeList2.isEmpty()) {
if (routeList == null) {
routeList = new ArrayList<>();
}
// 使用 Set 存储 routeList 中已有的 materialCode用于快速排重
Set<String> existingMaterialCodes = routeList.stream()
.map(ProcessRoute::getMaterialCode)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
for (ProcessRoute route : routeList2) {
if (StringUtils.isNotBlank(route.getMaterialCode()) && !existingMaterialCodes.contains(route.getMaterialCode())) {
routeList.add(route);
existingMaterialCodes.add(route.getMaterialCode()); // 避免重复添加
}
}
}
List<ProcessRoute> routes = new ArrayList<>(); List<ProcessRoute> routes = new ArrayList<>();
List<Map<String, Object>> kingdeeBomRows = new ArrayList<>(); List<Map<String, Object>> kingdeeBomRows = new ArrayList<>();
for (ProcessRoute base : routeList) { for (ProcessRoute base : routeList) {
@ -1702,6 +1822,8 @@ public class ProcessOrderProController extends BaseController {
item.setDiscWeight(base.getDiscWeight()); item.setDiscWeight(base.getDiscWeight());
item.setUnitQuantity(base.getUnitQuantity()); item.setUnitQuantity(base.getUnitQuantity());
item.setBatchQuantity(base.getBatchQuantity()); item.setBatchQuantity(base.getBatchQuantity());
item.setXuEndTime(base.getXuEndTime());
item.setXuStartTime(base.getXuStartTime());
routes.add(item); routes.add(item);
continue; continue;
} }
@ -1715,6 +1837,8 @@ public class ProcessOrderProController extends BaseController {
item.setDiscWeight(base.getDiscWeight()); item.setDiscWeight(base.getDiscWeight());
item.setUnitQuantity(base.getUnitQuantity()); item.setUnitQuantity(base.getUnitQuantity());
item.setBatchQuantity(base.getBatchQuantity()); item.setBatchQuantity(base.getBatchQuantity());
item.setXuEndTime(base.getXuEndTime());
item.setXuStartTime(base.getXuStartTime());
routes.add(item); routes.add(item);
continue; continue;
} }
@ -1740,6 +1864,8 @@ public class ProcessOrderProController extends BaseController {
item.setProcessControl(r.getProcessControl()); item.setProcessControl(r.getProcessControl());
item.setActivityDuration(r.getActivityDuration()); item.setActivityDuration(r.getActivityDuration());
item.setActivityUnit(r.getActivityUnit()); item.setActivityUnit(r.getActivityUnit());
item.setXuStartTime(r.getXuStartTime());
item.setXuEndTime(r.getXuEndTime());
routes.add(item); routes.add(item);
}); });
} else { } else {
@ -1751,6 +1877,8 @@ public class ProcessOrderProController extends BaseController {
item.setDiscWeight(base.getDiscWeight()); item.setDiscWeight(base.getDiscWeight());
item.setUnitQuantity(base.getUnitQuantity()); item.setUnitQuantity(base.getUnitQuantity());
item.setBatchQuantity(base.getBatchQuantity()); item.setBatchQuantity(base.getBatchQuantity());
item.setXuStartTime(base.getXuStartTime());
item.setXuEndTime(base.getXuEndTime());
// 不写入BOM字段保持纯工艺数据行 // 不写入BOM字段保持纯工艺数据行
routes.add(item); routes.add(item);
} }

View File

@ -891,7 +891,7 @@ public class ProcessRouteController extends BaseController {
* 更新生产订单仓库字段 * 更新生产订单仓库字段
*/ */
@SaCheckPermission("system:route:updateProductionOrders") @SaCheckPermission("system:route:updateProductionOrders")
@Log(title = "获取全部工艺路线和物料清单", businessType = BusinessType.OTHER) @Log(title = "更新生产订单仓库", businessType = BusinessType.OTHER)
@PostMapping("/updateProductionOrders") @PostMapping("/updateProductionOrders")
public R updateProductionOrders(@RequestParam("rooteProdet") String rooteProdet,@RequestParam("cangKuNum")String cangKuNum) throws Exception { public R updateProductionOrders(@RequestParam("rooteProdet") String rooteProdet,@RequestParam("cangKuNum")String cangKuNum) throws Exception {
//判断此项目是否有 //判断此项目是否有
@ -900,10 +900,10 @@ public class ProcessRouteController extends BaseController {
} }
/** /**
* 更新采购申请仓库字段 * 更新采购申请入库仓库仓库
*/ */
@SaCheckPermission("system:route:updateProductionOrders") @SaCheckPermission("system:route:updateProductionOrders")
@Log(title = "获取全部工艺路线和物料清单", businessType = BusinessType.OTHER) @Log(title = "更新采购申请单仓库", businessType = BusinessType.OTHER)
@PostMapping("/updateCgOrders") @PostMapping("/updateCgOrders")
public R updateCgOrders(@RequestParam("rooteProdet") String rooteProdet,@RequestParam("cangKuNum")String cangKuNum) throws Exception { public R updateCgOrders(@RequestParam("rooteProdet") String rooteProdet,@RequestParam("cangKuNum")String cangKuNum) throws Exception {
//判断此项目是否有 //判断此项目是否有

View File

@ -9,4 +9,6 @@ public class CgDTO {
private String orderBillNo; private String orderBillNo;
@JsonProperty("FMaterialId.FNumber") @JsonProperty("FMaterialId.FNumber")
private String materialCode; private String materialCode;
@JsonProperty("FMaterialName")
private String materialName;
} }

View File

@ -33,6 +33,6 @@ public class PlanPrcessNumDTO {
private Date FOperPlanFinishTime; private Date FOperPlanFinishTime;
@JsonProperty("FMONumber") @JsonProperty("FMONumber")
private String FMONumber; private String FMONumber;
@JsonProperty("F_HBYT_RKCK.FName") @JsonProperty("F_HBYT_RKCK")
private String FRKCKFName; private String FRKCKFName;
} }

View File

@ -2,7 +2,11 @@ package com.ruoyi.system.domain.dto;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
/**
* 采购订单未入库数量
* @author tzy
* @date 2025-10-26
*/
@Data @Data
public class PurchaseOrderDTO { public class PurchaseOrderDTO {
@JsonProperty("FMaterialId.FNumber") @JsonProperty("FMaterialId.FNumber")

View File

@ -0,0 +1,14 @@
package com.ruoyi.system.domain.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class SalesOrderDTO {
@JsonProperty("FMaterialId.FNumber")
private String FMaterialIdFNumber;
@JsonProperty("FMaterialName")
private String FMaterialName;
@JsonProperty("FBaseRemainOutQty")
private Double FBaseRemainOutQty;
}

View File

@ -10,4 +10,5 @@ public class InventoryInfoVO {
private String stockName; // 仓库 private String stockName; // 仓库
private String stockUnit; // 单位 private String stockUnit; // 单位
private String quantity; // 数量 private String quantity; // 数量
private String salesOrder; // 销售数量
} }

View File

@ -21,6 +21,12 @@ public class FSubEntity {
/* /*
入库仓库 入库仓库
*/ */
@JsonProperty("F_HBYT_RKCK.FName") @JsonProperty("F_HBYT_RKCK")
private String rkckName; private F_HBYT_RKCK F_HBYT_RKCK;
@Data
public static class F_HBYT_RKCK {
@JsonProperty("FNUMBER")
private String FNUMBER;
}
} }

View File

@ -11,7 +11,6 @@ import java.util.List;
public class Model { public class Model {
@JsonProperty("FID") @JsonProperty("FID")
private int FID; private int FID;
@JsonProperty("FPlanStartTime") @JsonProperty("FPlanStartTime")
private Date FPlanStartTime; private Date FPlanStartTime;
@JsonProperty("FProcessId_number") // 添加 FProcessId_number 字段 @JsonProperty("FProcessId_number") // 添加 FProcessId_number 字段

View File

@ -3049,6 +3049,7 @@ public class JdUtil {
pageJson.addProperty("StartRow", startRow); pageJson.addProperty("StartRow", startRow);
pageJson.addProperty("Limit", pageSize); pageJson.addProperty("Limit", pageSize);
String resultJson = String.valueOf(client.billQuery(pageJson.toString())); String resultJson = String.valueOf(client.billQuery(pageJson.toString()));
System.out.println("0000000000000000"+ pageJson);
JsonArray jsonArray = new Gson().fromJson(resultJson, JsonArray.class); JsonArray jsonArray = new Gson().fromJson(resultJson, JsonArray.class);
if (jsonArray == null || jsonArray.size() == 0) { if (jsonArray == null || jsonArray.size() == 0) {
break; break;
@ -3432,7 +3433,7 @@ public class JdUtil {
// 请求参数要求为json字符串 // 请求参数要求为json字符串
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
json.addProperty("FormId", "PUR_Requisition"); json.addProperty("FormId", "PUR_Requisition");
json.addProperty("FieldKeys", "FBillNo,FMaterialId.FNumber"); json.addProperty("FieldKeys", "FBillNo,FMaterialId.FNumber,FMaterialName");
JsonArray filterString = new JsonArray(); JsonArray filterString = new JsonArray();
JsonObject filterObject = new JsonObject(); JsonObject filterObject = new JsonObject();
filterObject.addProperty("FieldName", "F_UCHN_Text"); filterObject.addProperty("FieldName", "F_UCHN_Text");
@ -3473,7 +3474,8 @@ public class JdUtil {
JsonArray needUpDateFields = new JsonArray(); JsonArray needUpDateFields = new JsonArray();
needUpDateFields.add("FID"); needUpDateFields.add("FID");
needUpDateFields.add("F_HBYT_RKCK.FName"); needUpDateFields.add("FENTRYID");
needUpDateFields.add("F_HBYT_RKCK");
needUpDateFields.add("FTreeEntity"); needUpDateFields.add("FTreeEntity");
json.add("NeedUpDateFields", needUpDateFields); json.add("NeedUpDateFields", needUpDateFields);
@ -3484,7 +3486,11 @@ public class JdUtil {
JsonArray fTreeEntity = new JsonArray(); JsonArray fTreeEntity = new JsonArray();
JsonObject fTreeEntityItem = new JsonObject(); JsonObject fTreeEntityItem = new JsonObject();
fTreeEntityItem.addProperty("F_HBYT_RKCK.FName", cangKuNum); // 修改使用 FNUMBER 对象结构设置仓库
JsonObject rkck = new JsonObject();
rkck.addProperty("FNUMBER", cangKuNum);
fTreeEntityItem.add("F_HBYT_RKCK", rkck);
fTreeEntityItem.addProperty("FENTRYID", jdHuoZhu.getFTreeEntityFENTRYID()); fTreeEntityItem.addProperty("FENTRYID", jdHuoZhu.getFTreeEntityFENTRYID());
fTreeEntity.add(fTreeEntityItem); fTreeEntity.add(fTreeEntityItem);
model.add("FTreeEntity", fTreeEntity); model.add("FTreeEntity", fTreeEntity);
@ -3523,8 +3529,12 @@ public class JdUtil {
JsonArray fTreeEntity = new JsonArray(); JsonArray fTreeEntity = new JsonArray();
JsonObject fTreeEntityItem = new JsonObject(); JsonObject fTreeEntityItem = new JsonObject();
fTreeEntityItem.addProperty("F_HBYT_RKCK.FName", cangKuNum); // 修改使用 FNUMBER 对象结构设置仓库
fTreeEntityItem.addProperty("FEntity_FEntryID", fidEntryIdDTO.getFEntryId()); JsonObject rkck = new JsonObject();
rkck.addProperty("FNUMBER", cangKuNum);
fTreeEntityItem.add("F_HBYT_RKCK", rkck);
fTreeEntityItem.addProperty("FEntryID", fidEntryIdDTO.getFEntryId());
fTreeEntity.add(fTreeEntityItem); fTreeEntity.add(fTreeEntityItem);
model.add("FEntity", fTreeEntity); model.add("FEntity", fTreeEntity);
json.add("Model", model); json.add("Model", model);
@ -3539,9 +3549,9 @@ public class JdUtil {
RepoRet sRet = gson.fromJson(result, RepoRet.class); RepoRet sRet = gson.fromJson(result, RepoRet.class);
if (sRet.isSuccessfully()) { if (sRet.isSuccessfully()) {
return "采购申请单保存成功"; return gson.toJson(sRet.getResult());
} else { } else {
return "采购申请单成功失败:" + gson.toJson(sRet.getResult()); return gson.toJson(sRet.getResult());
} }
} }
@ -3588,4 +3598,65 @@ public class JdUtil {
} }
return Collections.emptyList(); return Collections.emptyList();
} }
/**
* 销售订单的查询
* @param materialCode
* @return
*/
public static List<SalesOrderDTO> getSalesOrderList(String materialCode) {
K3CloudApi client = new K3CloudApi();
// 请求参数要求为json字符串
JsonObject json = new JsonObject();
json.addProperty("FormId", "SAL_SaleOrder");
json.addProperty("FieldKeys",
"FMaterialId.FNumber,FMaterialName,FBaseRemainOutQty");
// 创建过滤条件
JsonArray filterString = new JsonArray();
JsonObject filterObject = new JsonObject();
filterObject.addProperty("FieldName", "FMaterialId.FNumber");
filterObject.addProperty("Compare", "=");
filterObject.addProperty("Value", materialCode);
filterObject.addProperty("Left", "");
filterObject.addProperty("Right", "");
filterObject.addProperty("Logic", 0);
filterString.add(filterObject);
JsonObject filterObject1 = new JsonObject();
filterObject1.addProperty("FieldName", "FCloseStatus");
filterObject1.addProperty("Compare", "105");
filterObject1.addProperty("Value", "A");
filterObject1.addProperty("Left", "");
filterObject1.addProperty("Right", "");
filterObject1.addProperty("Logic", 0);
filterString.add(filterObject1);
json.add("FilterString", filterString);
json.addProperty("OrderString", "");
json.addProperty("TopRowCount", 0);
json.addProperty("StartRow", 0);
json.addProperty("Limit", 2000);
json.addProperty("SubSystemId", "");
String jsonData = json.toString();
List<SalesOrderDTO> plannedProcessList = null;
try {
// 调用接口
String resultJson = String.valueOf(client.billQuery(jsonData));
JsonArray jsonArray = new Gson().fromJson(resultJson, JsonArray.class);
// 使用 ObjectMapper JsonArray 转换为 List<PlannedProcessVo>
ObjectMapper objectMapper = new ObjectMapper();
plannedProcessList = objectMapper.readValue(jsonArray.toString(),
new TypeReference<List<SalesOrderDTO>>() {
});
} catch (Exception e) {
e.printStackTrace(); // 输出异常日志
}
return plannedProcessList; // 返回结果
}
} }

View File

@ -126,7 +126,7 @@ public class updatePcessPlanConver {
for (Model model : numDTO) { for (Model model : numDTO) {
MainModel mainModel = new MainModel(); MainModel mainModel = new MainModel();
List<String> needUpDateFields = Arrays.asList( List<String> needUpDateFields = Arrays.asList(
"FEntity", "FSubEntity", "F_HBYT_RKCK.FName" "FEntity", "FSubEntity", "F_HBYT_RKCK"
); );
mainModel.setNeedUpDateFields(needUpDateFields); mainModel.setNeedUpDateFields(needUpDateFields);
mainModel.setDeleteEntry(false); mainModel.setDeleteEntry(false);

View File

@ -58,4 +58,5 @@ public interface IBomDetailsService {
List<BomDetails> selectByFNumberAndTotalWeight(String fnumber, String totalWeight); List<BomDetails> selectByFNumberAndTotalWeight(String fnumber, String totalWeight);
List<BomDetails> selectByProjectNumber(String totalWeight); List<BomDetails> selectByProjectNumber(String totalWeight);
List<BomDetails> selectMaterialZH(String totalWeight,String partNumber);
} }

View File

@ -119,6 +119,7 @@ public interface IProcessRouteService {
boolean isAnTuDingGou(String materialCode); boolean isAnTuDingGou(String materialCode);
List<ProcessRouteSelectDTO> getSelectProcessRoute(String materilCode); List<ProcessRouteSelectDTO> getSelectProcessRoute(String materilCode);
List<ProcessRouteSelectDTO> getSelectStandProcessRoute(String materilCode); List<ProcessRouteSelectDTO> getSelectStandProcessRoute(String materilCode);
//根据项目令号删除 材料bom 总装bom 工艺路线 //根据项目令号删除 材料bom 总装bom 工艺路线
R<Void> delByProjectCode(String productionOrderNo); R<Void> delByProjectCode(String productionOrderNo);

View File

@ -26,6 +26,32 @@ public class MssqlQueryService {
return jdbcTemplate.queryForList(sql); return jdbcTemplate.queryForList(sql);
} }
/**
* 根据物料编码查询是否是VMI业务
* @param materialNumber 物料编码
* @return true:是VMI业务, false:
*/
@DS("sqlserver")
public boolean checkIfVmiBusiness(String materialNumber) {
String sql = "SELECT p.FISVMIBUSINESS " +
" FROM T_BD_MATERIAL m " +
" INNER JOIN T_BD_MATERIALPURCHASE p ON m.FMATERIALID = p.FMATERIALID " +
" WHERE m.FNUMBER = ?";
try {
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, materialNumber);
if (list != null && !list.isEmpty()) {
Object val = list.get(0).get("FISVMIBUSINESS");
if (val != null) {
String s = val.toString();
return "1".equals(s) || "true".equalsIgnoreCase(s);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@DS("sqlserver") @DS("sqlserver")
public List<KingdeeWorkCenterDataBo> getKingdeeProduceData(String workCenterName) { public List<KingdeeWorkCenterDataBo> getKingdeeProduceData(String workCenterName) {
String sql = "select t8.FBillNo as MoBillNo,t8.F_HBYT_SCLH as MoOrderNo, \n" + String sql = "select t8.FBillNo as MoBillNo,t8.F_HBYT_SCLH as MoOrderNo, \n" +

View File

@ -246,6 +246,24 @@ public class BomDetailsServiceImpl implements IBomDetailsService {
return baseMapper.selectList(bomDetailsLambdaQueryWrapper); return baseMapper.selectList(bomDetailsLambdaQueryWrapper);
} }
/**
* 查询部件是否为组焊件
* @param totalWeight 总权重/版本号
* @param partNumber 部件编码
* @return 如果是组焊件返回包含该记录的列表否则返回空列表
*/
@Override
public List<BomDetails> selectMaterialZH(String totalWeight, String partNumber) {
// 你的逻辑fProcessIdNumber作为partNumbertotalWeightMaterial=组焊件
// 查询出数据 就代表他就是组焊件子件或者说需要走CK025
LambdaQueryWrapper<BomDetails> bq = new LambdaQueryWrapper<>();
bq.eq(BomDetails::getTotalWeight, totalWeight);
bq.eq(BomDetails::getPartNumber, partNumber);
bq.eq(BomDetails::getMaterial, "组焊件");
return baseMapper.selectList(bq);
}
public int loadMaterialPreservation(BomDetails bomDetails1) { public int loadMaterialPreservation(BomDetails bomDetails1) {
K3CloudApi client = new K3CloudApi(); K3CloudApi client = new K3CloudApi();

View File

@ -1004,14 +1004,16 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
String code = processOrderPro.getProductionOrderNo(); String code = processOrderPro.getProductionOrderNo();
log.info("开始处理项目PDF文件项目令号: {}", code); log.info("开始处理项目PDF文件项目令号: {}", code);
// 3. 查询产品信息 // 3. 查询产品信息/system/orderPro/uploadPDF
List<FigureSave> fiseList = iFigureSaveService.selectByFid(id); List<FigureSave> fiseList = iFigureSaveService.selectByFid(id);
if (fiseList == null || fiseList.isEmpty()) { if (fiseList == null || fiseList.isEmpty()) {
return R.fail("项目下没有找到产品信息"); return R.fail("项目下没有找到产品信息");
} }
// 4. 构建本地根目录 // 4. 构建本地根目录
String localBaseDir = "D:/2025/"; // 修正不再硬编码为 D:/2025/而是根据 code 动态构建与后续处理逻辑保持一致
String localBaseDir = buildWorkDirectory(code);
// 5. 清空本地目标文件夹 // 5. 清空本地目标文件夹
String targetDir = localBaseDir + code; String targetDir = localBaseDir + code;
File targetDirectory = new File(targetDir); File targetDirectory = new File(targetDir);
@ -1029,9 +1031,46 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
// 6. 下载FTP文件到本地 // 6. 下载FTP文件到本地
log.info("开始从FTP下载文件..."); log.info("开始从FTP下载文件...");
for (FigureSave figureSave : fiseList) { for (FigureSave figureSave : fiseList) {
String productionCode = figureSave.getProductionCode();
//解析出年份 从左往右第一个- 后面就是 年份25 =2025 26 就是2026
//如果这个是EY开头的那就取EY后面的数字 25 26 27 作为年份 2025 2026 2027
try { try {
String year = "2025"; // 默认2025
if (productionCode != null) {
if (productionCode.startsWith("EY")) {
// EY开头取第2位开始的两位 (索引2和3)
if (productionCode.length() >= 4) {
String yearShort = productionCode.substring(2, 4);
try {
Integer.parseInt(yearShort);
year = "20" + yearShort;
} catch (NumberFormatException e) {
// 解析失败保持默认
}
}
} else {
// 原有逻辑取第一个横杠后的两位
int firstDash = productionCode.indexOf("-");
if (firstDash >= 0 && firstDash + 3 <= productionCode.length()) {
String yearShort = productionCode.substring(firstDash + 1, firstDash + 3);
try {
Integer.parseInt(yearShort); // 验证是否为数字
year = "20" + yearShort;
} catch (NumberFormatException e) {
// 解析失败保持默认
}
}
}
}
// 拼接远程文件夹路径 // 拼接远程文件夹路径
String ftpPath = "/" + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF"; // 确保路径使用正斜杠且不包含本地盘符
String ftpPath = year + "/" + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF";
ftpPath = ftpPath.replace("\\", "/"); // 强制替换反斜杠
if (!ftpPath.startsWith("/")) {
ftpPath = "/" + ftpPath; // 确保以/开头表示从FTP根目录开始
}
// 拼接本地存储路径 // 拼接本地存储路径
String localPath = localBaseDir + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF"; String localPath = localBaseDir + code + "/" + figureSave.getFigureNumber().replace("/", "-") + "-PDF";
@ -1147,11 +1186,11 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
* PDF 按工作中心分类复制到目标目录 zip/工作中心/ * PDF 按工作中心分类复制到目标目录 zip/工作中心/
*/ */
private void classifyPdfByWorkCenterToTarget(String productionOrderNo,String sourceDirPath, String code, String targetBaseDirPath) { private void classifyPdfByWorkCenterToTarget(String productionOrderNo,String sourceDirPath, String code, String targetBaseDirPath) {
//按工作中心 分类时 查找是否有采购申请单 和生产订单 /* //按工作中心 分类时 查找是否有采购申请单 和生产订单
//获取这个项目的所有生产令号 //获取这个项目的所有生产令号
List<String> orderList = JdUtil.getOrderNumberCollection(productionOrderNo); List<String> orderList = JdUtil.getOrderNumberCollection(productionOrderNo);
List<String> cgorderList = JdUtil.getCaiGouOrderNumberList(productionOrderNo); List<String> cgorderList = JdUtil.getCaiGouOrderNumberList(productionOrderNo);*/
File sourceDir = new File(sourceDirPath); File sourceDir = new File(sourceDirPath);
if (!sourceDir.exists() || !sourceDir.isDirectory()) return; if (!sourceDir.exists() || !sourceDir.isDirectory()) return;
@ -1166,16 +1205,16 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
if (materialCode == null) continue; if (materialCode == null) continue;
String workCenter; String workCenter;
//如果这个文件的生产令号 生产令号列表 或者 采购申请单列表 可以分类到对应的文件夹中 /* //如果这个文件的生产令号 生产令号列表 或者 采购申请单列表 可以分类到对应的文件夹中
// 由于物料名近似如KP08和KP08.7使用equals精确匹配不使用contains // 由于物料名近似如KP08和KP08.7使用equals精确匹配不使用contains
if (!orderList.contains(materialCode) && !cgorderList.contains(materialCode)) { if (!orderList.contains(materialCode) && !cgorderList.contains(materialCode)) {
unmovedMaterials.add(materialCode); unmovedMaterials.add(materialCode);
workCenter = "未匹配订单"; workCenter = "未匹配订单";
} else { } else {*/
workCenter = iProcessRouteService.getRouteCode(materialCode, code); workCenter = iProcessRouteService.getRouteCode(materialCode, code);
if (iProductionOrderService.isPurchas(productionOrderNo, materialCode)) workCenter = "外购件"; if (iProductionOrderService.isPurchas(productionOrderNo, materialCode)) workCenter = "外购件";
if (workCenter == null || workCenter.isEmpty()) workCenter = "无工段"; if (workCenter == null || workCenter.isEmpty()) workCenter = "无工段";
} /*}*/
File workCenterDir = new File(targetBaseDirPath, workCenter); File workCenterDir = new File(targetBaseDirPath, workCenter);
if (!workCenterDir.exists()) workCenterDir.mkdirs(); if (!workCenterDir.exists()) workCenterDir.mkdirs();
@ -1188,7 +1227,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
log.warn("PDF {} 复制失败: {}", pdf.getName(), e.getMessage()); log.warn("PDF {} 复制失败: {}", pdf.getName(), e.getMessage());
} }
} }
if (!unmovedMaterials.isEmpty()) { if (!unmovedMaterials.isEmpty()) {
log.info("以下物料未匹配到生产订单或采购申请单,未进行分类移动: {}", unmovedMaterials); log.info("以下物料未匹配到生产订单或采购申请单,未进行分类移动: {}", unmovedMaterials);
} }

View File

@ -19,8 +19,6 @@ 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.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.domain.dto.BOMItem;
import com.ruoyi.system.domain.dto.BOMUploadResult;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.controller.ProcessRouteController; import com.ruoyi.system.controller.ProcessRouteController;
import com.ruoyi.system.domain.*; import com.ruoyi.system.domain.*;
@ -55,7 +53,6 @@ import java.util.concurrent.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.ruoyi.system.controller.BomDetailsController.*; import static com.ruoyi.system.controller.BomDetailsController.*;
import static com.ruoyi.system.runner.JdUtil.getCaiGouOrderNumberList;
import static com.ruoyi.system.runner.JdUtil.updateCgOrder1; import static com.ruoyi.system.runner.JdUtil.updateCgOrder1;
import static com.ruoyi.system.runner.JsonConverter.createProcessModel; import static com.ruoyi.system.runner.JsonConverter.createProcessModel;
import static com.ruoyi.system.runner.updatePcessPlanConver.updatePcessPlan1; import static com.ruoyi.system.runner.updatePcessPlanConver.updatePcessPlan1;
@ -70,8 +67,9 @@ import static com.ruoyi.system.runner.updatePcessPlanConver.updatePcessPlan2;
@RequiredArgsConstructor @RequiredArgsConstructor
@Service @Service
public class ProcessRouteServiceImpl implements IProcessRouteService { public class ProcessRouteServiceImpl implements IProcessRouteService {
private final IMaterialTotalService iMaterialTotalService; private final IImMaterialService imMaterialService;
private final IBomDetailsService iBomDetailsService; private final IBomDetailsService iBomDetailsService;
private final MssqlQueryService sqlQueryService;
private final ProcessRouteMapper baseMapper; private final ProcessRouteMapper baseMapper;
private final BomDetailsMapper bomDetailsMapper; private final BomDetailsMapper bomDetailsMapper;
private final IMaterialPropertiesService iMaterialPropertiesService; private final IMaterialPropertiesService iMaterialPropertiesService;
@ -84,6 +82,17 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
private static final Logger log = LoggerFactory.getLogger(ProcessRouteController.class); private static final Logger log = LoggerFactory.getLogger(ProcessRouteController.class);
private static final Logger logger = LoggerFactory.getLogger(IProcessRouteService.class); private static final Logger logger = LoggerFactory.getLogger(IProcessRouteService.class);
// 全局线程池核心线程10最大线程50存活60s有界队列
private static final ExecutorService GLOBAL_EXECUTOR = new ThreadPoolExecutor(
10, 50, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行防止丢任务
);
// 单据锁 Map
private static final ConcurrentHashMap<String, Object> BILL_LOCK = new ConcurrentHashMap<>();
/** /**
* 查询工艺路线 * 查询工艺路线
*/ */
@ -244,7 +253,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
* @return * @return
*/ */
@Override @Override
public List<JDMaterialAndRoute> getProcessRouteGD(List<ProcessRouteGetDTO> list,String rooteProdet) { public List<JDMaterialAndRoute> getProcessRouteGD(List<ProcessRouteGetDTO> list, String rooteProdet) {
List<JDMaterialAndRoute> materialAndRouteList = new ArrayList<>(); List<JDMaterialAndRoute> materialAndRouteList = new ArrayList<>();
for (ProcessRouteGetDTO processRoute : list) { for (ProcessRouteGetDTO processRoute : list) {
// 跳过空行或解析为空的记录避免空指针 // 跳过空行或解析为空的记录避免空指针
@ -327,15 +336,14 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
LambdaQueryWrapper<ProcessRoute> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ProcessRoute> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ProcessRoute::getRouteDescription, productionOrderNo) wrapper.eq(ProcessRoute::getRouteDescription, productionOrderNo)
.eq(ProcessRoute::getMaterialCode, materialCode) .eq(ProcessRoute::getMaterialCode, materialCode)
.eq(ProcessRoute::getProcessNo,xu); .eq(ProcessRoute::getProcessNo, xu);
if (baseMapper.selectOne(wrapper) != null) { if (baseMapper.selectOne(wrapper) != null) {
return baseMapper.selectOne(wrapper); return baseMapper.selectOne(wrapper);
} }
return null; return null;
} }
private String generateKey1(BomDetails bomDetail, ProcessRoute processRoute) { private String generateKey1(BomDetails bomDetail, ProcessRoute processRoute) {
return String.format("%s:%s:%s", processRoute.getMaterialCode(), processRoute.getMaterialName(), bomDetail.getName(), bomDetail.getPartNumber()); return String.format("%s:%s:%s", processRoute.getMaterialCode(), processRoute.getMaterialName(), bomDetail.getName(), bomDetail.getPartNumber());
} }
@ -794,10 +802,10 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
} else if ("".equals(unit) && quantity.contains("/")) { } else if ("".equals(unit) && quantity.contains("/")) {
//写入工艺表时分数的体现 直接取分母 "/"为分割符,取第二个字符串 //写入工艺表时分数的体现 直接取分母 "/"为分割符,取第二个字符串
materialBom.setQuantity(quantity); materialBom.setQuantity(quantity);
} else if(!"".equals(unit)&&quantity.contains("/") ){ } else if (!"".equals(unit) && quantity.contains("/")) {
// //
throw new RuntimeException("单位不为根的时候不能为分数"); throw new RuntimeException("单位不为根的时候不能为分数");
}else { } else {
// 其他单位直接使用原值保留2位小数 // 其他单位直接使用原值保留2位小数
materialBom.setQuantity(String.valueOf(new BigDecimal(quantity).setScale(2, RoundingMode.HALF_UP))); materialBom.setQuantity(String.valueOf(new BigDecimal(quantity).setScale(2, RoundingMode.HALF_UP)));
} }
@ -1264,7 +1272,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
} }
@Override @Override
public R<ProcessRoutePushResultDTO> pushRouteBom(String rooteProdet,String groupName) { public R<ProcessRoutePushResultDTO> pushRouteBom(String rooteProdet, String groupName) {
List<ProcessRouteXuDTO> rawBomList = getProcessRoute(rooteProdet); List<ProcessRouteXuDTO> rawBomList = getProcessRoute(rooteProdet);
List<ProcessRouteXuDTO> successfulRoutes = new ArrayList<>(); List<ProcessRouteXuDTO> successfulRoutes = new ArrayList<>();
List<ProcessRouteXuDTO> failedRoutes = new ArrayList<>(); List<ProcessRouteXuDTO> failedRoutes = new ArrayList<>();
@ -1281,14 +1289,14 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
if (isDifferent) { if (isDifferent) {
log.info("工艺路线不同,进行更新: " + processRouteXuDTO.getMaterialCode()); log.info("工艺路线不同,进行更新: " + processRouteXuDTO.getMaterialCode());
// 保存工艺路线 // 保存工艺路线
LoadBomResult result = loadBillOfMaterialsPreservation(processRouteXuDTO,groupName); LoadBomResult result = loadBillOfMaterialsPreservation(processRouteXuDTO, groupName);
// 处理返回结果 // 处理返回结果
if (result.isSuccess()) { if (result.isSuccess()) {
log.info("工艺路线保存成功: " + processRouteXuDTO.getMaterialCode() + result.getResultData()); log.info("工艺路线保存成功: " + processRouteXuDTO.getMaterialCode() + result.getResultData());
successfulRoutes.add(processRouteXuDTO); successfulRoutes.add(processRouteXuDTO);
} else { } else {
log.info("工艺路线保存失败: " + processRouteXuDTO.getMaterialCode() + result.getMessage()); log.info("工艺路线保存失败: " + processRouteXuDTO.getMaterialCode() + result.getMessage());
processRouteXuDTO.setErrorMessage( result.getMessage()); processRouteXuDTO.setErrorMessage(result.getMessage());
failedRoutes.add(processRouteXuDTO); failedRoutes.add(processRouteXuDTO);
} }
} else { } else {
@ -1331,7 +1339,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
log.info("工艺路线不同,进行更新: " + processRouteXuDTO.getMaterialCode()); log.info("工艺路线不同,进行更新: " + processRouteXuDTO.getMaterialCode());
// 保存工艺路线 // 保存工艺路线
LoadBomResult result = loadBillOfMaterialsPreservation(processRouteXuDTO,""); LoadBomResult result = loadBillOfMaterialsPreservation(processRouteXuDTO, "");
// 处理返回结果 // 处理返回结果
if (result.isSuccess()) { if (result.isSuccess()) {
log.info("工艺路线保存成功: " + processRouteXuDTO.getMaterialCode() + result.getResultData()); log.info("工艺路线保存成功: " + processRouteXuDTO.getMaterialCode() + result.getResultData());
@ -1412,8 +1420,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
// 请求参数要求为json字符串 // 请求参数要求为json字符串
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
json.addProperty("FormId", "SFC_OperationPlanning"); json.addProperty("FormId", "SFC_OperationPlanning");
json.addProperty("FieldKeys", json.addProperty("FieldKeys", "FBillNo,FOperNumber,FProcessId.FName,FOptCtrlCodeId.FName,FActivity1BaseQty,FOperPlanStartTime,FOperPlanFinishTime,FOperQty,FDepartmentId.FName,FWorkCenterId.FName,FOperDescription,FPlanStartTime,FPlanFinishTime,FMOQty,FOperUnitId.FName");
"FBillNo,FOperNumber,FProcessId.FName,FOptCtrlCodeId.FName,FActivity1BaseQty,FOperPlanStartTime,FOperPlanFinishTime,FOperQty,FDepartmentId.FName,FWorkCenterId.FName,FOperDescription,FPlanStartTime,FPlanFinishTime,FMOQty,FOperUnitId.FName");
// 创建过滤条件 // 创建过滤条件
JsonArray filterString = new JsonArray(); JsonArray filterString = new JsonArray();
JsonObject filterObject = new JsonObject(); JsonObject filterObject = new JsonObject();
@ -1619,13 +1626,13 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
} }
// 工艺保存方法 // 工艺保存方法
public LoadBomResult loadBillOfMaterialsPreservation(ProcessRouteXuDTO rawBomList,String groupName) { public LoadBomResult loadBillOfMaterialsPreservation(ProcessRouteXuDTO rawBomList, String groupName) {
// TODO: 实现加载和保存物料清单数据的逻辑 // TODO: 实现加载和保存物料清单数据的逻辑
K3CloudApi client = new K3CloudApi(); K3CloudApi client = new K3CloudApi();
ProcessModel processModel = createProcessModel(rawBomList,groupName); ProcessModel processModel = createProcessModel(rawBomList, groupName);
String jsonStr = JSONUtil.toJsonStr(processModel); String jsonStr = JSONUtil.toJsonStr(processModel);
log.debug("推送工艺报文=====》{}",JSONUtil.toJsonStr(jsonStr)); log.debug("推送工艺报文=====》{}", JSONUtil.toJsonStr(jsonStr));
try { try {
// 业务对象标识 // 业务对象标识
String formId = "ENG_Route"; String formId = "ENG_Route";
@ -2031,7 +2038,7 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
json.addProperty("FieldKeys", json.addProperty("FieldKeys",
"FID,FSubEntity_FDetailID,FProductId.FNumber,FOperNumber,FEntity_FEntryID,FProcessId.FName,FSeqNumber,FSeqName,FPlanStartTime,FPlanFinishTime," "FID,FSubEntity_FDetailID,FProductId.FNumber,FOperNumber,FEntity_FEntryID,FProcessId.FName,FSeqNumber,FSeqName,FPlanStartTime,FPlanFinishTime,"
+ +
"FOperPlanStartTime,FOperPlanFinishTime,FMONumber,F_HBYT_RKCK.FName"); "FOperPlanStartTime,FOperPlanFinishTime,FMONumber,F_HBYT_RKCK");
JsonArray filterString = new JsonArray(); JsonArray filterString = new JsonArray();
JsonObject filterObject = new JsonObject(); JsonObject filterObject = new JsonObject();
@ -2115,7 +2122,11 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
fSubEntity.setFOperNumber(dto.getFOperNumber()); fSubEntity.setFOperNumber(dto.getFOperNumber());
fSubEntity.setFOperPlanStartTime((dto.getFOperPlanStartTime())); fSubEntity.setFOperPlanStartTime((dto.getFOperPlanStartTime()));
fSubEntity.setFOperPlanFinishTime(dto.getFOperPlanFinishTime()); fSubEntity.setFOperPlanFinishTime(dto.getFOperPlanFinishTime());
fSubEntity.setRkckName(dto.getFRKCKFName());
FSubEntity.F_HBYT_RKCK rkck = new FSubEntity.F_HBYT_RKCK();
rkck.setFNUMBER(dto.getFRKCKFName());
fSubEntity.setF_HBYT_RKCK(rkck);
fEntity.getFSubEntity().add(fSubEntity); fEntity.getFSubEntity().add(fSubEntity);
} }
@ -2601,55 +2612,198 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
*/ */
@Override @Override
public List<String> updateProductionOrders(String rooteProdet, String cangKuNum) throws Exception { public List<String> updateProductionOrders(String rooteProdet, String cangKuNum) throws Exception {
// 1. 更新生产订单 (PRD_MO) // 创建一个线程安全的List来收集结果
List<String> results = Collections.synchronizedList(new ArrayList<>());
List<PlanOrderVo> planOrderList = getSelectProceOrder1(rooteProdet); List<PlanOrderVo> planOrderList = getSelectProceOrder1(rooteProdet);
for (PlanOrderVo planOrderVo : planOrderList) { List<Future<?>> futures = new ArrayList<>();
JdHuoZhu fidToProductionOrder = JdUtil.getFIDToProductionOrder(planOrderVo);
if (fidToProductionOrder != null) { // 1. 更新生产订单 (PRD_MO)
// 更新生产订单的仓库 if (planOrderList != null && !planOrderList.isEmpty()) {
JdUtil.updateOrder2(fidToProductionOrder, cangKuNum); // 按单据号分组减少锁竞争和线程数
Map<String, List<PlanOrderVo>> groupedByBillNo = planOrderList.stream()
.collect(Collectors.groupingBy(PlanOrderVo::getFBillNo));
for (Map.Entry<String, List<PlanOrderVo>> entry : groupedByBillNo.entrySet()) {
String billNo = entry.getKey();
List<PlanOrderVo> orders = entry.getValue();
// 使用全局线程池每个单据号一个任务
Future<?> future = GLOBAL_EXECUTOR.submit(() -> {
// 获取锁对象防止跨请求并发
Object lock = BILL_LOCK.computeIfAbsent(billNo, k -> new Object());
synchronized (lock) {
try {
for (PlanOrderVo planOrderVo : orders) {
String currentCangKuNum = cangKuNum;
// 判断是否为组焊件
if (planOrderVo.getFmaterialidFnumber() != null) {
List<BomDetails> bomDetails = iBomDetailsService.selectMaterialZH(rooteProdet, planOrderVo.getFmaterialidFnumber());
if (bomDetails != null && !bomDetails.isEmpty()) {
currentCangKuNum = "CK025";
}
}
JdHuoZhu fidToProductionOrder = JdUtil.getFIDToProductionOrder(planOrderVo);
if (fidToProductionOrder != null) {
String result = JdUtil.updateOrder2(fidToProductionOrder, currentCangKuNum);
results.add("生产订单[" + planOrderVo.getFBillNo() + "]更新结果: " + result);
}
}
} catch (Exception e) {
log.error("更新生产订单失败: {}", billNo, e);
results.add("生产订单[" + billNo + "]更新失败: " + e.getMessage());
}
}
});
futures.add(future);
} }
} }
// 2. 更新工序计划单 (SFC_OperationPlanning) // 2. 更新工序计划单 (SFC_OperationPlanning)
List<Model> numDTOS = getSelecPlan(rooteProdet); // 将此逻辑也放入 Future 实现与生产订单更新的并行处理
boolean needUpdate = false; Future<?> planUpdateFuture = GLOBAL_EXECUTOR.submit(() -> {
for (Model numDTO : numDTOS) { try {
List<FEntity> fEntityList = numDTO.getFEntity(); List<Model> numDTOS = getSelecPlan(rooteProdet);
if (fEntityList != null && !fEntityList.isEmpty()) { boolean needUpdate = false;
for (FEntity fEntity : fEntityList) { if (numDTOS != null) {
List<FSubEntity> fSubEntityList = fEntity.getFSubEntity(); for (Model numDTO : numDTOS) {
if (fSubEntityList != null && !fSubEntityList.isEmpty()) { String fProcessIdNumber = numDTO.getFProcessId_number();
for (FSubEntity fSubEntity : fSubEntityList) { String finalCurrentCangKuNum = cangKuNum;
// 设置入库仓库 // 判断是否为组焊件
fSubEntity.setRkckName(cangKuNum); List<BomDetails> bomDetails = iBomDetailsService.selectMaterialZH(rooteProdet, fProcessIdNumber);
needUpdate = true; if (bomDetails != null && !bomDetails.isEmpty()) {
// 这里假设 selectMaterialZH 返回的是匹配的记录
finalCurrentCangKuNum = "CK025";
}
List<FEntity> fEntityList = numDTO.getFEntity();
if (fEntityList != null && !fEntityList.isEmpty()) {
for (FEntity fEntity : fEntityList) {
List<FSubEntity> fSubEntityList = fEntity.getFSubEntity();
if (fSubEntityList != null && !fSubEntityList.isEmpty()) {
for (FSubEntity fSubEntity : fSubEntityList) {
// 设置入库仓库
FSubEntity.F_HBYT_RKCK rkck = new FSubEntity.F_HBYT_RKCK();
rkck.setFNUMBER(finalCurrentCangKuNum);
fSubEntity.setF_HBYT_RKCK(rkck);
needUpdate = true;
}
}
}
}
}
if (needUpdate) {
RepoRet repoRet = updatePcessPlan2(numDTOS);
if (repoRet != null) {
results.add("工序计划单批量更新结果: " + (repoRet.isSuccessfully() ? "成功" : "失败"));
} }
} }
} }
} catch (Exception e) {
log.error("更新工序计划单失败", e);
results.add("工序计划单更新失败: " + e.getMessage());
}
});
futures.add(planUpdateFuture);
// 等待所有任务生产订单 + 工序计划单完成
for (Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
log.error("等待线程执行异常", e);
} }
} }
// 注意全局线程池不需要shutdown
if (needUpdate) { return results;
updatePcessPlan2(numDTOS);
}
return Collections.emptyList();
} }
/** /**
* 更新采购申请仓库字段 * 更新采购申请仓库字段
*/ */
public List<String> updateCgOrders(String rooteProdet, String cangKuNum) throws Exception { public List<String> updateCgOrders(String rooteProdet, String cangKuNum) throws Exception {
List<CgDTO> list = JdUtil.getCGOrderFBillNoList2(rooteProdet); List<CgDTO> list = JdUtil.getCGOrderFBillNoList2(rooteProdet);
for (CgDTO orderBillNo : list) { if (list == null || list.isEmpty()) {
List<FidEntryIdDTO> fidEntryIdDTOS = JdUtil.queryFidAndEntryId(orderBillNo.getOrderBillNo()); return Collections.emptyList();
for (FidEntryIdDTO fidEntryIdDTO : fidEntryIdDTOS) {
String s = updateCgOrder1(fidEntryIdDTO, cangKuNum);
}
} }
return null; // 创建一个线程安全的List来收集结果
List<String> results = Collections.synchronizedList(new ArrayList<>());
List<Future<?>> futures = new ArrayList<>();
// 按单据号分组减少锁竞争和线程数
Map<String, List<CgDTO>> groupedByBillNo = list.stream()
.collect(Collectors.groupingBy(CgDTO::getOrderBillNo));
for (Map.Entry<String, List<CgDTO>> entry : groupedByBillNo.entrySet()) {
String billNo = entry.getKey();
List<CgDTO> cgList = entry.getValue();
// 使用全局线程池每个单据号一个任务
Future<?> future = GLOBAL_EXECUTOR.submit(() -> {
// 获取锁对象防止跨请求并发
Object lock = BILL_LOCK.computeIfAbsent(billNo, k -> new Object());
synchronized (lock) {
try {
for (CgDTO cg : cgList) {
ImMaterial material = imMaterialService.selectByCodeAndName(cg.getMaterialCode(), cg.getMaterialName());
List<FidEntryIdDTO> fidEntryIdDTOS = JdUtil.queryFidAndEntryId(cg.getOrderBillNo());
if (fidEntryIdDTOS != null && !fidEntryIdDTOS.isEmpty()) {
for (FidEntryIdDTO fidEntryIdDTO : fidEntryIdDTOS) {
String currentCangKuNum = cangKuNum;
String materialCode = cg.getMaterialCode();
if (materialCode != null) {
// VMI库
boolean result = sqlQueryService.checkIfVmiBusiness(materialCode);
if (material != null && result) {
continue;
}
// 015开头原材料入库
else if (materialCode.startsWith("015") || materialCode.startsWith("AB")) {
currentCangKuNum = "CK001";
}
// 毛坯库
else if (materialCode.startsWith("(M)")) {
currentCangKuNum = "CK002";
}
// 电器入库
/*else if (materialCode.startsWith("DK") || materialCode.startsWith("DC") || materialCode.startsWith("DP") || materialCode.startsWith("DT")
|| materialCode.startsWith("DQ") || materialCode.startsWith("DD") || materialCode.startsWith("DF") || materialCode.startsWith("DX")
|| materialCode.startsWith("DL") || materialCode.startsWith("DH") || materialCode.startsWith("DJ") || materialCode.startsWith("DZ") || materialCode.startsWith("DB")) {
currentCangKuNum = "CK006";
}*/
}
String resultStr = updateCgOrder1(fidEntryIdDTO, currentCangKuNum);
results.add(resultStr);
}
}
}
} catch (Exception e) {
log.error("更新采购订单失败: {}", billNo, e);
results.add("更新失败: " + billNo + " - " + e.getMessage());
}
}
});
futures.add(future);
}
// 等待所有任务完成
for (Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
log.error("等待线程执行异常", e);
}
}
return results;
} }
} }