更新项目工作流
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.ruoyi.common.config.DingTalkProperties;
|
||||
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.domain.ProcessOrderPro;
|
||||
import com.ruoyi.system.mapper.ProcessOrderProMapper;
|
||||
@ -39,6 +39,13 @@ public class CallbackController {
|
||||
private DingTalkProperties dingTalkProperties;
|
||||
@Autowired
|
||||
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")
|
||||
public Map<String, String> callBack(@RequestBody(required = false) Map<String, Object> json) {
|
||||
@ -166,7 +173,7 @@ public class CallbackController {
|
||||
|
||||
// 调用 DingUtil 更新卡片
|
||||
try {
|
||||
DingUtil.updateInteractiveCard(accessToken, outTrackId, cardDataMap);
|
||||
cardService.updateInteractiveCard(accessToken, outTrackId, cardDataMap);
|
||||
bizLogger.info("成功更新卡片状态: outTrackId={}, action={}", outTrackId, action);
|
||||
} catch (Exception e) {
|
||||
bizLogger.error("更新卡片失败", e);
|
||||
@ -230,10 +237,8 @@ public class CallbackController {
|
||||
String replyContent = "你好 @" + senderId;
|
||||
|
||||
// 使用 Text 消息格式
|
||||
Map<String, Object> textParam = new HashMap<>();
|
||||
textParam.put("content", replyContent);
|
||||
try {
|
||||
DingUtil.robotGroupSend(accessToken, textParam, "sampleText", conversationId, targetAppKey, null);
|
||||
robotGroupService.sendText(conversationId, replyContent, Collections.singletonList(senderId));
|
||||
bizLogger.info("已回复你好(Text),并@用户: {}", senderId);
|
||||
} catch (Exception e) {
|
||||
bizLogger.error("回复你好失败", e);
|
||||
@ -344,7 +349,7 @@ public class CallbackController {
|
||||
|
||||
// 使用回调中的 sessionWebhook 直接回复 (这是最标准的回调回复方式)
|
||||
if (sessionWebhook != null && !sessionWebhook.isEmpty()) {
|
||||
DingUtil.sendSessionText(sessionWebhook, helpContent, Collections.singletonList(senderId));
|
||||
webhookClient.sendText(sessionWebhook, helpContent, Collections.singletonList(senderId), null);
|
||||
bizLogger.info("通过Webhook回复帮助信息给用户: {}", senderId);
|
||||
} else {
|
||||
|
||||
@ -356,36 +361,31 @@ public class CallbackController {
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private com.ruoyi.common.dingding.token.AccessTokenManager accessTokenManager; // 注入 AccessTokenManager
|
||||
|
||||
/**
|
||||
* 根据回调中的 robotCode 获取对应的 AccessToken
|
||||
*/
|
||||
private String getAccessToken(JSONObject eventJson) {
|
||||
String currentRobotCode = eventJson.getString("robotCode");
|
||||
String currentAppKey = dingTalkProperties.getAppKey();
|
||||
String currentAppSecret = dingTalkProperties.getAppSecret();
|
||||
|
||||
// 判定是否为安全库存机器人
|
||||
boolean isSafetyRobot = false;
|
||||
String safetyKey = dingTalkProperties.getSafetyStockAppKey();
|
||||
if (currentRobotCode != null) {
|
||||
if (currentRobotCode.equals(SAFETY_STOCK_ROBOT_CODE)) {
|
||||
isSafetyRobot = true;
|
||||
} else if (currentRobotCode.equals(safetyKey)) {
|
||||
isSafetyRobot = true;
|
||||
if (eventJson.containsKey("robotCode")) {
|
||||
String robotCode = eventJson.getString("robotCode");
|
||||
// 如果是安全库存机器人
|
||||
if (SAFETY_STOCK_ROBOT_CODE.equals(robotCode) ||
|
||||
(dingTalkProperties.getSafetyStockAppKey() != null && dingTalkProperties.getSafetyStockAppKey().equals(robotCode))) {
|
||||
String appKey = dingTalkProperties.getSafetyStockAppKey();
|
||||
String appSecret = dingTalkProperties.getSafetyStockAppSecret();
|
||||
// 兜底:如果配置为空,使用常量
|
||||
if (appKey == null || appKey.isEmpty()) {
|
||||
appKey = SAFETY_STOCK_ROBOT_CODE;
|
||||
}
|
||||
// 动态获取 Token
|
||||
bizLogger.info("使用安全库存机器人Token: robotCode={}", robotCode);
|
||||
return DingUtil.getAccessToken(appKey, appSecret);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSafetyRobot) {
|
||||
// 优先使用配置的 Key/Secret
|
||||
if (safetyKey != null && !safetyKey.isEmpty()) {
|
||||
currentAppKey = safetyKey;
|
||||
} else {
|
||||
currentAppKey = SAFETY_STOCK_ROBOT_CODE;
|
||||
}
|
||||
currentAppSecret = dingTalkProperties.getSafetyStockAppSecret();
|
||||
}
|
||||
|
||||
return DingUtil.getAccessToken(currentAppKey, currentAppSecret);
|
||||
// 默认情况,使用主应用的 Token
|
||||
return accessTokenManager.getAccessToken();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ package com.ruoyi.web.controller.dingtalk;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.dingding.DingUtil;
|
||||
import com.ruoyi.common.dingding.util.DingUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@ -29,7 +29,7 @@ server:
|
||||
port: 8033
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
context-path: /dev-api
|
||||
undertow:
|
||||
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
|
||||
max-http-post-size: -1
|
||||
@ -301,3 +301,5 @@ dingtalk:
|
||||
project-card-template-id: 2b7bd7ab-0a20-43f2-902c-71198a8dd6a1.schema
|
||||
route-card-template-id: 58244f90-b92d-4ab9-b619-8e83cef67d0c.schema
|
||||
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.stereotype.Component;
|
||||
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* 钉钉配置属性
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@Primary
|
||||
@ConfigurationProperties(prefix = "dingtalk")
|
||||
public class DingTalkProperties {
|
||||
/**
|
||||
@ -57,8 +60,41 @@ public class DingTalkProperties {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 接收工艺表格个人用户ID
|
||||
*/
|
||||
private String notifyUserId;
|
||||
/**
|
||||
* 接收工艺审批用户ID
|
||||
*/
|
||||
private String routeApprovalId;
|
||||
|
||||
/**
|
||||
* 回调路由 Key
|
||||
*/
|
||||
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.dingtalkim_1_0.models.SendInteractiveCardHeaders;
|
||||
@ -18,6 +18,7 @@ import com.dingtalk.api.response.OapiRobotSendResponse;
|
||||
import com.dingtalk.api.response.OapiV2UserGetResponse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ruoyi.common.dingding.DingChatCreateResult;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
@ -28,6 +29,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class DingUtil {
|
||||
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,
|
||||
String openConversationId, String outTrackId, Map<String, String> cardDataMap, String callbackRouteKey) {
|
||||
// 修改为 1 (群聊),原本为 0 (单聊) 导致接口报错
|
||||
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.dto.*;
|
||||
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.poi.ExcelUtil;
|
||||
import com.ruoyi.system.domain.BomDetails;
|
||||
|
||||
@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -107,4 +108,25 @@ public class FigureSaveController extends BaseController {
|
||||
@PathVariable Long[] ids) {
|
||||
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.ruoyi.common.utils.HttpRequestUtil;
|
||||
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.poi.ExcelTemplateProc;
|
||||
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.getSecAvbqty())).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"); // 添加分隔线
|
||||
}
|
||||
// 添加总数汇总
|
||||
|
||||
@ -56,6 +56,7 @@ import com.ruoyi.system.domain.bo.ProcessOrderProBo;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空项目关联数据(保留项目)
|
||||
*
|
||||
* @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)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
||||
|
||||
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")
|
||||
@Log(title = "获取项目生产数据表", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@ -371,6 +394,16 @@ public class ProcessOrderProController extends BaseController {
|
||||
return iProcessOrderProService.getOverdueProjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新并计算项目延期状态
|
||||
*/
|
||||
@SaCheckPermission("system:orderPro:list")
|
||||
@GetMapping("/refreshDelayStatus")
|
||||
public R<Void> refreshDelayStatus() {
|
||||
iProcessOrderProService.refreshDelayStatus();
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@SaCheckPermission("system:orderPro:geMRPResults")
|
||||
@Log(title = "获取MRP复核结果", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/getMRPResults/{id}")
|
||||
@ -762,9 +795,7 @@ public class ProcessOrderProController extends BaseController {
|
||||
map.put("processDescription", route.getProcessDescription());
|
||||
map.put("productionOrderNo", orderPro.getProductionOrderNo());
|
||||
map.put("productionName", orderPro.getProductionName());
|
||||
map.put("drawingNo", orderPro.getDrawingNo());
|
||||
map.put("mainProducts", orderPro.getDrawingName());
|
||||
map.put("mainProductsName", orderPro.getDrawingName());
|
||||
|
||||
mapList.add(map);
|
||||
}
|
||||
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.validate.AddGroup;
|
||||
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.excel.DefaultExcelListener;
|
||||
import com.ruoyi.common.excel.ExcelResult;
|
||||
@ -439,12 +439,22 @@ public class ProcessRouteController extends BaseController {
|
||||
cardDataMap.put("status", StringUtils.defaultIfEmpty("待审批", "待审批"));
|
||||
|
||||
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
|
||||
ProcessOrderPro update = new ProcessOrderPro();
|
||||
update.setId(order.getId());
|
||||
update.setUpdateBy("Dingding");
|
||||
update.setAuditCardId(outTrackId1);
|
||||
update.setAuditCardId(fullOutTrackId);
|
||||
update.setBomStatus(2L);
|
||||
// 更新审核状态为1(已审核/提交审核)
|
||||
update.setRouteStatus(0L);
|
||||
|
||||
@ -3,10 +3,13 @@ package com.ruoyi.system.domain;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 项目产品对象 figure_save
|
||||
*
|
||||
@ -65,4 +68,25 @@ public class FigureSave extends BaseEntity {
|
||||
* 箱体类型
|
||||
*/
|
||||
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)
|
||||
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 drawingNo;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String drawingName;
|
||||
|
||||
/**
|
||||
* 计划结束时间
|
||||
@ -72,11 +75,10 @@ public class ProcessOrderPro extends BaseEntity {
|
||||
/**
|
||||
* 是否企标 (0-否, 1-是)
|
||||
*/
|
||||
private Integer isEnterpriseStandard;
|
||||
private String isEnterpriseStandard;
|
||||
/**
|
||||
* 图纸路径
|
||||
*/
|
||||
@Translation(type = TransConstant.OSS_ID_TO_URL)
|
||||
private String drawingPath;
|
||||
|
||||
/**
|
||||
|
||||
@ -6,6 +6,7 @@ import com.ruoyi.common.core.validate.EditGroup;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import javax.validation.constraints.*;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
@ -19,9 +20,7 @@ import javax.validation.constraints.*;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class FigureSaveBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
@ -80,5 +79,33 @@ public class FigureSaveBo extends BaseEntity {
|
||||
*/
|
||||
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
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
|
||||
public class ProcessOrderProBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
@ -39,15 +40,7 @@ public class ProcessOrderProBo extends BaseEntity {
|
||||
*/
|
||||
private String productionName;
|
||||
|
||||
/**
|
||||
* 项目图号
|
||||
*/
|
||||
private String drawingNo;
|
||||
|
||||
/**
|
||||
* 产品名称
|
||||
*/
|
||||
private String drawingName;
|
||||
/**
|
||||
* 计划结束时间
|
||||
*/
|
||||
@ -81,7 +74,13 @@ public class ProcessOrderProBo extends BaseEntity {
|
||||
/**
|
||||
* 是否企标 (0-否, 1-是)
|
||||
*/
|
||||
private Integer isEnterpriseStandard;
|
||||
private String isEnterpriseStandard;
|
||||
|
||||
/**
|
||||
* 是否企标 (查询多选使用)
|
||||
*/
|
||||
private List<String> isEnterpriseStandardList;
|
||||
|
||||
|
||||
private String drawingPath;
|
||||
/**
|
||||
@ -151,4 +150,13 @@ public class ProcessOrderProBo extends BaseEntity {
|
||||
* 通知卡片Id
|
||||
*/
|
||||
private String notificationCardId;
|
||||
/**
|
||||
* 是否延期:0否 1是
|
||||
*/
|
||||
private Integer whetherDelay;
|
||||
|
||||
/**
|
||||
* 延期天数
|
||||
*/
|
||||
private Long delayDay;
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package com.ruoyi.system.domain.dto.excuteDrawing;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
@Data
|
||||
public class RigidChainModelDTO {
|
||||
@ -28,5 +31,29 @@ public class RigidChainModelDTO {
|
||||
private String figureNumber;
|
||||
private String productName;
|
||||
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 lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
@ -21,42 +22,41 @@ public class FigureSaveVo {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ExcelProperty(value = "")
|
||||
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;
|
||||
|
||||
/**
|
||||
* 生产令号
|
||||
*/
|
||||
@ExcelProperty(value = "生产令号")
|
||||
private String productionCode;
|
||||
|
||||
/**
|
||||
* 产品型号
|
||||
*/
|
||||
@ExcelProperty(value = "产品型号")
|
||||
private String figureNumber;
|
||||
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
@ExcelProperty(value = "数量")
|
||||
@ExcelProperty(value = "数量", index = 3)
|
||||
private Long figureNum;
|
||||
|
||||
/**
|
||||
* 行程
|
||||
*/
|
||||
@ExcelProperty(value = "行程")
|
||||
@ExcelProperty(value = "行程", index = 4)
|
||||
private Long jdInventory;
|
||||
|
||||
/**
|
||||
* 图纸路径
|
||||
*/
|
||||
@ExcelProperty(value = "图纸路径")
|
||||
@ExcelProperty(value = "图纸路径", index = 5)
|
||||
private String drawPath;
|
||||
/**
|
||||
* 类型
|
||||
@ -67,17 +67,41 @@ public class FigureSaveVo {
|
||||
/**
|
||||
* 关联项目表
|
||||
*/
|
||||
@ExcelProperty(value = "关联项目表")
|
||||
private Long pid;
|
||||
/**
|
||||
* 轴向
|
||||
*/
|
||||
@ExcelProperty(value = "轴向")
|
||||
private String axialType;
|
||||
|
||||
/**
|
||||
* 箱体类型
|
||||
*/
|
||||
@ExcelProperty(value = "箱体类型")
|
||||
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 = "项目名称")
|
||||
private String productionName;
|
||||
|
||||
/**
|
||||
* 图号
|
||||
*/
|
||||
@ExcelProperty(value = "图号")
|
||||
private String drawingNo;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@ExcelProperty(value = "名称")
|
||||
private String drawingName;
|
||||
/**
|
||||
* 计划结束时间
|
||||
*/
|
||||
@ -94,7 +84,7 @@ public class ProcessOrderProVo {
|
||||
* 是否企标 (0-否, 1-是)
|
||||
*/
|
||||
@ExcelProperty(value = "是否企标 (0-否, 1-是)")
|
||||
private Integer isEnterpriseStandard;
|
||||
private String isEnterpriseStandard;
|
||||
/**
|
||||
* bom状态(0,未完成 1,完成 2已上传)
|
||||
*/
|
||||
@ -115,6 +105,21 @@ public class ProcessOrderProVo {
|
||||
@Translation(mapper = "drawingPath", type = TransConstant.OSS_ID_TO_URL)
|
||||
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;
|
||||
/**
|
||||
* 创建人
|
||||
@ -170,4 +175,14 @@ public class ProcessOrderProVo {
|
||||
@ExcelProperty(value = "通知卡片Id")
|
||||
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.vo.BomDetailsVo;
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
|
||||
/**
|
||||
* bom明细Mapper接口
|
||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
* @date 2024-04-08
|
||||
*/
|
||||
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.vo.FigureSaveVo;
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
|
||||
/**
|
||||
* 外购件临时Mapper接口
|
||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
* @date 2024-05-28
|
||||
*/
|
||||
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.vo.MaterialBomVo;
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
|
||||
/**
|
||||
* bomaterialMapper接口
|
||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
* @date 2024-11-08
|
||||
*/
|
||||
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}")
|
||||
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);
|
||||
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package com.ruoyi.system.mapper;
|
||||
import com.ruoyi.system.domain.ProductionOrder;
|
||||
import com.ruoyi.system.domain.vo.ProductionOrderVo;
|
||||
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
|
||||
/**
|
||||
* 生产订单Mapper接口
|
||||
@ -11,5 +12,6 @@ import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||
* @date 2024-10-20
|
||||
*/
|
||||
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> selectByProCode(String totalWeight);
|
||||
|
||||
/**
|
||||
* 导入数据
|
||||
*/
|
||||
String importData(List<FigureSaveVo> dataList, boolean updateSupport, String operName);
|
||||
}
|
||||
|
||||
@ -56,6 +56,11 @@ public interface IProcessOrderProService {
|
||||
*/
|
||||
Boolean updateByBo(ProcessOrderProBo bo);
|
||||
|
||||
/**
|
||||
* 清空项目关联数据(保留项目本身)
|
||||
*/
|
||||
R<Void> clearProjectData(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 校验并批量删除项目令号信息
|
||||
*/
|
||||
@ -89,4 +94,17 @@ public interface IProcessOrderProService {
|
||||
R<String> getRouteLog(Long id) throws JsonProcessingException;
|
||||
|
||||
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.core.conditions.query.LambdaQueryWrapper;
|
||||
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 org.springframework.stereotype.Service;
|
||||
import com.ruoyi.system.domain.bo.FigureSaveBo;
|
||||
@ -31,7 +34,7 @@ import java.util.Collection;
|
||||
public class FigureSaveServiceImpl implements IFigureSaveService {
|
||||
|
||||
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(StringUtils.isNotBlank(bo.getDrawPath()), FigureSave::getDrawPath, bo.getDrawPath());
|
||||
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;
|
||||
}
|
||||
|
||||
@ -137,4 +145,39 @@ public class FigureSaveServiceImpl implements IFigureSaveService {
|
||||
|
||||
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.entity.SysUser;
|
||||
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.utils.FtpUtil;
|
||||
import com.ruoyi.common.utils.HttpUtils;
|
||||
@ -91,11 +94,23 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
private final DeviceSpec60rMapper deviceSpec60rMapper;
|
||||
private final DeviceSpec80rMapper deviceSpec80rMapper;
|
||||
private final DeviceSpec100rMapper deviceSpec100rMapper;
|
||||
|
||||
private final IFigureSaveService iFigureSaveService;
|
||||
private final SafetyStockMapper safetyStockMapper;
|
||||
private final IProcessRouteService iProcessRouteService;
|
||||
private final IProductionOrderService iProductionOrderService;
|
||||
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
|
||||
private ProcessRouteMapper processRouteMapper;
|
||||
@Autowired
|
||||
@ -111,8 +126,12 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
@Value("${app.drawing.status-url:http://192.168.5.18:9000/getstate}")
|
||||
private String statusUrl;
|
||||
@Autowired
|
||||
private IFigureSaveService iFigureSaveService;
|
||||
private IFigureSaveService iFigureSaveSservice;
|
||||
private Map<String, SpecStrategy> specStrategies;
|
||||
@Autowired
|
||||
private BomDetailsMapper bomDetailsMapper;
|
||||
@Autowired
|
||||
private MaterialBomMapper materialBomMapper;
|
||||
|
||||
public static String[] getNullPropertyNames(Object source) {
|
||||
BeanWrapper src = new BeanWrapperImpl(source);
|
||||
@ -234,11 +253,71 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
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
|
||||
public TableDataInfo<ProcessOrderProVo> queryPageList(ProcessOrderProBo bo, PageQuery pageQuery) {
|
||||
// 在查询前先刷新数据库中的延期状态
|
||||
// refreshDelayStatus();
|
||||
|
||||
LambdaQueryWrapper<ProcessOrderPro> lqw = buildQueryWrapper(bo);
|
||||
Page<ProcessOrderProVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
@ -258,8 +337,6 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
LambdaQueryWrapper<ProcessOrderPro> lqw = Wrappers.lambdaQuery();
|
||||
lqw.like(StringUtils.isNotBlank(bo.getProductionOrderNo()), ProcessOrderPro::getProductionOrderNo, bo.getProductionOrderNo());
|
||||
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.getPlanStartTime() != null, ProcessOrderPro::getPlanStartTime, bo.getPlanStartTime());
|
||||
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.getBomStatus() != null, ProcessOrderPro::getBomStatus, bo.getBomStatus());
|
||||
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.getWhetherDelay() != null, ProcessOrderPro::getWhetherDelay, bo.getWhetherDelay());
|
||||
//按照创建时间排序,最新的在顶端
|
||||
lqw.orderByDesc(true, ProcessOrderPro::getCreateTime);
|
||||
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());
|
||||
}
|
||||
}
|
||||
// 如果出图时间为空,默认为当前时间
|
||||
if (add.getDrawingTime() == null) {
|
||||
add.setDrawingTime(new Date());
|
||||
bo.setDrawingTime(add.getDrawingTime());
|
||||
|
||||
// 对项目令号的判断,根据前缀设置DrawingType
|
||||
String productionOrderNo = bo.getProductionOrderNo();
|
||||
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.setBomStatus(0L);
|
||||
@ -303,92 +414,116 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送钉钉卡片
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
String productionOrderNo = processOrderProVo.getProductionOrderNo();
|
||||
|
||||
// 调用 selectByProjectCode 方法并检查结果
|
||||
return iProcessRouteService.delByProjectCode(productionOrderNo);
|
||||
}
|
||||
@ -443,7 +595,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
log.error("删除 ID 列表失败: {}", ids);
|
||||
}
|
||||
|
||||
return R.fail();
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -564,6 +716,11 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
figureSave.setFigureName(orderPro.getFigureName());
|
||||
figureSave.setAxialType(axial);
|
||||
figureSave.setBoxType(box);
|
||||
figureSave.setFileType(orderPro.getFileType());
|
||||
figureSave.setDrawBy(orderPro.getDrawBy());
|
||||
figureSave.setDelayDay(orderPro.getDelayDay());
|
||||
figureSave.setDrawTime(orderPro.getDrawTime());
|
||||
|
||||
try {
|
||||
figureSave.setJdInventory(Long.valueOf(journey));
|
||||
} catch (Exception ignore) {
|
||||
@ -620,12 +777,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
ProcessOrderPro po = processOrderPro;
|
||||
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||
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 planEnd = po.getPlanEndTime() == null ? "" : sdfDate.format(po.getPlanEndTime());
|
||||
if (StringUtils.isNotBlank(planStart) || StringUtils.isNotBlank(planEnd)) {
|
||||
@ -637,7 +789,7 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
}
|
||||
|
||||
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;
|
||||
@ -667,8 +819,12 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
|
||||
markdownText.append("\n请相关人员及时处理。");
|
||||
|
||||
// 发送Markdown消息
|
||||
DingUtil.sendOrgGroupMarkdown(accessToken, dingTalkProperties.getRobotCode(), dingTalkProperties.getOpenConversationId(), "生产项目创建通知", markdownText.toString());
|
||||
// 发送Markdown消息给个人
|
||||
if (StringUtils.isNotBlank(dingTalkProperties.getNotifyUserId())) {
|
||||
robotGroupService.sendMarkdownToUser(dingTalkProperties.getNotifyUserId(), "生产项目创建通知", markdownText.toString());
|
||||
} else {
|
||||
log.warn("未配置钉钉通知个人ID(notifyUserId),跳过发送Markdown消息");
|
||||
}
|
||||
|
||||
// 下载并发送Excel文件
|
||||
log.info("开始下载并发送Excel文件: {}", orderPro.getProductionOrderNo());
|
||||
@ -712,10 +868,14 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
File excelFile = new File(filePath);
|
||||
if (excelFile.exists() && excelFile.length() > 0) {
|
||||
// Upload media
|
||||
String mediaId = DingUtil.uploadMedia(accessToken, "file", filePath);
|
||||
// Send file
|
||||
DingUtil.sendOrgGroupFile(accessToken, dingTalkProperties.getRobotCode(), dingTalkProperties.getOpenConversationId(), mediaId, excelFile.getName(), "sampleFile");
|
||||
log.info("Excel文件发送成功");
|
||||
String mediaId = mediaService.uploadMedia("file", filePath);
|
||||
// Send file给个人
|
||||
if (StringUtils.isNotBlank(dingTalkProperties.getNotifyUserId())) {
|
||||
robotGroupService.sendFileToUser(dingTalkProperties.getNotifyUserId(), mediaId, excelFile.getName());
|
||||
log.info("Excel文件发送成功");
|
||||
} else {
|
||||
log.warn("未配置钉钉通知个人ID(notifyUserId),跳过发送Excel文件");
|
||||
}
|
||||
} else {
|
||||
log.warn("Excel文件未找到或为空: {}", filePath);
|
||||
}
|
||||
@ -1194,6 +1354,84 @@ public class ProcessOrderProServiceImpl implements IProcessOrderProService {
|
||||
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
|
||||
*/
|
||||
|
||||
@ -848,10 +848,16 @@ public class ProcessRouteServiceImpl implements IProcessRouteService {
|
||||
}
|
||||
//将带工艺的bom 存表 bomdetail
|
||||
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) {
|
||||
// 处理表中的材料bom
|
||||
bomDetailsVos.add(createBomDetails(processRouteVo));
|
||||
try {
|
||||
// 处理表中的材料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.setMaterial(processRoute.getMaterial());
|
||||
|
||||
if (StringUtils.isEmpty(processRoute.getDiscUsage())) {
|
||||
throw new ServiceException("材料BOM物料信息不完整: 物料编码[" + processRoute.getRawMaterialCode() + "], 物料名称[" + processRoute.getRawMaterialName() + "] 的用量为空");
|
||||
}
|
||||
|
||||
// 处理单位换算
|
||||
if ("mm".equals(processRoute.getBomUnit())) {
|
||||
bomDetails.setQuantity(Double.parseDouble(processRoute.getDiscUsage()) / 1000.0); // 转换为米
|
||||
|
||||
@ -207,7 +207,6 @@ public class WorkProcedureServiceImpl implements IWorkProcedureService {
|
||||
parentTask.setId(pro.getId());
|
||||
parentTask.setText(pro.getProductionOrderNo());
|
||||
parentTask.setOpen(false);
|
||||
parentTask.setToolTipsTxt(pro.getDrawingNo());
|
||||
/* parentTask.setProgress(1L);*/
|
||||
parentTask.setStatus("yellow");
|
||||
parentTask.setParent(null);
|
||||
|
||||
@ -20,6 +20,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="pid" column="pid"/>
|
||||
<result property="axialType" column="axial_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>
|
||||
|
||||
</mapper>
|
||||
|
||||
@ -8,8 +8,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="id" column="id"/>
|
||||
<result property="productionOrderNo" column="production_order_no"/>
|
||||
<result property="productionName" column="production_name"/>
|
||||
<result property="drawingNo" column="drawing_no"/>
|
||||
<result property="drawingName" column="drawing_name"/>
|
||||
<result property="whetherDelay" column="whether_delay"/>
|
||||
<result property="delayDay" column="delay_day"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
@ -38,7 +38,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
<!-- 根据项目编号查询 -->
|
||||
<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
|
||||
FROM process_order_pro
|
||||
WHERE production_order_no = #{routeDescription}
|
||||
@ -48,8 +48,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
UPDATE process_order_pro
|
||||
SET production_order_no = #{productionOrderNo},
|
||||
production_name = #{productionName},
|
||||
drawing_no = #{drawingNo},
|
||||
drawing_name = #{drawingName},
|
||||
plan_end_time = #{planEndTime},
|
||||
plan_start_time = #{planStartTime},
|
||||
create_by = #{createBy},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user