更新项目工作流
This commit is contained in:
parent
27308be393
commit
84fb4742d3
@ -6,7 +6,7 @@ import com.alibaba.fastjson.JSONObject;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.ruoyi.common.config.DingTalkProperties;
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
import com.ruoyi.common.dingding.DingCallbackCrypto;
|
import com.ruoyi.common.dingding.DingCallbackCrypto;
|
||||||
import com.ruoyi.common.dingding.DingUtil;
|
import com.ruoyi.common.dingding.util.DingUtil;
|
||||||
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;
|
||||||
@ -39,6 +39,13 @@ public class CallbackController {
|
|||||||
private DingTalkProperties dingTalkProperties;
|
private DingTalkProperties dingTalkProperties;
|
||||||
@Autowired
|
@Autowired
|
||||||
private KingdeeWorkCenterDataController kingdeeWorkCenterDataController;
|
private KingdeeWorkCenterDataController kingdeeWorkCenterDataController;
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.common.dingding.card.CardService cardService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.common.dingding.robot.RobotGroupService robotGroupService; // 注入 RobotGroupService
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.common.dingding.webhook.WebhookClient webhookClient; // 注入 WebhookClient
|
||||||
|
|
||||||
@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) {
|
||||||
@ -166,7 +173,7 @@ public class CallbackController {
|
|||||||
|
|
||||||
// 调用 DingUtil 更新卡片
|
// 调用 DingUtil 更新卡片
|
||||||
try {
|
try {
|
||||||
DingUtil.updateInteractiveCard(accessToken, outTrackId, cardDataMap);
|
cardService.updateInteractiveCard(accessToken, outTrackId, cardDataMap);
|
||||||
bizLogger.info("成功更新卡片状态: outTrackId={}, action={}", outTrackId, action);
|
bizLogger.info("成功更新卡片状态: outTrackId={}, action={}", outTrackId, action);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
bizLogger.error("更新卡片失败", e);
|
bizLogger.error("更新卡片失败", e);
|
||||||
@ -230,10 +237,8 @@ public class CallbackController {
|
|||||||
String replyContent = "你好 @" + senderId;
|
String replyContent = "你好 @" + senderId;
|
||||||
|
|
||||||
// 使用 Text 消息格式
|
// 使用 Text 消息格式
|
||||||
Map<String, Object> textParam = new HashMap<>();
|
|
||||||
textParam.put("content", replyContent);
|
|
||||||
try {
|
try {
|
||||||
DingUtil.robotGroupSend(accessToken, textParam, "sampleText", conversationId, targetAppKey, null);
|
robotGroupService.sendText(conversationId, replyContent, Collections.singletonList(senderId));
|
||||||
bizLogger.info("已回复你好(Text),并@用户: {}", senderId);
|
bizLogger.info("已回复你好(Text),并@用户: {}", senderId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
bizLogger.error("回复你好失败", e);
|
bizLogger.error("回复你好失败", e);
|
||||||
@ -344,7 +349,7 @@ public class CallbackController {
|
|||||||
|
|
||||||
// 使用回调中的 sessionWebhook 直接回复 (这是最标准的回调回复方式)
|
// 使用回调中的 sessionWebhook 直接回复 (这是最标准的回调回复方式)
|
||||||
if (sessionWebhook != null && !sessionWebhook.isEmpty()) {
|
if (sessionWebhook != null && !sessionWebhook.isEmpty()) {
|
||||||
DingUtil.sendSessionText(sessionWebhook, helpContent, Collections.singletonList(senderId));
|
webhookClient.sendText(sessionWebhook, helpContent, Collections.singletonList(senderId), null);
|
||||||
bizLogger.info("通过Webhook回复帮助信息给用户: {}", senderId);
|
bizLogger.info("通过Webhook回复帮助信息给用户: {}", senderId);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -356,36 +361,31 @@ public class CallbackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.common.dingding.token.AccessTokenManager accessTokenManager; // 注入 AccessTokenManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据回调中的 robotCode 获取对应的 AccessToken
|
* 根据回调中的 robotCode 获取对应的 AccessToken
|
||||||
*/
|
*/
|
||||||
private String getAccessToken(JSONObject eventJson) {
|
private String getAccessToken(JSONObject eventJson) {
|
||||||
String currentRobotCode = eventJson.getString("robotCode");
|
if (eventJson.containsKey("robotCode")) {
|
||||||
String currentAppKey = dingTalkProperties.getAppKey();
|
String robotCode = eventJson.getString("robotCode");
|
||||||
String currentAppSecret = dingTalkProperties.getAppSecret();
|
// 如果是安全库存机器人
|
||||||
|
if (SAFETY_STOCK_ROBOT_CODE.equals(robotCode) ||
|
||||||
// 判定是否为安全库存机器人
|
(dingTalkProperties.getSafetyStockAppKey() != null && dingTalkProperties.getSafetyStockAppKey().equals(robotCode))) {
|
||||||
boolean isSafetyRobot = false;
|
String appKey = dingTalkProperties.getSafetyStockAppKey();
|
||||||
String safetyKey = dingTalkProperties.getSafetyStockAppKey();
|
String appSecret = dingTalkProperties.getSafetyStockAppSecret();
|
||||||
if (currentRobotCode != null) {
|
// 兜底:如果配置为空,使用常量
|
||||||
if (currentRobotCode.equals(SAFETY_STOCK_ROBOT_CODE)) {
|
if (appKey == null || appKey.isEmpty()) {
|
||||||
isSafetyRobot = true;
|
appKey = SAFETY_STOCK_ROBOT_CODE;
|
||||||
} else if (currentRobotCode.equals(safetyKey)) {
|
}
|
||||||
isSafetyRobot = true;
|
// 动态获取 Token
|
||||||
|
bizLogger.info("使用安全库存机器人Token: robotCode={}", robotCode);
|
||||||
|
return DingUtil.getAccessToken(appKey, appSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 默认情况,使用主应用的 Token
|
||||||
if (isSafetyRobot) {
|
return accessTokenManager.getAccessToken();
|
||||||
// 优先使用配置的 Key/Secret
|
|
||||||
if (safetyKey != null && !safetyKey.isEmpty()) {
|
|
||||||
currentAppKey = safetyKey;
|
|
||||||
} else {
|
|
||||||
currentAppKey = SAFETY_STOCK_ROBOT_CODE;
|
|
||||||
}
|
|
||||||
currentAppSecret = dingTalkProperties.getSafetyStockAppSecret();
|
|
||||||
}
|
|
||||||
|
|
||||||
return DingUtil.getAccessToken(currentAppKey, currentAppSecret);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ 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.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;
|
||||||
|
|||||||
@ -29,7 +29,7 @@ server:
|
|||||||
port: 8033
|
port: 8033
|
||||||
servlet:
|
servlet:
|
||||||
# 应用的访问路径
|
# 应用的访问路径
|
||||||
context-path: /
|
context-path: /dev-api
|
||||||
undertow:
|
undertow:
|
||||||
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
|
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
|
||||||
max-http-post-size: -1
|
max-http-post-size: -1
|
||||||
@ -301,3 +301,5 @@ dingtalk:
|
|||||||
project-card-template-id: 2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema
|
project-card-template-id: 2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema
|
||||||
route-card-template-id: 58244f90-b92d-4ab9-b619-8e83cef67d0c.schema
|
route-card-template-id: 58244f90-b92d-4ab9-b619-8e83cef67d0c.schema
|
||||||
callback-route-key: update_route_status2
|
callback-route-key: update_route_status2
|
||||||
|
notify-user-id: 0754423610937470
|
||||||
|
route-approval-id: 01201038164023402380
|
||||||
|
|||||||
@ -4,11 +4,14 @@ import lombok.Data;
|
|||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 钉钉配置属性
|
* 钉钉配置属性
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Component
|
@Component
|
||||||
|
@Primary
|
||||||
@ConfigurationProperties(prefix = "dingtalk")
|
@ConfigurationProperties(prefix = "dingtalk")
|
||||||
public class DingTalkProperties {
|
public class DingTalkProperties {
|
||||||
/**
|
/**
|
||||||
@ -57,8 +60,41 @@ public class DingTalkProperties {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收工艺表格个人用户ID
|
||||||
|
*/
|
||||||
|
private String notifyUserId;
|
||||||
|
/**
|
||||||
|
* 接收工艺审批用户ID
|
||||||
|
*/
|
||||||
|
private String routeApprovalId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调路由 Key
|
* 回调路由 Key
|
||||||
*/
|
*/
|
||||||
private String callbackRouteKey;
|
private String callbackRouteKey;
|
||||||
|
/**
|
||||||
|
* Webhook相关配置
|
||||||
|
*/
|
||||||
|
private DingTalkProperties.Webhook webhook = new DingTalkProperties.Webhook();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Robot {
|
||||||
|
/**
|
||||||
|
* 机器人Code
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Webhook {
|
||||||
|
/**
|
||||||
|
* 默认Token
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
/**
|
||||||
|
* 默认Secret
|
||||||
|
*/
|
||||||
|
private String secret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,132 @@
|
|||||||
|
package com.ruoyi.common.dingding.card;
|
||||||
|
|
||||||
|
import com.aliyun.dingtalkcard_1_0.models.UpdateCardHeaders;
|
||||||
|
import com.aliyun.dingtalkcard_1_0.models.UpdateCardRequest;
|
||||||
|
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest;
|
||||||
|
import com.aliyun.teaopenapi.models.Config;
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import com.ruoyi.common.dingding.exception.DingException;
|
||||||
|
import com.ruoyi.common.dingding.token.AccessTokenManager;
|
||||||
|
import com.ruoyi.common.dingding.util.DingUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.aliyun.dingtalkcard_1_0.Client;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 互动卡片服务
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CardService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CardService.class);
|
||||||
|
|
||||||
|
private final AccessTokenManager tokenManager;
|
||||||
|
private final DingTalkProperties dingTalkProperties;
|
||||||
|
public CardService(AccessTokenManager tokenManager, DingTalkProperties dingTalkProperties) {
|
||||||
|
this.tokenManager = tokenManager;
|
||||||
|
this.dingTalkProperties = dingTalkProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Token 初始化账号Client (Card)
|
||||||
|
* @return Client
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static Client createCardClient() throws Exception {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
return new Client(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建并投放互动卡片 (IM 1.0)
|
||||||
|
*
|
||||||
|
* @param templateId 卡片模板ID (Standard Card ID)
|
||||||
|
* @param openConversationId 投放的目标会话ID
|
||||||
|
* @param outTrackId 唯一跟踪ID (业务侧生成)
|
||||||
|
* @param cardDataMap 卡片初始数据
|
||||||
|
* @param callbackRouteKey 回调路由Key (可选)
|
||||||
|
* @return outTrackId (成功时返回)
|
||||||
|
*/
|
||||||
|
public String createAndDeliverCard(String templateId, String openConversationId, String outTrackId, Map<String, String> cardDataMap, String callbackRouteKey) {
|
||||||
|
try {
|
||||||
|
com.aliyun.dingtalkim_1_0.Client client = createImClient();
|
||||||
|
com.aliyun.dingtalkim_1_0.models.SendInteractiveCardHeaders headers = new com.aliyun.dingtalkim_1_0.models.SendInteractiveCardHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
SendInteractiveCardRequest.SendInteractiveCardRequestCardOptions cardOptions = new com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest.SendInteractiveCardRequestCardOptions()
|
||||||
|
.setSupportForward(true);
|
||||||
|
|
||||||
|
com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest.SendInteractiveCardRequestCardData cardData = new com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest.SendInteractiveCardRequestCardData()
|
||||||
|
.setCardParamMap(cardDataMap);
|
||||||
|
|
||||||
|
com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest request = new com.aliyun.dingtalkim_1_0.models.SendInteractiveCardRequest()
|
||||||
|
.setCardTemplateId(templateId)
|
||||||
|
.setOpenConversationId(openConversationId)
|
||||||
|
.setConversationType(1)
|
||||||
|
.setOutTrackId(outTrackId)
|
||||||
|
.setRobotCode(dingTalkProperties.getRobotCode())
|
||||||
|
.setCallbackRouteKey(callbackRouteKey)
|
||||||
|
.setCardData(cardData)
|
||||||
|
.setUserIdType(1)
|
||||||
|
.setPullStrategy(false)
|
||||||
|
.setCardOptions(cardOptions);
|
||||||
|
|
||||||
|
// 发送卡片
|
||||||
|
client.sendInteractiveCardWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("互动卡片发送成功, outTrackId: {}", outTrackId);
|
||||||
|
return outTrackId;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("互动卡片发送失败", e);
|
||||||
|
throw new DingException("互动卡片发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 IM 1.0 Client
|
||||||
|
*/
|
||||||
|
private static com.aliyun.dingtalkim_1_0.Client createImClient() throws Exception {
|
||||||
|
return DingUtil.createImClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新互动卡片 (IM 1.0)
|
||||||
|
*
|
||||||
|
* @param accessToken AccessToken
|
||||||
|
* @param outTrackId 外部跟踪 ID (创建卡片时生成的唯一ID)
|
||||||
|
* @param cardDataMap 需要更新的卡片数据 (key-value)
|
||||||
|
*/
|
||||||
|
public void updateInteractiveCard(String accessToken, String outTrackId, Map<String, String> cardDataMap) {
|
||||||
|
try {
|
||||||
|
Client client = createCardClient();
|
||||||
|
|
||||||
|
UpdateCardHeaders updateCardHeaders = new UpdateCardHeaders();
|
||||||
|
updateCardHeaders.xAcsDingtalkAccessToken = accessToken;
|
||||||
|
|
||||||
|
UpdateCardRequest.UpdateCardRequestCardData cardData = new UpdateCardRequest.UpdateCardRequestCardData()
|
||||||
|
.setCardParamMap(cardDataMap);
|
||||||
|
|
||||||
|
UpdateCardRequest.UpdateCardRequestCardUpdateOptions cardUpdateOptions = new UpdateCardRequest.UpdateCardRequestCardUpdateOptions()
|
||||||
|
.setUpdateCardDataByKey(true)
|
||||||
|
.setUpdatePrivateDataByKey(false);
|
||||||
|
|
||||||
|
UpdateCardRequest updateCardRequest = new UpdateCardRequest()
|
||||||
|
.setOutTrackId(outTrackId)
|
||||||
|
.setCardData(cardData)
|
||||||
|
.setCardUpdateOptions(cardUpdateOptions);
|
||||||
|
|
||||||
|
client.updateCardWithOptions(updateCardRequest, updateCardHeaders, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new DingException("更新钉钉互动卡片失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.common.dingding.config;
|
||||||
|
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钉钉自动配置
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(DingTalkProperties.class)
|
||||||
|
@ComponentScan(basePackages = "com.ruoyi.common.dingding")
|
||||||
|
public class DingAutoConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.ruoyi.common.dingding.corp;
|
||||||
|
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.Client;
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOHeaders;
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTORequest;
|
||||||
|
import com.aliyun.teaopenapi.models.Config;
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import com.ruoyi.common.dingding.exception.DingException;
|
||||||
|
import com.ruoyi.common.dingding.token.AccessTokenManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作通知服务
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CorpMessageService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CorpMessageService.class);
|
||||||
|
|
||||||
|
private final AccessTokenManager tokenManager;
|
||||||
|
private final DingTalkProperties dingTalkProperties;
|
||||||
|
|
||||||
|
public CorpMessageService(AccessTokenManager tokenManager, DingTalkProperties dingTalkProperties) {
|
||||||
|
this.tokenManager = tokenManager;
|
||||||
|
this.dingTalkProperties = dingTalkProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送工作通知 (批量单发)
|
||||||
|
*
|
||||||
|
* @param userIds 用户ID列表
|
||||||
|
* @param content 消息内容
|
||||||
|
* @return 任务ID
|
||||||
|
*/
|
||||||
|
public String sendText(List<String> userIds, String content) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(dingTalkProperties.getRobotCode())
|
||||||
|
.setUserIds(userIds)
|
||||||
|
.setMsgKey("sampleText")
|
||||||
|
.setMsgParam("{\"content\":\"" + content + "\"}");
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("工作通知发送成功, userIds: {}", userIds);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("工作通知发送失败", e);
|
||||||
|
throw new DingException("工作通知发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.ruoyi.common.dingding.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钉钉业务异常
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
public class DingException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
public DingException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DingException(String code, String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DingException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.common.dingding.im;
|
||||||
|
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import com.ruoyi.common.dingding.token.AccessTokenManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IM 2.0 服务
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ImService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ImService.class);
|
||||||
|
|
||||||
|
private final AccessTokenManager tokenManager;
|
||||||
|
private final DingTalkProperties dingTalkProperties;
|
||||||
|
|
||||||
|
public ImService(AccessTokenManager tokenManager, DingTalkProperties dingTalkProperties) {
|
||||||
|
this.tokenManager = tokenManager;
|
||||||
|
this.dingTalkProperties = dingTalkProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送IM消息 (示例)
|
||||||
|
*/
|
||||||
|
public void sendMessage() {
|
||||||
|
// TODO: 实现IM 2.0发送逻辑
|
||||||
|
log.info("发送IM消息");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package com.ruoyi.common.dingding.media;
|
||||||
|
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders;
|
||||||
|
import com.dingtalk.api.DefaultDingTalkClient;
|
||||||
|
import com.dingtalk.api.DingTalkClient;
|
||||||
|
import com.dingtalk.api.request.OapiMediaUploadRequest;
|
||||||
|
import com.dingtalk.api.response.OapiMediaUploadResponse;
|
||||||
|
import com.ruoyi.common.dingding.exception.DingException;
|
||||||
|
import com.ruoyi.common.dingding.token.AccessTokenManager;
|
||||||
|
import com.taobao.api.FileItem;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒体文件服务
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class MediaService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MediaService.class);
|
||||||
|
|
||||||
|
private final AccessTokenManager tokenManager;
|
||||||
|
|
||||||
|
public MediaService(AccessTokenManager tokenManager) {
|
||||||
|
this.tokenManager = tokenManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传媒体文件 (旧版API)
|
||||||
|
*
|
||||||
|
* @param type 媒体类型: image, voice, file, video
|
||||||
|
* @param filePath 本地文件路径
|
||||||
|
* @return mediaId
|
||||||
|
*/
|
||||||
|
public String uploadMedia(String type, String filePath) {
|
||||||
|
try {
|
||||||
|
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/media/upload");
|
||||||
|
OapiMediaUploadRequest req = new OapiMediaUploadRequest();
|
||||||
|
req.setType(type);
|
||||||
|
req.setMedia(new FileItem(filePath));
|
||||||
|
|
||||||
|
String accessToken = tokenManager.getAccessToken();
|
||||||
|
OapiMediaUploadResponse rsp = client.execute(req, accessToken);
|
||||||
|
|
||||||
|
if (rsp.isSuccess()) {
|
||||||
|
log.info("媒体文件上传成功, type: {}, mediaId: {}", type, rsp.getMediaId());
|
||||||
|
return rsp.getMediaId();
|
||||||
|
} else {
|
||||||
|
throw new DingException("媒体文件上传失败: " + rsp.getErrmsg());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("媒体文件上传异常", e);
|
||||||
|
throw new DingException("媒体文件上传异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,223 @@
|
|||||||
|
package com.ruoyi.common.dingding.robot;
|
||||||
|
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.Client;
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOHeaders;
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTORequest;
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders;
|
||||||
|
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendRequest;
|
||||||
|
import com.aliyun.teaopenapi.models.Config;
|
||||||
|
import com.aliyun.teautil.models.RuntimeOptions;
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import com.ruoyi.common.dingding.exception.DingException;
|
||||||
|
import com.ruoyi.common.dingding.token.AccessTokenManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业机器人服务 (OpenAPI)
|
||||||
|
*
|
||||||
|
* @author tzy
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class RobotGroupService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RobotGroupService.class);
|
||||||
|
|
||||||
|
private final AccessTokenManager tokenManager;
|
||||||
|
private final DingTalkProperties properties;
|
||||||
|
|
||||||
|
public RobotGroupService(AccessTokenManager tokenManager, DingTalkProperties dingTalkProperties) {
|
||||||
|
this.tokenManager = tokenManager;
|
||||||
|
this.properties = dingTalkProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送Markdown消息到群
|
||||||
|
*
|
||||||
|
* @param openConversationId 群会话ID
|
||||||
|
* @param title 标题
|
||||||
|
* @param text Markdown内容
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public String sendMarkdown(String openConversationId, String title, String text) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
OrgGroupSendHeaders headers = new OrgGroupSendHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
// 构造 Markdown 消息参数 JSON
|
||||||
|
// 格式参考钉钉官方文档: {"title": "标题", "text": "内容"}
|
||||||
|
String msgParam = String.format("{\"title\":\"%s\",\"text\":\"%s\"}",
|
||||||
|
title.replace("\"", "\\\""),
|
||||||
|
text.replace("\"", "\\\"").replace("\n", "\\n"));
|
||||||
|
|
||||||
|
OrgGroupSendRequest request = new OrgGroupSendRequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setOpenConversationId(openConversationId)
|
||||||
|
.setMsgKey("sampleMarkdown") // 对应Markdown消息的 msgKey
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.orgGroupSendWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("机器人Markdown消息发送成功, conversationId: {}, title: {}", openConversationId, title);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人Markdown消息发送失败", e);
|
||||||
|
throw new DingException("机器人Markdown消息发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文件到群 (需先上传媒体文件获取 mediaId)
|
||||||
|
*
|
||||||
|
* @param openConversationId 群会话ID
|
||||||
|
* @param mediaId 媒体文件ID
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public String sendFile(String openConversationId, String mediaId, String fileName) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
OrgGroupSendHeaders headers = new OrgGroupSendHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
// 构造 File 消息参数 JSON
|
||||||
|
String msgParam = String.format("{\"mediaId\":\"%s\",\"fileName\":\"%s\"}",
|
||||||
|
mediaId, fileName);
|
||||||
|
|
||||||
|
OrgGroupSendRequest request = new OrgGroupSendRequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setOpenConversationId(openConversationId)
|
||||||
|
.setMsgKey("sampleFile") // 对应文件消息的 msgKey
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.orgGroupSendWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("机器人文件发送成功, conversationId: {}, fileName: {}", openConversationId, fileName);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人文件发送失败", e);
|
||||||
|
throw new DingException("机器人文件发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送Markdown消息给个人 (批量单聊)
|
||||||
|
*
|
||||||
|
* @param userId 接收人userId
|
||||||
|
* @param title 标题
|
||||||
|
* @param text Markdown内容
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public String sendMarkdownToUser(String userId, String title, String text) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
String msgParam = String.format("{\"title\":\"%s\",\"text\":\"%s\"}",
|
||||||
|
title.replace("\"", "\\\""),
|
||||||
|
text.replace("\"", "\\\"").replace("\n", "\\n"));
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setUserIds(Collections.singletonList(userId))
|
||||||
|
.setMsgKey("sampleMarkdown")
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new RuntimeOptions());
|
||||||
|
log.info("机器人个人Markdown消息发送成功, userId: {}, title: {}", userId, title);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人个人Markdown消息发送失败", e);
|
||||||
|
throw new DingException("机器人个人Markdown消息发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文件给个人 (批量单聊)
|
||||||
|
*
|
||||||
|
* @param userId 接收人userId
|
||||||
|
* @param mediaId 媒体文件ID
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
public String sendFileToUser(String userId, String mediaId, String fileName) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
BatchSendOTOHeaders headers = new BatchSendOTOHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
String msgParam = String.format("{\"mediaId\":\"%s\",\"fileName\":\"%s\"}",
|
||||||
|
mediaId, fileName);
|
||||||
|
|
||||||
|
BatchSendOTORequest request = new BatchSendOTORequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setUserIds(Arrays.asList(userId))
|
||||||
|
.setMsgKey("sampleFile")
|
||||||
|
.setMsgParam(msgParam);
|
||||||
|
|
||||||
|
client.batchSendOTOWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("机器人个人文件发送成功, userId: {}, fileName: {}", userId, fileName);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人个人文件发送失败", e);
|
||||||
|
throw new DingException("机器人个人文件发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文本消息到群
|
||||||
|
*
|
||||||
|
* @param openConversationId 群会话ID
|
||||||
|
* @param content 消息内容
|
||||||
|
* @param atUserIds @的用户ID列表
|
||||||
|
* @return processQueryKey 消息唯一标识
|
||||||
|
*/
|
||||||
|
public String sendText(String openConversationId, String content, List<String> atUserIds) {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
|
||||||
|
OrgGroupSendHeaders headers = new OrgGroupSendHeaders();
|
||||||
|
headers.xAcsDingtalkAccessToken = tokenManager.getAccessToken();
|
||||||
|
|
||||||
|
OrgGroupSendRequest request = new OrgGroupSendRequest()
|
||||||
|
.setRobotCode(properties.getRobotCode())
|
||||||
|
.setOpenConversationId(openConversationId)
|
||||||
|
.setMsgKey("sampleText") // 对应文本消息的 msgKey
|
||||||
|
.setMsgParam("{\"content\":\"" + content + "\"}");
|
||||||
|
|
||||||
|
// 注意: atUserIds 在sampleText模板中可能需要特殊处理,或者使用自定义模板
|
||||||
|
// 这里仅作示例,实际使用可能需要更复杂的JSON构建
|
||||||
|
|
||||||
|
client.orgGroupSendWithOptions(request, headers, new com.aliyun.teautil.models.RuntimeOptions());
|
||||||
|
log.info("机器人消息发送成功, conversationId: {}", openConversationId);
|
||||||
|
return "success";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人消息发送失败", e);
|
||||||
|
throw new DingException("机器人消息发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.ruoyi.common.dingding.token;
|
||||||
|
|
||||||
|
import com.aliyun.dingtalkoauth2_1_0.Client;
|
||||||
|
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
|
||||||
|
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
|
||||||
|
import com.aliyun.teaopenapi.models.Config;
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import com.ruoyi.common.dingding.exception.DingException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AccessToken管理器
|
||||||
|
* 支持自动刷新和线程安全
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AccessTokenManager {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AccessTokenManager.class);
|
||||||
|
|
||||||
|
private final DingTalkProperties properties;
|
||||||
|
private volatile String accessToken;
|
||||||
|
private volatile long expireTime;
|
||||||
|
|
||||||
|
public AccessTokenManager(DingTalkProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AccessToken
|
||||||
|
*
|
||||||
|
* @return accessToken
|
||||||
|
*/
|
||||||
|
public synchronized String getAccessToken() {
|
||||||
|
if (accessToken == null || System.currentTimeMillis() > expireTime) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新AccessToken
|
||||||
|
*/
|
||||||
|
private void refresh() {
|
||||||
|
try {
|
||||||
|
Config config = new Config();
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
Client client = new Client(config);
|
||||||
|
GetAccessTokenRequest request = new GetAccessTokenRequest()
|
||||||
|
.setAppKey(properties.getAppKey())
|
||||||
|
.setAppSecret(properties.getAppSecret());
|
||||||
|
|
||||||
|
GetAccessTokenResponse response = client.getAccessToken(request);
|
||||||
|
if (response.getBody() != null) {
|
||||||
|
this.accessToken = response.getBody().getAccessToken();
|
||||||
|
// 设置过期时间,提前100秒刷新
|
||||||
|
this.expireTime = System.currentTimeMillis() + (response.getBody().getExpireIn() * 1000) - 100000;
|
||||||
|
log.info("钉钉AccessToken刷新成功,过期时间: {}", this.expireTime);
|
||||||
|
} else {
|
||||||
|
throw new DingException("刷新AccessToken失败,返回为空");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("刷新钉钉AccessToken失败", e);
|
||||||
|
throw new DingException("刷新钉钉AccessToken失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.ruoyi.common.dingding;
|
package com.ruoyi.common.dingding.util;
|
||||||
|
|
||||||
import com.aliyun.dingtalkcard_1_0.models.*;
|
import com.aliyun.dingtalkcard_1_0.models.*;
|
||||||
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardHeaders;
|
import com.aliyun.dingtalkim_1_0.models.SendInteractiveCardHeaders;
|
||||||
@ -18,6 +18,7 @@ import com.dingtalk.api.response.OapiRobotSendResponse;
|
|||||||
import com.dingtalk.api.response.OapiV2UserGetResponse;
|
import com.dingtalk.api.response.OapiV2UserGetResponse;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.dingding.DingChatCreateResult;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
@ -28,6 +29,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
public class DingUtil {
|
public class DingUtil {
|
||||||
public static final String CUSTOM_ROBOT_TOKEN = "1f3711d72605f77e8ba8e3e3fe0d98989361a7bfb9c30b51a23520eeb783652e";
|
public static final String CUSTOM_ROBOT_TOKEN = "1f3711d72605f77e8ba8e3e3fe0d98989361a7bfb9c30b51a23520eeb783652e";
|
||||||
|
|
||||||
@ -643,6 +645,7 @@ public class DingUtil {
|
|||||||
*/
|
*/
|
||||||
public static String createAndDeliverCard(String accessToken, String cardTemplateId, String robotCode,
|
public static String createAndDeliverCard(String accessToken, String cardTemplateId, String robotCode,
|
||||||
String openConversationId, String outTrackId, Map<String, String> cardDataMap, String callbackRouteKey) {
|
String openConversationId, String outTrackId, Map<String, String> cardDataMap, String callbackRouteKey) {
|
||||||
|
// 修改为 1 (群聊),原本为 0 (单聊) 导致接口报错
|
||||||
return createAndDeliverCardInternal(accessToken, cardTemplateId, robotCode, 1, openConversationId, null, outTrackId, cardDataMap, callbackRouteKey);
|
return createAndDeliverCardInternal(accessToken, cardTemplateId, robotCode, 1, openConversationId, null, outTrackId, cardDataMap, callbackRouteKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.ruoyi.common.dingding.webhook;
|
||||||
|
|
||||||
|
import com.dingtalk.api.DefaultDingTalkClient;
|
||||||
|
import com.dingtalk.api.DingTalkClient;
|
||||||
|
import com.dingtalk.api.request.OapiRobotSendRequest;
|
||||||
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
|
import com.ruoyi.common.dingding.exception.DingException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webhook客户端(群机器人)
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class WebhookClient {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WebhookClient.class);
|
||||||
|
private final DingTalkProperties properties;
|
||||||
|
|
||||||
|
public WebhookClient(DingTalkProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文本消息
|
||||||
|
*
|
||||||
|
* @param webhookUrl Webhook URL (如果为空则使用配置中的默认值)
|
||||||
|
* @param content 消息内容
|
||||||
|
* @param atUserIds @的用户ID列表
|
||||||
|
* @param atMobiles @的手机号列表
|
||||||
|
*/
|
||||||
|
public void sendText(String webhookUrl, String content, List<String> atUserIds, List<String> atMobiles) {
|
||||||
|
String url = StringUtils.isEmpty(webhookUrl) ?
|
||||||
|
"https://oapi.dingtalk.com/robot/send?access_token=" + properties.getWebhook().getToken() :
|
||||||
|
webhookUrl;
|
||||||
|
|
||||||
|
try {
|
||||||
|
DingTalkClient client = new DefaultDingTalkClient(url);
|
||||||
|
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 (atMobiles != null && !atMobiles.isEmpty()) {
|
||||||
|
at.setAtMobiles(atMobiles);
|
||||||
|
}
|
||||||
|
if ((atUserIds != null && !atUserIds.isEmpty()) || (atMobiles != null && !atMobiles.isEmpty())) {
|
||||||
|
at.setIsAtAll(false);
|
||||||
|
}
|
||||||
|
req.setAt(at);
|
||||||
|
client.execute(req);
|
||||||
|
log.info("Webhook消息发送成功: {}", content);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Webhook消息发送失败", e);
|
||||||
|
throw new DingException("Webhook消息发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -520,6 +520,104 @@ public class FtpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载单个文件
|
||||||
|
*
|
||||||
|
* @param ftpHost FTP服务器IP
|
||||||
|
* @param ftpUserName FTP用户名
|
||||||
|
* @param ftpPassword FTP密码
|
||||||
|
* @param ftpPort FTP端口
|
||||||
|
* @param remotePath 远程文件完整路径 (包含文件名)
|
||||||
|
* @param localPath 本地保存完整路径 (包含文件名)
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
public static boolean downloadSingleFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort,
|
||||||
|
String remotePath, String localPath) {
|
||||||
|
FTPClient ftpClient = null;
|
||||||
|
try {
|
||||||
|
ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
|
||||||
|
if (ftpClient == null || !ftpClient.isConnected()) {
|
||||||
|
log.error("FTP连接失败");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认优先使用GBK,适配大多数Windows FTP服务器
|
||||||
|
ftpClient.setControlEncoding("GBK");
|
||||||
|
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
|
||||||
|
ftpClient.enterLocalPassiveMode();
|
||||||
|
|
||||||
|
File localFile = new File(localPath);
|
||||||
|
File parentDir = localFile.getParentFile();
|
||||||
|
if (!parentDir.exists()) {
|
||||||
|
parentDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (OutputStream os = new FileOutputStream(localFile)) {
|
||||||
|
// 尝试直接下载 (GBK)
|
||||||
|
boolean success = ftpClient.retrieveFile(remotePath, os);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
// 如果GBK失败,尝试开启UTF-8重试
|
||||||
|
if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
|
||||||
|
ftpClient.setControlEncoding("UTF-8");
|
||||||
|
// UTF-8下无需手动转码,直接使用原路径
|
||||||
|
success = ftpClient.retrieveFile(remotePath, os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果还失败,尝试 ISO-8859-1 (兼容某些特殊配置)
|
||||||
|
if (!success) {
|
||||||
|
String isoPath = new String(remotePath.getBytes("GBK"), "ISO-8859-1");
|
||||||
|
success = ftpClient.retrieveFile(isoPath, os);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
log.info("文件下载成功: {}", remotePath);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.error("文件下载失败: {} - 响应: {}", remotePath, ftpClient.getReplyString());
|
||||||
|
// 尝试切换目录再下载文件名
|
||||||
|
// 有些FTP服务器不支持直接 retrieveFile("/path/to/file")
|
||||||
|
int lastSlash = remotePath.lastIndexOf("/");
|
||||||
|
if (lastSlash >= 0) {
|
||||||
|
String dir = remotePath.substring(0, lastSlash);
|
||||||
|
String name = remotePath.substring(lastSlash + 1);
|
||||||
|
|
||||||
|
if (ftpClient.changeWorkingDirectory(dir) ||
|
||||||
|
ftpClient.changeWorkingDirectory(new String(dir.getBytes("GBK"), "ISO-8859-1"))) {
|
||||||
|
|
||||||
|
success = ftpClient.retrieveFile(name, os);
|
||||||
|
if (!success) {
|
||||||
|
success = ftpClient.retrieveFile(new String(name.getBytes("GBK"), "ISO-8859-1"), os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果失败,删除空文件
|
||||||
|
if (localFile.exists() && localFile.length() == 0) {
|
||||||
|
localFile.delete();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("FTP下载异常: {}", remotePath, e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (ftpClient != null && ftpClient.isConnected()) {
|
||||||
|
try {
|
||||||
|
ftpClient.logout();
|
||||||
|
ftpClient.disconnect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 列出远程目录中的文件
|
* 列出远程目录中的文件
|
||||||
*
|
*
|
||||||
|
|||||||
@ -53,6 +53,18 @@ public class ExcelUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步导入(适用于小数据量)
|
||||||
|
*
|
||||||
|
* @param is 输入流
|
||||||
|
* @param clazz 对象类型
|
||||||
|
* @param charset 字符集
|
||||||
|
* @return 转换后集合
|
||||||
|
*/
|
||||||
|
public static <T> List<T> importExcel(InputStream is, Class<T> clazz, String charset) {
|
||||||
|
return EasyExcel.read(is).head(clazz).charset(java.nio.charset.Charset.forName(charset)).autoCloseStream(false).sheet().doReadSync();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用校验监听器 异步导入 同步返回
|
* 使用校验监听器 异步导入 同步返回
|
||||||
*
|
*
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.system.domain.ProcessRoute;
|
import com.ruoyi.system.domain.ProcessRoute;
|
||||||
import com.ruoyi.system.domain.dto.*;
|
import com.ruoyi.system.domain.dto.*;
|
||||||
import com.ruoyi.common.config.DingTalkProperties;
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
import com.ruoyi.common.dingding.DingUtil;
|
import com.ruoyi.common.dingding.util.DingUtil;
|
||||||
import com.ruoyi.common.utils.JdUtils;
|
import com.ruoyi.common.utils.JdUtils;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.system.domain.BomDetails;
|
import com.ruoyi.system.domain.BomDetails;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -107,4 +108,25 @@ public class FigureSaveController extends BaseController {
|
|||||||
@PathVariable Long[] ids) {
|
@PathVariable Long[] ids) {
|
||||||
return toAjax(iFigureSaveService.deleteWithValidByIds(Arrays.asList(ids), true));
|
return toAjax(iFigureSaveService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入产品
|
||||||
|
*/
|
||||||
|
@Log(title = "导入产品", businessType = BusinessType.IMPORT)
|
||||||
|
@SaCheckPermission("system:save:import")
|
||||||
|
@PostMapping("/importData")
|
||||||
|
public R<String> importData(MultipartFile file, boolean updateSupport) throws Exception {
|
||||||
|
String filename = file.getOriginalFilename();
|
||||||
|
List<FigureSaveVo> list;
|
||||||
|
// 如果是CSV文件,尝试使用GBK编码读取(兼容Windows下生成的CSV)
|
||||||
|
if (filename != null && (filename.endsWith(".csv") || filename.endsWith(".CSV"))) {
|
||||||
|
list = ExcelUtil.importExcel(file.getInputStream(), FigureSaveVo.class, "GBK");
|
||||||
|
} else {
|
||||||
|
// list = ExcelUtil.importExcel(file.getInputStream(), FigureSaveVo.class);
|
||||||
|
list = ExcelUtil.importExcelSheet1(file.getInputStream(), FigureSaveVo.class, true).getList();
|
||||||
|
}
|
||||||
|
String operName = getLoginUser().getUsername();
|
||||||
|
String message = iFigureSaveService.importData(list, updateSupport, operName);
|
||||||
|
return R.ok(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import com.google.gson.JsonObject;
|
|||||||
import com.kingdee.bos.webapi.sdk.K3CloudApi;
|
import com.kingdee.bos.webapi.sdk.K3CloudApi;
|
||||||
import com.ruoyi.common.utils.HttpRequestUtil;
|
import com.ruoyi.common.utils.HttpRequestUtil;
|
||||||
import com.ruoyi.common.utils.WxRobotUtil;
|
import com.ruoyi.common.utils.WxRobotUtil;
|
||||||
import com.ruoyi.common.dingding.DingUtil;
|
import com.ruoyi.common.dingding.util.DingUtil;
|
||||||
import com.ruoyi.common.config.DingTalkProperties;
|
import com.ruoyi.common.config.DingTalkProperties;
|
||||||
import com.ruoyi.common.poi.ExcelTemplateProc;
|
import com.ruoyi.common.poi.ExcelTemplateProc;
|
||||||
import com.ruoyi.common.poi.DynamicDataMapping;
|
import com.ruoyi.common.poi.DynamicDataMapping;
|
||||||
@ -775,7 +775,7 @@ public class KingdeeWorkCenterDataController extends BaseController {
|
|||||||
.append("- 预计可用量: ").append(String.format("%.2f", safetyStock.getCurrentStock())).append("\n")
|
.append("- 预计可用量: ").append(String.format("%.2f", safetyStock.getCurrentStock())).append("\n")
|
||||||
.append("- 即时库存: ").append(String.format("%.2f", safetyStock.getSecAvbqty())).append("\n")
|
.append("- 即时库存: ").append(String.format("%.2f", safetyStock.getSecAvbqty())).append("\n")
|
||||||
.append("- 最大库存: ").append(String.format("%.2f", safetyStock.getMaxsafetyStock())).append("\n")
|
.append("- 最大库存: ").append(String.format("%.2f", safetyStock.getMaxsafetyStock())).append("\n")
|
||||||
.append("- 创建时间: ").append(DateUtil.formatDateTime(safetyStock.getCreateTime())).append("\n")
|
.append("- 创建时间: ").append(DateUtil.formatDateTime(new Date())).append("\n")
|
||||||
.append("---\n"); // 添加分隔线
|
.append("---\n"); // 添加分隔线
|
||||||
}
|
}
|
||||||
// 添加总数汇总
|
// 添加总数汇总
|
||||||
|
|||||||
@ -56,6 +56,7 @@ import com.ruoyi.system.domain.bo.ProcessOrderProBo;
|
|||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
import shade.com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生产项目
|
* 生产项目
|
||||||
@ -143,6 +144,18 @@ public class ProcessOrderProController extends BaseController {
|
|||||||
return toAjax(iProcessOrderProService.updateByBo(bo));
|
return toAjax(iProcessOrderProService.updateByBo(bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空项目关联数据(保留项目)
|
||||||
|
*
|
||||||
|
* @param ids 主键串
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:orderPro:clear")
|
||||||
|
@Log(title = "清空项目数据", businessType = BusinessType.CLEAN)
|
||||||
|
@DeleteMapping("/clear/{ids}")
|
||||||
|
public R<Void> clear(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
||||||
|
return iProcessOrderProService.clearProjectData(Arrays.asList(ids));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除项目令号
|
* 删除项目令号
|
||||||
*
|
*
|
||||||
@ -152,10 +165,20 @@ public class ProcessOrderProController extends BaseController {
|
|||||||
@Log(title = "项目令号", businessType = BusinessType.DELETE)
|
@Log(title = "项目令号", businessType = BusinessType.DELETE)
|
||||||
@DeleteMapping("/{ids}")
|
@DeleteMapping("/{ids}")
|
||||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
||||||
|
|
||||||
return iProcessOrderProService.deleteWithValidByIds(Arrays.asList(ids), true);
|
return iProcessOrderProService.deleteWithValidByIds(Arrays.asList(ids), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送钉钉卡片
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:orderPro:pushDingTalkCard")
|
||||||
|
@Log(title = "推送钉钉卡片", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/pushDingTalkCard/{id}")
|
||||||
|
public R<Void> pushDingTalkCard(@PathVariable Long id) {
|
||||||
|
iProcessOrderProService.pushDingTalkCard(id);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
@SaCheckPermission("system:orderPro:getExcel")
|
@SaCheckPermission("system:orderPro:getExcel")
|
||||||
@Log(title = "获取项目生产数据表", businessType = BusinessType.INSERT)
|
@Log(title = "获取项目生产数据表", businessType = BusinessType.INSERT)
|
||||||
@RepeatSubmit()
|
@RepeatSubmit()
|
||||||
@ -371,6 +394,16 @@ public class ProcessOrderProController extends BaseController {
|
|||||||
return iProcessOrderProService.getOverdueProjects();
|
return iProcessOrderProService.getOverdueProjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新并计算项目延期状态
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:orderPro:list")
|
||||||
|
@GetMapping("/refreshDelayStatus")
|
||||||
|
public R<Void> refreshDelayStatus() {
|
||||||
|
iProcessOrderProService.refreshDelayStatus();
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
@SaCheckPermission("system:orderPro:geMRPResults")
|
@SaCheckPermission("system:orderPro:geMRPResults")
|
||||||
@Log(title = "获取MRP复核结果", businessType = BusinessType.OTHER)
|
@Log(title = "获取MRP复核结果", businessType = BusinessType.OTHER)
|
||||||
@PostMapping("/getMRPResults/{id}")
|
@PostMapping("/getMRPResults/{id}")
|
||||||
@ -762,9 +795,7 @@ public class ProcessOrderProController extends BaseController {
|
|||||||
map.put("processDescription", route.getProcessDescription());
|
map.put("processDescription", route.getProcessDescription());
|
||||||
map.put("productionOrderNo", orderPro.getProductionOrderNo());
|
map.put("productionOrderNo", orderPro.getProductionOrderNo());
|
||||||
map.put("productionName", orderPro.getProductionName());
|
map.put("productionName", orderPro.getProductionName());
|
||||||
map.put("drawingNo", orderPro.getDrawingNo());
|
|
||||||
map.put("mainProducts", orderPro.getDrawingName());
|
|
||||||
map.put("mainProductsName", orderPro.getDrawingName());
|
|
||||||
mapList.add(map);
|
mapList.add(map);
|
||||||
}
|
}
|
||||||
return mapList;
|
return mapList;
|
||||||
@ -2136,4 +2167,53 @@ public class ProcessOrderProController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@SaCheckPermission("system:orderPro:downloadZip")
|
||||||
|
@Log(title = "下载项目DWG", businessType = BusinessType.OTHER)
|
||||||
|
@GetMapping("/downloadDwg/{id}")
|
||||||
|
public void downloadDwg(@PathVariable("id") List<Long> ids, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
R<String> result = iProcessOrderProService.downloadDwg(ids);
|
||||||
|
if (result.getCode() == 200) {
|
||||||
|
String filePath = result.getData();
|
||||||
|
File downloadFile = new File(filePath);
|
||||||
|
|
||||||
|
if (downloadFile.exists()) {
|
||||||
|
// 设置响应头
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
String fileName = URLEncoder.encode(downloadFile.getName(), "UTF-8").replaceAll("\\+", "%20");
|
||||||
|
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
|
||||||
|
|
||||||
|
// 写入响应流
|
||||||
|
try (FileInputStream fis = new FileInputStream(downloadFile);
|
||||||
|
OutputStream os = response.getOutputStream()) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = fis.read(buffer)) > 0) {
|
||||||
|
os.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
// 删除临时文件
|
||||||
|
downloadFile.delete();
|
||||||
|
} else {
|
||||||
|
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件生成失败");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.setContentType("application/json;charset=utf-8");
|
||||||
|
response.getWriter().write(JSONObject.toJSONString(result));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载DWG失败", e);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "下载失败: " + e.getMessage());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
log.error("发送错误响应失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import com.ruoyi.common.core.domain.entity.SysUser;
|
|||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
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;
|
||||||
import com.ruoyi.common.dingding.DingUtil;
|
import com.ruoyi.common.dingding.util.DingUtil;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.common.excel.DefaultExcelListener;
|
import com.ruoyi.common.excel.DefaultExcelListener;
|
||||||
import com.ruoyi.common.excel.ExcelResult;
|
import com.ruoyi.common.excel.ExcelResult;
|
||||||
@ -439,12 +439,22 @@ public class ProcessRouteController extends BaseController {
|
|||||||
cardDataMap.put("status", StringUtils.defaultIfEmpty("待审批", "待审批"));
|
cardDataMap.put("status", StringUtils.defaultIfEmpty("待审批", "待审批"));
|
||||||
|
|
||||||
String outTrackId = "audit-card-" + System.currentTimeMillis();
|
String outTrackId = "audit-card-" + System.currentTimeMillis();
|
||||||
String outTrackId1 = DingUtil.createAndDeliverCard(accessToken, dingTalkProperties.getRouteCardTemplateId(), dingTalkProperties.getRobotCode(), dingTalkProperties.getOpenConversationId(), outTrackId, cardDataMap, dingTalkProperties.getCallbackRouteKey());
|
// 调用发送给工艺负责人的方法 createAndDeliverCardToUser
|
||||||
|
DingUtil.createAndDeliverCardToUser(
|
||||||
|
accessToken,
|
||||||
|
dingTalkProperties.getRouteCardTemplateId(),
|
||||||
|
Collections.singletonList(dingTalkProperties.getNotifyUserId()),
|
||||||
|
outTrackId,
|
||||||
|
cardDataMap,
|
||||||
|
dingTalkProperties.getCallbackRouteKey()
|
||||||
|
);
|
||||||
|
// 统一存入带 userId 后缀的完整 outTrackId,以便和钉钉回调保持一致
|
||||||
|
String fullOutTrackId = outTrackId + "_" + dingTalkProperties.getNotifyUserId();
|
||||||
// 更新数据库中的AuditCardId
|
// 更新数据库中的AuditCardId
|
||||||
ProcessOrderPro update = new ProcessOrderPro();
|
ProcessOrderPro update = new ProcessOrderPro();
|
||||||
update.setId(order.getId());
|
update.setId(order.getId());
|
||||||
update.setUpdateBy("Dingding");
|
update.setUpdateBy("Dingding");
|
||||||
update.setAuditCardId(outTrackId1);
|
update.setAuditCardId(fullOutTrackId);
|
||||||
update.setBomStatus(2L);
|
update.setBomStatus(2L);
|
||||||
// 更新审核状态为1(已审核/提交审核)
|
// 更新审核状态为1(已审核/提交审核)
|
||||||
update.setRouteStatus(0L);
|
update.setRouteStatus(0L);
|
||||||
|
|||||||
@ -3,10 +3,13 @@ package com.ruoyi.system.domain;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import com.ruoyi.common.core.domain.BaseEntity;
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目产品对象 figure_save
|
* 项目产品对象 figure_save
|
||||||
*
|
*
|
||||||
@ -65,4 +68,25 @@ public class FigureSave extends BaseEntity {
|
|||||||
* 箱体类型
|
* 箱体类型
|
||||||
*/
|
*/
|
||||||
private String boxType;
|
private String boxType;
|
||||||
|
/**
|
||||||
|
* 文件类型
|
||||||
|
*/
|
||||||
|
private String fileType;
|
||||||
|
/**
|
||||||
|
* 出图人
|
||||||
|
*/
|
||||||
|
private String drawBy;
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
private Integer whetherDelay;
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
private Long delayDay;
|
||||||
|
/**
|
||||||
|
* 出图时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date drawTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,17 @@ public class ProcessOrderPro extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
private Integer whetherDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
private Long delayDay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生产令号
|
* 生产令号
|
||||||
*/
|
*/
|
||||||
@ -36,14 +47,6 @@ public class ProcessOrderPro extends BaseEntity {
|
|||||||
* 项目名称
|
* 项目名称
|
||||||
*/
|
*/
|
||||||
private String productionName;
|
private String productionName;
|
||||||
/**
|
|
||||||
* 图号
|
|
||||||
*/
|
|
||||||
private String drawingNo;
|
|
||||||
/**
|
|
||||||
* 名称
|
|
||||||
*/
|
|
||||||
private String drawingName;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计划结束时间
|
* 计划结束时间
|
||||||
@ -72,11 +75,10 @@ public class ProcessOrderPro extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 是否企标 (0-否, 1-是)
|
* 是否企标 (0-否, 1-是)
|
||||||
*/
|
*/
|
||||||
private Integer isEnterpriseStandard;
|
private String isEnterpriseStandard;
|
||||||
/**
|
/**
|
||||||
* 图纸路径
|
* 图纸路径
|
||||||
*/
|
*/
|
||||||
@Translation(type = TransConstant.OSS_ID_TO_URL)
|
|
||||||
private String drawingPath;
|
private String drawingPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import com.ruoyi.common.core.validate.EditGroup;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import javax.validation.constraints.*;
|
import javax.validation.constraints.*;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,9 +20,7 @@ import javax.validation.constraints.*;
|
|||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class FigureSaveBo extends BaseEntity {
|
public class FigureSaveBo extends BaseEntity {
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,5 +79,33 @@ public class FigureSaveBo extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String boxType;
|
private String boxType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件类型
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "文件类型不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String fileType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出图人
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "出图人不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String drawBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
@NotNull(message = "是否延期:0否 1是不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Integer whetherDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
@NotNull(message = "延期天数不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Long delayDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出图时间
|
||||||
|
*/
|
||||||
|
@NotNull(message = "出图时间不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Date drawTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
|
||||||
public class ProcessOrderProBo extends BaseEntity {
|
public class ProcessOrderProBo extends BaseEntity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,15 +40,7 @@ public class ProcessOrderProBo extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String productionName;
|
private String productionName;
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目图号
|
|
||||||
*/
|
|
||||||
private String drawingNo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 产品名称
|
|
||||||
*/
|
|
||||||
private String drawingName;
|
|
||||||
/**
|
/**
|
||||||
* 计划结束时间
|
* 计划结束时间
|
||||||
*/
|
*/
|
||||||
@ -81,7 +74,13 @@ public class ProcessOrderProBo extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 是否企标 (0-否, 1-是)
|
* 是否企标 (0-否, 1-是)
|
||||||
*/
|
*/
|
||||||
private Integer isEnterpriseStandard;
|
private String isEnterpriseStandard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否企标 (查询多选使用)
|
||||||
|
*/
|
||||||
|
private List<String> isEnterpriseStandardList;
|
||||||
|
|
||||||
|
|
||||||
private String drawingPath;
|
private String drawingPath;
|
||||||
/**
|
/**
|
||||||
@ -151,4 +150,13 @@ public class ProcessOrderProBo extends BaseEntity {
|
|||||||
* 通知卡片Id
|
* 通知卡片Id
|
||||||
*/
|
*/
|
||||||
private String notificationCardId;
|
private String notificationCardId;
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
private Integer whetherDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
private Long delayDay;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
package com.ruoyi.system.domain.dto.excuteDrawing;
|
package com.ruoyi.system.domain.dto.excuteDrawing;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class RigidChainModelDTO {
|
public class RigidChainModelDTO {
|
||||||
@ -28,5 +31,29 @@ public class RigidChainModelDTO {
|
|||||||
private String figureNumber;
|
private String figureNumber;
|
||||||
private String productName;
|
private String productName;
|
||||||
private String figureName;
|
private String figureName;
|
||||||
|
/**
|
||||||
|
* 文件类型
|
||||||
|
*/
|
||||||
|
private String fileType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出图人
|
||||||
|
*/
|
||||||
|
private String drawBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
private Integer whetherDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
private Long delayDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出图时间
|
||||||
|
*/
|
||||||
|
private Date drawTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,42 +22,41 @@ public class FigureSaveVo {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 生产令号
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "生产令号", index = 0)
|
||||||
|
private String productionCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品编码
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "产品编码", index = 1)
|
||||||
|
private String figureNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 产品名称
|
* 产品名称
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "产品名称")
|
@ExcelProperty(value = "产品名称", index = 2)
|
||||||
private String figureName;
|
private String figureName;
|
||||||
|
|
||||||
/**
|
|
||||||
* 生产令号
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "生产令号")
|
|
||||||
private String productionCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 产品型号
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "产品型号")
|
|
||||||
private String figureNumber;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数量
|
* 数量
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "数量")
|
@ExcelProperty(value = "数量", index = 3)
|
||||||
private Long figureNum;
|
private Long figureNum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 行程
|
* 行程
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "行程")
|
@ExcelProperty(value = "行程", index = 4)
|
||||||
private Long jdInventory;
|
private Long jdInventory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图纸路径
|
* 图纸路径
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "图纸路径")
|
@ExcelProperty(value = "图纸路径", index = 5)
|
||||||
private String drawPath;
|
private String drawPath;
|
||||||
/**
|
/**
|
||||||
* 类型
|
* 类型
|
||||||
@ -67,17 +67,41 @@ public class FigureSaveVo {
|
|||||||
/**
|
/**
|
||||||
* 关联项目表
|
* 关联项目表
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "关联项目表")
|
|
||||||
private Long pid;
|
private Long pid;
|
||||||
/**
|
/**
|
||||||
* 轴向
|
* 轴向
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "轴向")
|
|
||||||
private String axialType;
|
private String axialType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 箱体类型
|
* 箱体类型
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "箱体类型")
|
|
||||||
private String boxType;
|
private String boxType;
|
||||||
|
/**
|
||||||
|
* 文件类型
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "文件类型")
|
||||||
|
private String fileType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出图人
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "出图人")
|
||||||
|
private String drawBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
private Integer whetherDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
private Long delayDay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 出图时间
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "出图时间")
|
||||||
|
private Date drawTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,17 +44,7 @@ public class ProcessOrderProVo {
|
|||||||
@ExcelProperty(value = "项目名称")
|
@ExcelProperty(value = "项目名称")
|
||||||
private String productionName;
|
private String productionName;
|
||||||
|
|
||||||
/**
|
|
||||||
* 图号
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "图号")
|
|
||||||
private String drawingNo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 名称
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "名称")
|
|
||||||
private String drawingName;
|
|
||||||
/**
|
/**
|
||||||
* 计划结束时间
|
* 计划结束时间
|
||||||
*/
|
*/
|
||||||
@ -94,7 +84,7 @@ public class ProcessOrderProVo {
|
|||||||
* 是否企标 (0-否, 1-是)
|
* 是否企标 (0-否, 1-是)
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "是否企标 (0-否, 1-是)")
|
@ExcelProperty(value = "是否企标 (0-否, 1-是)")
|
||||||
private Integer isEnterpriseStandard;
|
private String isEnterpriseStandard;
|
||||||
/**
|
/**
|
||||||
* bom状态(0,未完成 1,完成 2已上传)
|
* bom状态(0,未完成 1,完成 2已上传)
|
||||||
*/
|
*/
|
||||||
@ -115,6 +105,21 @@ public class ProcessOrderProVo {
|
|||||||
@Translation(mapper = "drawingPath", type = TransConstant.OSS_ID_TO_URL)
|
@Translation(mapper = "drawingPath", type = TransConstant.OSS_ID_TO_URL)
|
||||||
private String drawingPathUrl;
|
private String drawingPathUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图纸路径列表(逗号分隔转数组)
|
||||||
|
*/
|
||||||
|
public String[] getDrawingPathList() {
|
||||||
|
// 优先使用 drawingPathUrl (已翻译的 URL)
|
||||||
|
if (drawingPathUrl != null && !drawingPathUrl.isEmpty()) {
|
||||||
|
return drawingPathUrl.split(",");
|
||||||
|
}
|
||||||
|
// 如果 drawingPathUrl 为空,尝试直接使用 drawingPath (可能是未翻译的 ID 或直接存的 URL)
|
||||||
|
if (drawingPath != null && !drawingPath.isEmpty()) {
|
||||||
|
return drawingPath.split(",");
|
||||||
|
}
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
/**
|
/**
|
||||||
* 创建人
|
* 创建人
|
||||||
@ -170,4 +175,14 @@ public class ProcessOrderProVo {
|
|||||||
@ExcelProperty(value = "通知卡片Id")
|
@ExcelProperty(value = "通知卡片Id")
|
||||||
private String notificationCardId;
|
private String notificationCardId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否延期:0否 1是
|
||||||
|
*/
|
||||||
|
private Integer whetherDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延期天数
|
||||||
|
*/
|
||||||
|
private Long delayDay;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.ruoyi.system.mapper;
|
|||||||
import com.ruoyi.system.domain.BomDetails;
|
import com.ruoyi.system.domain.BomDetails;
|
||||||
import com.ruoyi.system.domain.vo.BomDetailsVo;
|
import com.ruoyi.system.domain.vo.BomDetailsVo;
|
||||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||||
|
import org.apache.ibatis.annotations.Delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bom明细Mapper接口
|
* bom明细Mapper接口
|
||||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
|||||||
* @date 2024-04-08
|
* @date 2024-04-08
|
||||||
*/
|
*/
|
||||||
public interface BomDetailsMapper extends BaseMapperPlus<BomDetailsMapper, BomDetails, BomDetailsVo> {
|
public interface BomDetailsMapper extends BaseMapperPlus<BomDetailsMapper, BomDetails, BomDetailsVo> {
|
||||||
|
@Delete("DELETE FROM bom_details where total_weight = #{productionOrderNo}")
|
||||||
|
void deleteWithProCode(String productionOrderNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.ruoyi.system.mapper;
|
|||||||
import com.ruoyi.system.domain.FigureSave;
|
import com.ruoyi.system.domain.FigureSave;
|
||||||
import com.ruoyi.system.domain.vo.FigureSaveVo;
|
import com.ruoyi.system.domain.vo.FigureSaveVo;
|
||||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||||
|
import org.apache.ibatis.annotations.Delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 外购件临时Mapper接口
|
* 外购件临时Mapper接口
|
||||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
|||||||
* @date 2024-05-28
|
* @date 2024-05-28
|
||||||
*/
|
*/
|
||||||
public interface FigureSaveMapper extends BaseMapperPlus<FigureSaveMapper, FigureSave, FigureSaveVo> {
|
public interface FigureSaveMapper extends BaseMapperPlus<FigureSaveMapper, FigureSave, FigureSaveVo> {
|
||||||
|
@Delete("DELETE FROM figure_save where production_code = #{productionOrderNo}")
|
||||||
|
void deleteWithProCode(String productionOrderNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.ruoyi.system.mapper;
|
|||||||
import com.ruoyi.system.domain.MaterialBom;
|
import com.ruoyi.system.domain.MaterialBom;
|
||||||
import com.ruoyi.system.domain.vo.MaterialBomVo;
|
import com.ruoyi.system.domain.vo.MaterialBomVo;
|
||||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||||
|
import org.apache.ibatis.annotations.Delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bomaterialMapper接口
|
* bomaterialMapper接口
|
||||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
|||||||
* @date 2024-11-08
|
* @date 2024-11-08
|
||||||
*/
|
*/
|
||||||
public interface MaterialBomMapper extends BaseMapperPlus<MaterialBomMapper, MaterialBom, MaterialBomVo> {
|
public interface MaterialBomMapper extends BaseMapperPlus<MaterialBomMapper, MaterialBom, MaterialBomVo> {
|
||||||
|
@Delete("DELETE FROM material_bom where project_number = #{productionOrderNo}")
|
||||||
|
void deleteWithProCode(String productionOrderNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public interface ProcessRouteMapper extends BaseMapperPlus<ProcessRouteMapper, P
|
|||||||
@Select("SELECT COUNT(1) FROM process_route WHERE process_no = #{processNo} AND material_code = #{materialCode} AND route_description = #{routeDescription}")
|
@Select("SELECT COUNT(1) FROM process_route WHERE process_no = #{processNo} AND material_code = #{materialCode} AND route_description = #{routeDescription}")
|
||||||
boolean existsByProcessNoAndMaterialCode(@Param("processNo") Long processNo, @Param("materialCode") String materialCode);
|
boolean existsByProcessNoAndMaterialCode(@Param("processNo") Long processNo, @Param("materialCode") String materialCode);
|
||||||
|
|
||||||
@Delete("DELETE FROM process_route where route_description = #{productionOrderNo}")
|
@Delete("DELETE FROM process_route where route_description = #{routeDescription}")
|
||||||
Boolean deleteByProCode(@Param("routeDescription") String productionOrderNo);
|
Boolean deleteByProCode(@Param("routeDescription") String productionOrderNo);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.ruoyi.system.mapper;
|
|||||||
import com.ruoyi.system.domain.ProductionOrder;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生产订单Mapper接口
|
* 生产订单Mapper接口
|
||||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
|||||||
* @date 2024-10-20
|
* @date 2024-10-20
|
||||||
*/
|
*/
|
||||||
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}")
|
||||||
|
void deleteWithProCode(String productionOrderNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,4 +55,9 @@ public interface IFigureSaveService {
|
|||||||
List<FigureSave> selectByFid(Long id);
|
List<FigureSave> selectByFid(Long id);
|
||||||
|
|
||||||
List<FigureSave> selectByProCode(String totalWeight);
|
List<FigureSave> selectByProCode(String totalWeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入数据
|
||||||
|
*/
|
||||||
|
String importData(List<FigureSaveVo> dataList, boolean updateSupport, String operName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,11 @@ public interface IProcessOrderProService {
|
|||||||
*/
|
*/
|
||||||
Boolean updateByBo(ProcessOrderProBo bo);
|
Boolean updateByBo(ProcessOrderProBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空项目关联数据(保留项目本身)
|
||||||
|
*/
|
||||||
|
R<Void> clearProjectData(Collection<Long> ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验并批量删除项目令号信息
|
* 校验并批量删除项目令号信息
|
||||||
*/
|
*/
|
||||||
@ -89,4 +94,17 @@ public interface IProcessOrderProService {
|
|||||||
R<String> getRouteLog(Long id) throws JsonProcessingException;
|
R<String> getRouteLog(Long id) throws JsonProcessingException;
|
||||||
|
|
||||||
SseEmitter startDrawingSse(Long id);
|
SseEmitter startDrawingSse(Long id);
|
||||||
|
|
||||||
|
R<String> downloadDwg(List<Long> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送钉钉卡片
|
||||||
|
* @param id 项目ID
|
||||||
|
*/
|
||||||
|
void pushDingTalkCard(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新并计算项目延期状态
|
||||||
|
*/
|
||||||
|
void refreshDelayStatus();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,9 @@ 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.system.domain.ProcessOrderPro;
|
||||||
|
import com.ruoyi.system.mapper.ProcessOrderProMapper;
|
||||||
|
import com.ruoyi.system.service.IProcessOrderProService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import com.ruoyi.system.domain.bo.FigureSaveBo;
|
import com.ruoyi.system.domain.bo.FigureSaveBo;
|
||||||
@ -31,7 +34,7 @@ import java.util.Collection;
|
|||||||
public class FigureSaveServiceImpl implements IFigureSaveService {
|
public class FigureSaveServiceImpl implements IFigureSaveService {
|
||||||
|
|
||||||
private final FigureSaveMapper baseMapper;
|
private final FigureSaveMapper baseMapper;
|
||||||
|
private final ProcessOrderProMapper processOrderProMapper;
|
||||||
/**
|
/**
|
||||||
* 查询外购件临时
|
* 查询外购件临时
|
||||||
*/
|
*/
|
||||||
@ -69,6 +72,11 @@ public class FigureSaveServiceImpl implements IFigureSaveService {
|
|||||||
lqw.eq(bo.getJdInventory() != null, FigureSave::getJdInventory, bo.getJdInventory());
|
lqw.eq(bo.getJdInventory() != null, FigureSave::getJdInventory, bo.getJdInventory());
|
||||||
lqw.eq(StringUtils.isNotBlank(bo.getDrawPath()), FigureSave::getDrawPath, bo.getDrawPath());
|
lqw.eq(StringUtils.isNotBlank(bo.getDrawPath()), FigureSave::getDrawPath, bo.getDrawPath());
|
||||||
lqw.eq(StringUtils.isNotBlank(bo.getProductionCode()), FigureSave::getProductionCode, bo.getProductionCode());
|
lqw.eq(StringUtils.isNotBlank(bo.getProductionCode()), FigureSave::getProductionCode, bo.getProductionCode());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getFileType()), FigureSave::getFileType, bo.getFileType());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getDrawBy()), FigureSave::getDrawBy, bo.getDrawBy());
|
||||||
|
lqw.eq(bo.getWhetherDelay() != null, FigureSave::getWhetherDelay, bo.getWhetherDelay());
|
||||||
|
lqw.eq(bo.getDelayDay() != null, FigureSave::getDelayDay, bo.getDelayDay());
|
||||||
|
lqw.eq(bo.getDrawTime() != null, FigureSave::getDrawTime, bo.getDrawTime());
|
||||||
return lqw;
|
return lqw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,4 +145,39 @@ public class FigureSaveServiceImpl implements IFigureSaveService {
|
|||||||
|
|
||||||
return baseMapper.selectList(wa);
|
return baseMapper.selectList(wa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String importData(List<FigureSaveVo> dataList, boolean updateSupport, String operName) {
|
||||||
|
if (dataList.isEmpty()) {
|
||||||
|
throw new RuntimeException("导入数据不能为空!");
|
||||||
|
}
|
||||||
|
|
||||||
|
int successNum = 0;
|
||||||
|
int failureNum = 0;
|
||||||
|
StringBuilder successMsg = new StringBuilder();
|
||||||
|
StringBuilder failureMsg = new StringBuilder();
|
||||||
|
for (FigureSaveVo vo : dataList) {
|
||||||
|
try {
|
||||||
|
// 验证是否存在这个用户
|
||||||
|
FigureSave figureSave = BeanUtil.toBean(vo, FigureSave.class);
|
||||||
|
ProcessOrderPro processOrderPro = processOrderProMapper.selectByProjectNumber(figureSave.getProductionCode());
|
||||||
|
figureSave.setPid(processOrderPro.getId());
|
||||||
|
baseMapper.insert(figureSave);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>").append(successNum).append("、数据 ").append(vo.getFigureName()).append(" 导入成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、数据 " + vo.getFigureName() + " 导入失败:";
|
||||||
|
failureMsg.append(msg).append(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failureNum > 0) {
|
||||||
|
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||||
|
// throw new ServiceException(failureMsg.toString());
|
||||||
|
return failureMsg.toString();
|
||||||
|
} else {
|
||||||
|
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
||||||
|
}
|
||||||
|
return successMsg.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,10 @@ 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.domain.entity.SysUser;
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.dingding.DingUtil;
|
import com.ruoyi.common.dingding.robot.RobotGroupService;
|
||||||
|
import com.ruoyi.common.dingding.token.AccessTokenManager;
|
||||||
|
import com.ruoyi.common.dingding.util.DingUtil;
|
||||||
|
import com.ruoyi.common.dingding.webhook.WebhookClient;
|
||||||
import com.ruoyi.common.helper.LoginHelper;
|
import com.ruoyi.common.helper.LoginHelper;
|
||||||
import com.ruoyi.common.utils.FtpUtil;
|
import com.ruoyi.common.utils.FtpUtil;
|
||||||
import com.ruoyi.common.utils.HttpUtils;
|
import com.ruoyi.common.utils.HttpUtils;
|
||||||
@ -91,11 +94,23 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
private final DeviceSpec60rMapper deviceSpec60rMapper;
|
private final DeviceSpec60rMapper deviceSpec60rMapper;
|
||||||
private final DeviceSpec80rMapper deviceSpec80rMapper;
|
private final DeviceSpec80rMapper deviceSpec80rMapper;
|
||||||
private final DeviceSpec100rMapper deviceSpec100rMapper;
|
private final DeviceSpec100rMapper deviceSpec100rMapper;
|
||||||
|
private final IFigureSaveService iFigureSaveService;
|
||||||
private final SafetyStockMapper safetyStockMapper;
|
private final SafetyStockMapper safetyStockMapper;
|
||||||
private final IProcessRouteService iProcessRouteService;
|
private final IProcessRouteService iProcessRouteService;
|
||||||
private final IProductionOrderService iProductionOrderService;
|
private final IProductionOrderService iProductionOrderService;
|
||||||
private final ISysUserService userService;
|
private final ISysUserService userService;
|
||||||
|
private final ProductionOrderMapper productionOrderMapper;
|
||||||
|
@Autowired
|
||||||
|
private AccessTokenManager accessTokenManager;
|
||||||
|
@Autowired
|
||||||
|
private RobotGroupService robotGroupService;
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.common.dingding.media.MediaService mediaService;
|
||||||
|
@Autowired
|
||||||
|
private com.ruoyi.common.dingding.card.CardService cardService;
|
||||||
|
@Autowired
|
||||||
|
private WebhookClient webhookClient;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ProcessRouteMapper processRouteMapper;
|
private ProcessRouteMapper processRouteMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -111,8 +126,12 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
@Value("${app.drawing.status-url:http://192.168.5.18:9000/getstate}")
|
@Value("${app.drawing.status-url:http://192.168.5.18:9000/getstate}")
|
||||||
private String statusUrl;
|
private String statusUrl;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IFigureSaveService iFigureSaveService;
|
private IFigureSaveService iFigureSaveSservice;
|
||||||
private Map<String, SpecStrategy> specStrategies;
|
private Map<String, SpecStrategy> specStrategies;
|
||||||
|
@Autowired
|
||||||
|
private BomDetailsMapper bomDetailsMapper;
|
||||||
|
@Autowired
|
||||||
|
private MaterialBomMapper materialBomMapper;
|
||||||
|
|
||||||
public static String[] getNullPropertyNames(Object source) {
|
public static String[] getNullPropertyNames(Object source) {
|
||||||
BeanWrapper src = new BeanWrapperImpl(source);
|
BeanWrapper src = new BeanWrapperImpl(source);
|
||||||
@ -234,11 +253,71 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
return baseMapper.selectVoById(id);
|
return baseMapper.selectVoById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新并计算项目延期状态
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void refreshDelayStatus() {
|
||||||
|
// 1. 查询所有未完成的项目(状态为 0)
|
||||||
|
LambdaQueryWrapper<ProcessOrderPro> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.in(ProcessOrderPro::getBomStatus, 0L);
|
||||||
|
// 确保计划结束时间不为空
|
||||||
|
wrapper.isNotNull(ProcessOrderPro::getDrawingTime);
|
||||||
|
|
||||||
|
List<ProcessOrderPro> list = baseMapper.selectList(wrapper);
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
List<ProcessOrderPro> updates = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ProcessOrderPro vo : list) {
|
||||||
|
Integer oldWhetherDelay = vo.getWhetherDelay();
|
||||||
|
Long oldDelayDay = vo.getDelayDay();
|
||||||
|
|
||||||
|
// 检查子产品是否有图纸
|
||||||
|
boolean hasProductDrawing = false;
|
||||||
|
List<FigureSave> figureSaves = iFigureSaveService.selectByFid(vo.getId());
|
||||||
|
if (figureSaves != null && !figureSaves.isEmpty()) {
|
||||||
|
hasProductDrawing = figureSaves.stream().anyMatch(f -> StringUtils.isNotBlank(f.getDrawPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果项目或产品已经上传了图纸,则认为没有延期
|
||||||
|
if (hasProductDrawing) {
|
||||||
|
vo.setWhetherDelay(0);
|
||||||
|
vo.setDelayDay(0L);
|
||||||
|
} else {
|
||||||
|
// 如果都没有上传图纸,则计算是否超过出图时间
|
||||||
|
long diff = now.getTime() - vo.getDrawingTime().getTime();
|
||||||
|
if (diff > 0) {
|
||||||
|
vo.setWhetherDelay(1);
|
||||||
|
vo.setDelayDay(diff / (1000 * 60 * 60 * 24));
|
||||||
|
} else {
|
||||||
|
vo.setWhetherDelay(0);
|
||||||
|
vo.setDelayDay(0L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查是否发生变化
|
||||||
|
if (!Objects.equals(oldWhetherDelay, vo.getWhetherDelay()) || !Objects.equals(oldDelayDay, vo.getDelayDay())) {
|
||||||
|
updates.add(vo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 批量更新
|
||||||
|
if (!updates.isEmpty()) {
|
||||||
|
baseMapper.updateBatchById(updates);
|
||||||
|
log.info("已刷新 {} 条项目的延期状态", updates.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目令号列表
|
* 查询项目令号列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public TableDataInfo<ProcessOrderProVo> queryPageList(ProcessOrderProBo bo, PageQuery pageQuery) {
|
public TableDataInfo<ProcessOrderProVo> queryPageList(ProcessOrderProBo bo, PageQuery pageQuery) {
|
||||||
|
// 在查询前先刷新数据库中的延期状态
|
||||||
|
// refreshDelayStatus();
|
||||||
|
|
||||||
LambdaQueryWrapper<ProcessOrderPro> lqw = buildQueryWrapper(bo);
|
LambdaQueryWrapper<ProcessOrderPro> lqw = buildQueryWrapper(bo);
|
||||||
Page<ProcessOrderProVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
Page<ProcessOrderProVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||||
return TableDataInfo.build(result);
|
return TableDataInfo.build(result);
|
||||||
@ -258,8 +337,6 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
LambdaQueryWrapper<ProcessOrderPro> lqw = Wrappers.lambdaQuery();
|
LambdaQueryWrapper<ProcessOrderPro> lqw = Wrappers.lambdaQuery();
|
||||||
lqw.like(StringUtils.isNotBlank(bo.getProductionOrderNo()), ProcessOrderPro::getProductionOrderNo, bo.getProductionOrderNo());
|
lqw.like(StringUtils.isNotBlank(bo.getProductionOrderNo()), ProcessOrderPro::getProductionOrderNo, bo.getProductionOrderNo());
|
||||||
lqw.like(StringUtils.isNotBlank(bo.getProductionName()), ProcessOrderPro::getProductionName, bo.getProductionName());
|
lqw.like(StringUtils.isNotBlank(bo.getProductionName()), ProcessOrderPro::getProductionName, bo.getProductionName());
|
||||||
lqw.eq(StringUtils.isNotBlank(bo.getDrawingNo()), ProcessOrderPro::getDrawingNo, bo.getDrawingNo());
|
|
||||||
lqw.like(StringUtils.isNotBlank(bo.getDrawingName()), ProcessOrderPro::getDrawingName, bo.getDrawingName());
|
|
||||||
lqw.eq(bo.getPlanEndTime() != null, ProcessOrderPro::getPlanEndTime, bo.getPlanEndTime());
|
lqw.eq(bo.getPlanEndTime() != null, ProcessOrderPro::getPlanEndTime, bo.getPlanEndTime());
|
||||||
lqw.eq(bo.getPlanStartTime() != null, ProcessOrderPro::getPlanStartTime, bo.getPlanStartTime());
|
lqw.eq(bo.getPlanStartTime() != null, ProcessOrderPro::getPlanStartTime, bo.getPlanStartTime());
|
||||||
lqw.eq(StringUtils.isNotBlank(bo.getDrawingType()), ProcessOrderPro::getDrawingType, bo.getDrawingType());
|
lqw.eq(StringUtils.isNotBlank(bo.getDrawingType()), ProcessOrderPro::getDrawingType, bo.getDrawingType());
|
||||||
@ -268,12 +345,34 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
lqw.eq(bo.getQuantity() != null, ProcessOrderPro::getQuantity, bo.getQuantity());
|
lqw.eq(bo.getQuantity() != null, ProcessOrderPro::getQuantity, bo.getQuantity());
|
||||||
lqw.eq(bo.getBomStatus() != null, ProcessOrderPro::getBomStatus, bo.getBomStatus());
|
lqw.eq(bo.getBomStatus() != null, ProcessOrderPro::getBomStatus, bo.getBomStatus());
|
||||||
lqw.eq(bo.getIsEnterpriseStandard() != null, ProcessOrderPro::getIsEnterpriseStandard, bo.getIsEnterpriseStandard());
|
lqw.eq(bo.getIsEnterpriseStandard() != null, ProcessOrderPro::getIsEnterpriseStandard, bo.getIsEnterpriseStandard());
|
||||||
|
|
||||||
|
// isEnterpriseStandardList 多选处理
|
||||||
|
List<String> list = bo.getIsEnterpriseStandardList();
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
lqw.in(ProcessOrderPro::getIsEnterpriseStandard, list);
|
||||||
|
}
|
||||||
|
|
||||||
lqw.eq(bo.getDrawingPath() != null, ProcessOrderPro::getDrawingPath, bo.getDrawingPath());
|
lqw.eq(bo.getDrawingPath() != null, ProcessOrderPro::getDrawingPath, bo.getDrawingPath());
|
||||||
|
lqw.eq(bo.getWhetherDelay() != null, ProcessOrderPro::getWhetherDelay, bo.getWhetherDelay());
|
||||||
//按照创建时间排序,最新的在顶端
|
//按照创建时间排序,最新的在顶端
|
||||||
lqw.orderByDesc(true, ProcessOrderPro::getCreateTime);
|
lqw.orderByDesc(true, ProcessOrderPro::getCreateTime);
|
||||||
return lqw;
|
return lqw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 项目类型常量定义
|
||||||
|
private static final Map<String, String> PROJECT_TYPE_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
PROJECT_TYPE_MAP.put("CP", "产品");
|
||||||
|
PROJECT_TYPE_MAP.put("FB", "非标");
|
||||||
|
PROJECT_TYPE_MAP.put("BG", "变更");
|
||||||
|
PROJECT_TYPE_MAP.put("QB", "企标");
|
||||||
|
PROJECT_TYPE_MAP.put("EY", "研发");
|
||||||
|
PROJECT_TYPE_MAP.put("GZ", "固资");
|
||||||
|
PROJECT_TYPE_MAP.put("ZP", "展品");
|
||||||
|
PROJECT_TYPE_MAP.put("KC", "库存");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增项目令号
|
* 新增项目令号
|
||||||
*/
|
*/
|
||||||
@ -292,10 +391,22 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
add.setCreateBy(user.getNickName());
|
add.setCreateBy(user.getNickName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果出图时间为空,默认为当前时间
|
|
||||||
if (add.getDrawingTime() == null) {
|
// 对项目令号的判断,根据前缀设置DrawingType
|
||||||
add.setDrawingTime(new Date());
|
String productionOrderNo = bo.getProductionOrderNo();
|
||||||
bo.setDrawingTime(add.getDrawingTime());
|
if (StringUtils.isNotBlank(productionOrderNo)) {
|
||||||
|
String isEnterpriseStandard = null;
|
||||||
|
if (productionOrderNo.toUpperCase().contains("BG")) {
|
||||||
|
isEnterpriseStandard = PROJECT_TYPE_MAP.get("BG");
|
||||||
|
} else {
|
||||||
|
// 提取前缀
|
||||||
|
String prefix = productionOrderNo.length() >= 2 ? productionOrderNo.substring(0, 2).toUpperCase() : "";
|
||||||
|
isEnterpriseStandard = PROJECT_TYPE_MAP.get(prefix);
|
||||||
|
}
|
||||||
|
if (isEnterpriseStandard != null) {
|
||||||
|
add.setIsEnterpriseStandard(isEnterpriseStandard);
|
||||||
|
bo.setIsEnterpriseStandard(isEnterpriseStandard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
add.setRouteStatus(0L);
|
add.setRouteStatus(0L);
|
||||||
add.setBomStatus(0L);
|
add.setBomStatus(0L);
|
||||||
@ -303,92 +414,116 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
boolean flag = baseMapper.insert(add) > 0;
|
boolean flag = baseMapper.insert(add) > 0;
|
||||||
if (flag) {
|
if (flag) {
|
||||||
bo.setId(add.getId());
|
bo.setId(add.getId());
|
||||||
// 异步发送钉钉卡片
|
|
||||||
CompletableFuture.runAsync(() -> {
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
||||||
try {
|
|
||||||
// 获取 AccessToken
|
|
||||||
String accessToken = DingUtil.getAccessToken(dingTalkProperties.getAppKey(), dingTalkProperties.getAppSecret());
|
|
||||||
// 处理图片上传
|
|
||||||
String imgMediaId = ""; // 默认图片
|
|
||||||
if (StringUtils.isNotEmpty(bo.getDrawingPath())) {
|
|
||||||
try {
|
|
||||||
Long ossId = Long.parseLong(bo.getDrawingPath());
|
|
||||||
SysOssVo ossVo = iSysOssService.getById(ossId);
|
|
||||||
if (ossVo != null) {
|
|
||||||
String suffix = FileUtil.getSuffix(ossVo.getOriginalName());
|
|
||||||
File tempFile = FileUtil.createTempFile("ding_img", StringUtils.isNotEmpty(suffix) ? "." + suffix : ".jpg", true);
|
|
||||||
try (InputStream is = OssFactory.instance().getObjectContent(ossVo.getUrl()); OutputStream os = Files.newOutputStream(tempFile.toPath())) {
|
|
||||||
IoUtil.copy(is, os);
|
|
||||||
}
|
|
||||||
imgMediaId = DingUtil.uploadMedia(accessToken, "image", tempFile.getAbsolutePath());
|
|
||||||
FileUtil.del(tempFile);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("上传图片到钉钉失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 构造卡片数据
|
|
||||||
Map<String, String> cardDataMap = new HashMap<>();
|
|
||||||
|
|
||||||
cardDataMap.put("drawing_time", bo.getDrawingTime() != null ? sdf.format(bo.getDrawingTime()) : "待定");
|
|
||||||
cardDataMap.put("production_order_no", StringUtils.defaultIfEmpty(bo.getProductionOrderNo(), ""));
|
|
||||||
cardDataMap.put("production_name", StringUtils.defaultIfEmpty(bo.getProductionName(), ""));
|
|
||||||
cardDataMap.put("drawing_by", StringUtils.defaultIfEmpty(bo.getDrawingBy(), "暂无"));
|
|
||||||
cardDataMap.put("bom_uptime", bo.getBomUptime() != null ? sdf.format(bo.getBomUptime()) : "未上传");
|
|
||||||
cardDataMap.put("drawing_type", StringUtils.defaultIfEmpty(bo.getDrawingType(), ""));
|
|
||||||
cardDataMap.put("project_end_time", bo.getProjectEndTime() != null ? sdf.format(bo.getProjectEndTime()) : "待定");
|
|
||||||
cardDataMap.put("route_uptime", bo.getRouteUptime() != null ? sdf.format(bo.getRouteUptime()) : "未上传");
|
|
||||||
cardDataMap.put("material_count", bo.getMaterialCount() != null ? String.valueOf(bo.getMaterialCount()) : "0");
|
|
||||||
cardDataMap.put("route_count", bo.getRouteCount() != null ? String.valueOf(bo.getRouteCount()) : "0");
|
|
||||||
cardDataMap.put("plan_uptime", bo.getPlanUptime() != null ? sdf.format(bo.getPlanUptime()) : "未上传");
|
|
||||||
cardDataMap.put("bom_count", bo.getBomCount() != null ? String.valueOf(bo.getBomCount()) : "0");
|
|
||||||
cardDataMap.put("img_id", imgMediaId);
|
|
||||||
String outTrackId = "project-card-" + System.currentTimeMillis();
|
|
||||||
String outTrackId1 = DingUtil.createAndDeliverCard(accessToken, dingTalkProperties.getProjectCardTemplateId(), dingTalkProperties.getRobotCode(), dingTalkProperties.getOpenConversationId(), outTrackId, cardDataMap, dingTalkProperties.getCallbackRouteKey());
|
|
||||||
//同时发送给设计人员
|
|
||||||
List<String> designers = StringUtils.splitList(bo.getDrawingBy());
|
|
||||||
// 收集手机号用于@
|
|
||||||
List<String> receiverMobiles = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String designer : designers) {
|
|
||||||
SysUser sysUser = userService.selectUserByUserName(designer);
|
|
||||||
if (sysUser != null && StringUtils.isNotEmpty(sysUser.getPhonenumber())) {
|
|
||||||
receiverMobiles.add(sysUser.getPhonenumber());
|
|
||||||
log.info("添加发送消息用户 {}: {}", sysUser.getNickName(), sysUser.getPhonenumber());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!receiverMobiles.isEmpty()) {
|
|
||||||
// 去重
|
|
||||||
receiverMobiles = receiverMobiles.stream().distinct().collect(Collectors.toList());
|
|
||||||
try {
|
|
||||||
log.info("发送群消息并@用户(手机号): {}", receiverMobiles);
|
|
||||||
DingUtil.sendSessionText("https://oapi.dingtalk.com/robot/send?access_token=61525f1eccca9dde871df334918b1aac47f08e4ef71b8c689b46a091b3d45450",
|
|
||||||
bo.getProductionOrderNo() + " 项目已创建", null, receiverMobiles);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("发送@消息失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 更新数据库中的 auditCardId
|
|
||||||
ProcessOrderPro update = new ProcessOrderPro();
|
|
||||||
update.setId(add.getId());
|
|
||||||
update.setUpdateBy("Dingding");
|
|
||||||
update.setNotificationCardId(outTrackId1);
|
|
||||||
log.info("更新项目令号 {} 卡片id {}", add.getProductionOrderNo(), outTrackId1);
|
|
||||||
baseMapper.updateById(update);
|
|
||||||
|
|
||||||
bo.setAuditCardId(outTrackId1);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("发送钉钉卡片异常", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送钉钉卡片
|
||||||
|
*
|
||||||
|
* @param id 项目ID
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void pushDingTalkCard(Long id) {
|
||||||
|
ProcessOrderPro processOrderPro = baseMapper.selectById(id);
|
||||||
|
List<FigureSave> figureSaves = iFigureSaveService.selectByFid(id);
|
||||||
|
if (processOrderPro == null) {
|
||||||
|
throw new RuntimeException("项目不存在");
|
||||||
|
}
|
||||||
|
ProcessOrderProBo bo = new ProcessOrderProBo();
|
||||||
|
BeanUtils.copyProperties(processOrderPro, bo);
|
||||||
|
// 收集所有设计人员
|
||||||
|
String drawingBy = figureSaves.stream().map(FigureSave::getDrawBy).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.joining(","));
|
||||||
|
// 异步发送钉钉卡片
|
||||||
|
// CompletableFuture.runAsync(() -> {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||||
|
try {
|
||||||
|
// 获取 AccessToken
|
||||||
|
String accessToken = accessTokenManager.getAccessToken();
|
||||||
|
// 处理图片上传
|
||||||
|
String imgMediaId = ""; // 默认图片
|
||||||
|
if (StringUtils.isNotEmpty(bo.getDrawingPath())) {
|
||||||
|
try {
|
||||||
|
String[] paths = bo.getDrawingPath().split(",");
|
||||||
|
Long ossId = Long.parseLong(paths[0]);
|
||||||
|
SysOssVo ossVo = iSysOssService.getById(ossId);
|
||||||
|
if (ossVo != null) {
|
||||||
|
String suffix = FileUtil.getSuffix(ossVo.getOriginalName());
|
||||||
|
File tempFile = FileUtil.createTempFile("ding_img", StringUtils.isNotEmpty(suffix) ? "." + suffix : ".jpg", true);
|
||||||
|
try (InputStream is = OssFactory.instance().getObjectContent(ossVo.getUrl()); OutputStream os = Files.newOutputStream(tempFile.toPath())) {
|
||||||
|
IoUtil.copy(is, os);
|
||||||
|
}
|
||||||
|
imgMediaId = mediaService.uploadMedia("image", tempFile.getAbsolutePath());
|
||||||
|
FileUtil.del(tempFile);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("上传图片到钉钉失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造卡片数据
|
||||||
|
Map<String, String> cardDataMap = new HashMap<>();
|
||||||
|
cardDataMap.put("drawing_time", bo.getDrawingTime() != null ? sdf.format(bo.getDrawingTime()) : "待定");
|
||||||
|
cardDataMap.put("production_order_no", StringUtils.defaultIfEmpty(bo.getProductionOrderNo(), ""));
|
||||||
|
cardDataMap.put("production_name", StringUtils.defaultIfEmpty(bo.getProductionName(), ""));
|
||||||
|
cardDataMap.put("drawing_by", StringUtils.defaultIfEmpty(drawingBy, "暂无"));
|
||||||
|
cardDataMap.put("bom_uptime", bo.getBomUptime() != null ? sdf.format(bo.getBomUptime()) : "未上传");
|
||||||
|
cardDataMap.put("drawing_type", StringUtils.defaultIfEmpty(bo.getDrawingType(), ""));
|
||||||
|
cardDataMap.put("project_end_time", bo.getProjectEndTime() != null ? sdf.format(bo.getProjectEndTime()) : "待定");
|
||||||
|
cardDataMap.put("route_uptime", bo.getRouteUptime() != null ? sdf.format(bo.getRouteUptime()) : "未上传");
|
||||||
|
cardDataMap.put("material_count", bo.getMaterialCount() != null ? String.valueOf(bo.getMaterialCount()) : "0");
|
||||||
|
cardDataMap.put("route_count", bo.getRouteCount() != null ? String.valueOf(bo.getRouteCount()) : "0");
|
||||||
|
cardDataMap.put("plan_uptime", bo.getPlanUptime() != null ? sdf.format(bo.getPlanUptime()) : "未上传");
|
||||||
|
cardDataMap.put("bom_count", bo.getBomCount() != null ? String.valueOf(bo.getBomCount()) : "0");
|
||||||
|
cardDataMap.put("img_id", imgMediaId);
|
||||||
|
String outTrackId = "project-card-" + System.currentTimeMillis();
|
||||||
|
String outTrackId1 = cardService.createAndDeliverCard(dingTalkProperties.getProjectCardTemplateId(), dingTalkProperties.getOpenConversationId(), outTrackId, cardDataMap, dingTalkProperties.getCallbackRouteKey());
|
||||||
|
//同时发送给设计人员
|
||||||
|
List<String> designers = StringUtils.splitList(bo.getDrawingBy());
|
||||||
|
// 收集手机号用于@
|
||||||
|
List<String> receiverMobiles = new ArrayList<>();
|
||||||
|
//拿到用户id,这里将用户id存入email字段
|
||||||
|
for (String designer : designers) {
|
||||||
|
SysUser sysUser = userService.selectUserByUserName(designer);
|
||||||
|
if (sysUser != null && StringUtils.isNotEmpty(sysUser.getEmail())) {
|
||||||
|
receiverMobiles.add(sysUser.getEmail());
|
||||||
|
log.info("添加发送消息用户 {}: {}", sysUser.getNickName(), sysUser.getEmail());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//获取最晚的出图时间
|
||||||
|
FigureSave latest = figureSaves.stream().filter(e -> e.getDrawTime() != null).max(Comparator.comparing(FigureSave::getDrawTime)).orElse(null);
|
||||||
|
|
||||||
|
|
||||||
|
if (!receiverMobiles.isEmpty()) {
|
||||||
|
// 去重
|
||||||
|
receiverMobiles = receiverMobiles.stream().distinct().collect(Collectors.toList());
|
||||||
|
try {
|
||||||
|
log.info("发送群消息并@用户(手机号): {}", receiverMobiles);
|
||||||
|
// 使用 WebhookClient 发送消息
|
||||||
|
webhookClient.sendText("https://oapi.dingtalk.com/robot/send?access_token=61525f1eccca9dde871df334918b1aac47f08e4ef71b8c689b46a091b3d45450", bo.getProductionOrderNo() + " 项目已创建", null, receiverMobiles);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送@消息失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 更新数据库中的 auditCardId
|
||||||
|
ProcessOrderPro update = new ProcessOrderPro();
|
||||||
|
update.setId(processOrderPro.getId());
|
||||||
|
update.setDrawingBy(drawingBy);
|
||||||
|
if (latest != null) {
|
||||||
|
update.setDrawingTime(latest.getDrawTime());
|
||||||
|
}
|
||||||
|
update.setUpdateBy("Dingding");
|
||||||
|
update.setNotificationCardId(outTrackId1);
|
||||||
|
log.info("更新项目令号 {} 卡片id {}", processOrderPro.getProductionOrderNo(), outTrackId1);
|
||||||
|
baseMapper.updateById(update);
|
||||||
|
|
||||||
|
bo.setAuditCardId(outTrackId1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送钉钉卡片异常", e);
|
||||||
|
}
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改项目令号
|
* 修改项目令号
|
||||||
*/
|
*/
|
||||||
@ -413,6 +548,24 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<Void> clearProjectData(Collection<Long> ids) {
|
||||||
|
for (Long id : ids) {
|
||||||
|
ProcessOrderProVo processOrderProVo = baseMapper.selectVoById(id);
|
||||||
|
if (processOrderProVo == null) {
|
||||||
|
log.warn("未找到对应的 ProcessOrderProVo,ID: {}", id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String productionOrderNo = processOrderProVo.getProductionOrderNo();
|
||||||
|
// 清空关联表数据
|
||||||
|
processRouteMapper.deleteByProCode(productionOrderNo);
|
||||||
|
productionOrderMapper.deleteWithProCode(productionOrderNo);
|
||||||
|
materialBomMapper.deleteWithProCode(productionOrderNo);
|
||||||
|
bomDetailsMapper.deleteWithProCode(productionOrderNo);
|
||||||
|
}
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除项目令号
|
* 批量删除项目令号
|
||||||
*/
|
*/
|
||||||
@ -425,7 +578,6 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String productionOrderNo = processOrderProVo.getProductionOrderNo();
|
String productionOrderNo = processOrderProVo.getProductionOrderNo();
|
||||||
|
|
||||||
// 调用 selectByProjectCode 方法并检查结果
|
// 调用 selectByProjectCode 方法并检查结果
|
||||||
return iProcessRouteService.delByProjectCode(productionOrderNo);
|
return iProcessRouteService.delByProjectCode(productionOrderNo);
|
||||||
}
|
}
|
||||||
@ -443,7 +595,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
log.error("删除 ID 列表失败: {}", ids);
|
log.error("删除 ID 列表失败: {}", ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
return R.fail();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -564,6 +716,11 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
figureSave.setFigureName(orderPro.getFigureName());
|
figureSave.setFigureName(orderPro.getFigureName());
|
||||||
figureSave.setAxialType(axial);
|
figureSave.setAxialType(axial);
|
||||||
figureSave.setBoxType(box);
|
figureSave.setBoxType(box);
|
||||||
|
figureSave.setFileType(orderPro.getFileType());
|
||||||
|
figureSave.setDrawBy(orderPro.getDrawBy());
|
||||||
|
figureSave.setDelayDay(orderPro.getDelayDay());
|
||||||
|
figureSave.setDrawTime(orderPro.getDrawTime());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
figureSave.setJdInventory(Long.valueOf(journey));
|
figureSave.setJdInventory(Long.valueOf(journey));
|
||||||
} catch (Exception ignore) {
|
} catch (Exception ignore) {
|
||||||
@ -620,12 +777,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
ProcessOrderPro po = processOrderPro;
|
ProcessOrderPro po = processOrderPro;
|
||||||
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
if (po != null) {
|
if (po != null) {
|
||||||
if (StringUtils.isNotBlank(po.getDrawingNo())) {
|
|
||||||
markdownText.append("> **图号**: ").append(po.getDrawingNo()).append("\n\n");
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(po.getDrawingName())) {
|
|
||||||
markdownText.append("> **图纸名称**: ").append(po.getDrawingName()).append("\n\n");
|
|
||||||
}
|
|
||||||
String planStart = po.getPlanStartTime() == null ? "" : sdfDate.format(po.getPlanStartTime());
|
String planStart = po.getPlanStartTime() == null ? "" : sdfDate.format(po.getPlanStartTime());
|
||||||
String planEnd = po.getPlanEndTime() == null ? "" : sdfDate.format(po.getPlanEndTime());
|
String planEnd = po.getPlanEndTime() == null ? "" : sdfDate.format(po.getPlanEndTime());
|
||||||
if (StringUtils.isNotBlank(planStart) || StringUtils.isNotBlank(planEnd)) {
|
if (StringUtils.isNotBlank(planStart) || StringUtils.isNotBlank(planEnd)) {
|
||||||
@ -637,7 +789,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (po.getIsEnterpriseStandard() != null) {
|
if (po.getIsEnterpriseStandard() != null) {
|
||||||
markdownText.append("> **是否企标**: ").append(po.getIsEnterpriseStandard() == 1 ? "是" : "否").append("\n\n");
|
markdownText.append("> **是否企标**: ").append(po.getIsEnterpriseStandard() == "1" ? "是" : "否").append("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
String bomStatus = null;
|
String bomStatus = null;
|
||||||
@ -667,8 +819,12 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
|
|
||||||
markdownText.append("\n请相关人员及时处理。");
|
markdownText.append("\n请相关人员及时处理。");
|
||||||
|
|
||||||
// 发送Markdown消息
|
// 发送Markdown消息给个人
|
||||||
DingUtil.sendOrgGroupMarkdown(accessToken, dingTalkProperties.getRobotCode(), dingTalkProperties.getOpenConversationId(), "生产项目创建通知", markdownText.toString());
|
if (StringUtils.isNotBlank(dingTalkProperties.getNotifyUserId())) {
|
||||||
|
robotGroupService.sendMarkdownToUser(dingTalkProperties.getNotifyUserId(), "生产项目创建通知", markdownText.toString());
|
||||||
|
} else {
|
||||||
|
log.warn("未配置钉钉通知个人ID(notifyUserId),跳过发送Markdown消息");
|
||||||
|
}
|
||||||
|
|
||||||
// 下载并发送Excel文件
|
// 下载并发送Excel文件
|
||||||
log.info("开始下载并发送Excel文件: {}", orderPro.getProductionOrderNo());
|
log.info("开始下载并发送Excel文件: {}", orderPro.getProductionOrderNo());
|
||||||
@ -712,10 +868,14 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
File excelFile = new File(filePath);
|
File excelFile = new File(filePath);
|
||||||
if (excelFile.exists() && excelFile.length() > 0) {
|
if (excelFile.exists() && excelFile.length() > 0) {
|
||||||
// Upload media
|
// Upload media
|
||||||
String mediaId = DingUtil.uploadMedia(accessToken, "file", filePath);
|
String mediaId = mediaService.uploadMedia("file", filePath);
|
||||||
// Send file
|
// Send file给个人
|
||||||
DingUtil.sendOrgGroupFile(accessToken, dingTalkProperties.getRobotCode(), dingTalkProperties.getOpenConversationId(), mediaId, excelFile.getName(), "sampleFile");
|
if (StringUtils.isNotBlank(dingTalkProperties.getNotifyUserId())) {
|
||||||
log.info("Excel文件发送成功");
|
robotGroupService.sendFileToUser(dingTalkProperties.getNotifyUserId(), mediaId, excelFile.getName());
|
||||||
|
log.info("Excel文件发送成功");
|
||||||
|
} else {
|
||||||
|
log.warn("未配置钉钉通知个人ID(notifyUserId),跳过发送Excel文件");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("Excel文件未找到或为空: {}", filePath);
|
log.warn("Excel文件未找到或为空: {}", filePath);
|
||||||
}
|
}
|
||||||
@ -1194,6 +1354,84 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
|||||||
return emitter;
|
return emitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载DWG文件
|
||||||
|
*
|
||||||
|
* @param ids FigureSaveVo ID列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public R<String> downloadDwg(List<Long> ids) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
return R.fail("ID列表为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只下载第一个文件
|
||||||
|
FigureSave figureSave = figureSaveMapper.selectById(ids.get(0));
|
||||||
|
if (figureSave == null) {
|
||||||
|
return R.fail("未找到对应的产品信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建临时目录
|
||||||
|
String tempBaseDir = "D:\\dwg_temp\\" + System.currentTimeMillis();
|
||||||
|
File tempDir = new File(tempBaseDir);
|
||||||
|
if (!tempDir.exists() && !tempDir.mkdirs()) {
|
||||||
|
return R.fail("创建临时目录失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String productionCode = figureSave.getProductionCode();
|
||||||
|
String figureName = figureSave.getFigureName();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(productionCode) || StringUtils.isEmpty(figureName)) {
|
||||||
|
return R.fail("产品信息不完整");
|
||||||
|
}
|
||||||
|
// 1. 解析年份
|
||||||
|
String year = "2025"; // 默认
|
||||||
|
if (productionCode.startsWith("EY")) {
|
||||||
|
if (productionCode.length() >= 4) {
|
||||||
|
try {
|
||||||
|
Integer.parseInt(productionCode.substring(2, 4));
|
||||||
|
year = "20" + productionCode.substring(2, 4);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. 构建FTP路径和本地路径
|
||||||
|
// FTP路径: /{year}/{productionCode}/{figureName}.dwg
|
||||||
|
String ftpPath = "/" + year + "/" + productionCode + "/" + figureName + ".dwg";
|
||||||
|
String fileName = figureName + ".dwg";
|
||||||
|
// 本地存放路径
|
||||||
|
String localFilePath = tempBaseDir + File.separator + fileName;
|
||||||
|
log.info("开始下载DWG: ftpPath={}, localFilePath={}", ftpPath, localFilePath);
|
||||||
|
// 3. 下载文件
|
||||||
|
boolean downloadSuccess = FtpUtil.downloadSingleFile("192.168.5.18", "admin", "hbyt2025", 21, ftpPath, localFilePath);
|
||||||
|
if (downloadSuccess) {
|
||||||
|
File downloadedFile = new File(localFilePath);
|
||||||
|
if (downloadedFile.exists() && downloadedFile.length() > 0) {
|
||||||
|
// 直接返回文件路径,无需打包
|
||||||
|
return R.ok("操作成功", localFilePath);
|
||||||
|
} else {
|
||||||
|
return R.fail("文件下载失败:文件为空");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return R.fail("文件下载失败");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载DWG过程中发生异常", e);
|
||||||
|
return R.fail("下载异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -848,10 +848,16 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
|||||||
}
|
}
|
||||||
//将带工艺的bom 存表 bomdetail
|
//将带工艺的bom 存表 bomdetail
|
||||||
if (routeVoList != null && !routeVoList.isEmpty()) {
|
if (routeVoList != null && !routeVoList.isEmpty()) {
|
||||||
for (ProcessRouteVo processRouteVo : routeVoList) {
|
for (int i = 0; i < routeVoList.size(); i++) {
|
||||||
|
ProcessRouteVo processRouteVo = routeVoList.get(i);
|
||||||
if (processRouteVo.getRawMaterialCode() != null && processRouteVo.getRawMaterialName() != null) {
|
if (processRouteVo.getRawMaterialCode() != null && processRouteVo.getRawMaterialName() != null) {
|
||||||
// 处理表中的材料bom
|
try {
|
||||||
bomDetailsVos.add(createBomDetails(processRouteVo));
|
// 处理表中的材料bom
|
||||||
|
bomDetailsVos.add(createBomDetails(processRouteVo));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ServiceException("第" + (i + 3) + "行, " + e.getMessage());
|
||||||
|
}
|
||||||
|
System.out.println("<UNK>BOM<UNK>" + processRouteVo.getRawMaterialCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -879,6 +885,10 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
|||||||
bomDetails.setSingleWeghit(processRoute.getBomDanZhong());
|
bomDetails.setSingleWeghit(processRoute.getBomDanZhong());
|
||||||
bomDetails.setMaterial(processRoute.getMaterial());
|
bomDetails.setMaterial(processRoute.getMaterial());
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(processRoute.getDiscUsage())) {
|
||||||
|
throw new ServiceException("材料BOM物料信息不完整: 物料编码[" + processRoute.getRawMaterialCode() + "], 物料名称[" + processRoute.getRawMaterialName() + "] 的用量为空");
|
||||||
|
}
|
||||||
|
|
||||||
// 处理单位换算
|
// 处理单位换算
|
||||||
if ("mm".equals(processRoute.getBomUnit())) {
|
if ("mm".equals(processRoute.getBomUnit())) {
|
||||||
bomDetails.setQuantity(Double.parseDouble(processRoute.getDiscUsage()) / 1000.0); // 转换为米
|
bomDetails.setQuantity(Double.parseDouble(processRoute.getDiscUsage()) / 1000.0); // 转换为米
|
||||||
|
|||||||
@ -207,7 +207,6 @@ public class WorkProcedureServiceImpl implements IWorkProcedureService {
|
|||||||
parentTask.setId(pro.getId());
|
parentTask.setId(pro.getId());
|
||||||
parentTask.setText(pro.getProductionOrderNo());
|
parentTask.setText(pro.getProductionOrderNo());
|
||||||
parentTask.setOpen(false);
|
parentTask.setOpen(false);
|
||||||
parentTask.setToolTipsTxt(pro.getDrawingNo());
|
|
||||||
/* parentTask.setProgress(1L);*/
|
/* parentTask.setProgress(1L);*/
|
||||||
parentTask.setStatus("yellow");
|
parentTask.setStatus("yellow");
|
||||||
parentTask.setParent(null);
|
parentTask.setParent(null);
|
||||||
|
|||||||
@ -20,6 +20,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="pid" column="pid"/>
|
<result property="pid" column="pid"/>
|
||||||
<result property="axialType" column="axial_type"/>
|
<result property="axialType" column="axial_type"/>
|
||||||
<result property="boxType" column="box_type"/>
|
<result property="boxType" column="box_type"/>
|
||||||
|
<result property="fileType" column="file_type"/>
|
||||||
|
<result property="drawBy" column="draw_by"/>
|
||||||
|
<result property="whetherDelay" column="whether_delay"/>
|
||||||
|
<result property="delayDay" column="delay_day"/>
|
||||||
|
<result property="drawTime" column="draw_time"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@ -8,8 +8,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="id" column="id"/>
|
<result property="id" column="id"/>
|
||||||
<result property="productionOrderNo" column="production_order_no"/>
|
<result property="productionOrderNo" column="production_order_no"/>
|
||||||
<result property="productionName" column="production_name"/>
|
<result property="productionName" column="production_name"/>
|
||||||
<result property="drawingNo" column="drawing_no"/>
|
<result property="whetherDelay" column="whether_delay"/>
|
||||||
<result property="drawingName" column="drawing_name"/>
|
<result property="delayDay" column="delay_day"/>
|
||||||
<result property="createBy" column="create_by"/>
|
<result property="createBy" column="create_by"/>
|
||||||
<result property="updateBy" column="update_by"/>
|
<result property="updateBy" column="update_by"/>
|
||||||
<result property="createTime" column="create_time"/>
|
<result property="createTime" column="create_time"/>
|
||||||
@ -38,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
<!-- 根据项目编号查询 -->
|
<!-- 根据项目编号查询 -->
|
||||||
<select id="selectByProjectNumber" resultType="com.ruoyi.system.domain.ProcessOrderPro">
|
<select id="selectByProjectNumber" resultType="com.ruoyi.system.domain.ProcessOrderPro">
|
||||||
SELECT id, production_order_no, production_name, drawing_no, drawing_name,drawing_type,
|
SELECT id, production_order_no, production_name, drawing_type,
|
||||||
plan_end_time, plan_start_time, create_by, create_time, update_by, update_time
|
plan_end_time, plan_start_time, create_by, create_time, update_by, update_time
|
||||||
FROM process_order_pro
|
FROM process_order_pro
|
||||||
WHERE production_order_no = #{routeDescription}
|
WHERE production_order_no = #{routeDescription}
|
||||||
@ -48,8 +48,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
UPDATE process_order_pro
|
UPDATE process_order_pro
|
||||||
SET production_order_no = #{productionOrderNo},
|
SET production_order_no = #{productionOrderNo},
|
||||||
production_name = #{productionName},
|
production_name = #{productionName},
|
||||||
drawing_no = #{drawingNo},
|
|
||||||
drawing_name = #{drawingName},
|
|
||||||
plan_end_time = #{planEndTime},
|
plan_end_time = #{planEndTime},
|
||||||
plan_start_time = #{planStartTime},
|
plan_start_time = #{planStartTime},
|
||||||
create_by = #{createBy},
|
create_by = #{createBy},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user