更新,工序计划

This commit is contained in:
tzy 2026-10-28 17:14:56 +08:00
parent 9fb3090015
commit ee9fb0149f
118 changed files with 10324 additions and 45 deletions

View File

@ -332,7 +332,7 @@ dingtalk:
#工艺审核互动卡片回调路由Key #工艺审核互动卡片回调路由Key
callback-route-key: update_route_status2 callback-route-key: update_route_status2
# 出图完成 / PLM图纸到达通知(钉钉 userId/staffId,多人逗号分隔;以 0 开头的须加引号) # 出图完成 / PLM图纸到达通知(钉钉 userId/staffId,多人逗号分隔;以 0 开头的须加引号)
notify-user-id: "293047444820124967,0108392663561185496,03610242692523410847" notify-user-id: "293047444820124967,0108392663561185496,03610242692523410847,283633411029629036"
# 工艺审核互动卡片 # 工艺审核互动卡片
route-approval-id: "0108392663561185496" route-approval-id: "0108392663561185496"
# 工艺计划推送用户;无需做工艺且出图完成后、PLM 总装BOM与工艺都到齐后通知生管 # 工艺计划推送用户;无需做工艺且出图完成后、PLM 总装BOM与工艺都到齐后通知生管

View File

@ -0,0 +1,23 @@
package com.ruoyi.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 图纸/模型文件查询服务。工艺路线「下载图纸模型」由后端转发,不让浏览器直连。
*/
@Data
@Component
@ConfigurationProperties(prefix = "app.drawing.file-query")
public class DrawingFileQueryProperties {
/** 文件服务根地址,如 http://192.168.5.20:28000 */
private String baseUrl = "http://192.168.5.20:28000";
/** 下载路径 */
private String downloadPath = "/api/FileQuery/download";
/** 读取超时(毫秒),模型文件可能较大 */
private int readTimeoutMs = 300_000;
}

View File

@ -0,0 +1,52 @@
package com.ruoyi.common.convert;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import java.math.BigDecimal;
/**
* 单台数量按原文读取。数字单元格转成普通数字文本,文本单元格保留 {@code 1/4}、{@code 1/3} 这类分数。
*/
public class NullableQuantityTextConverter implements Converter<String> {
@Override
public Class<String> supportJavaTypeKey() {
return String.class;
}
@Override
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (cellData == null || cellData.getType() == null || cellData.getType() == CellDataTypeEnum.EMPTY) {
return null;
}
switch (cellData.getType()) {
case NUMBER:
BigDecimal number = cellData.getNumberValue();
if (number == null) {
return null;
}
return number.stripTrailingZeros().toPlainString();
case STRING:
return ExcelNullableText.normalize(cellData.getStringValue());
case BOOLEAN:
if (cellData.getBooleanValue() == null) {
return null;
}
return cellData.getBooleanValue() ? "1" : "0";
default:
return null;
}
}
@Override
public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new WriteCellData<>(value == null ? "" : value);
}
}

View File

@ -0,0 +1,343 @@
package com.ruoyi.common.dingding.relay;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.config.DingTalkProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* 待办中转:先登录取 token,再以 multipart 调用 /ding/send 的 createToBeDoneUser。
*/
@Service
public class DingTodoRelayClient {
private static final Logger log = LoggerFactory.getLogger(DingTodoRelayClient.class);
/**
* 中转按该名字反射钉钉创建待办
*/
public static final String METHOD_CREATE_TO_BE_DONE_USER = "createToBeDoneUser";
private static final long DAY_MILLIS = 24L * 60L * 60L * 1000L;
private final RestTemplate restTemplate;
private final DingTalkProperties properties;
private volatile String cachedToken;
public DingTodoRelayClient(DingTalkProperties properties) {
this.properties = properties;
RestTemplate template = new RestTemplate();
template.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// 业务码在响应体里,HTTP 4xx 也把正文带回来
}
});
this.restTemplate = template;
}
/**
* 账号未配齐时不调用中转。
*/
public boolean isConfigured() {
return StringUtils.hasText(properties.getTodoBaseUrl())
&& StringUtils.hasText(properties.getTodoUsername())
&& StringUtils.hasText(properties.getTodoPassword())
&& StringUtils.hasText(properties.getTodoUuid());
}
/**
* 按场景文案创建工艺计划表待办。
*
* @param userIdsCsv 逗号分隔的钉钉 userId
* @param createUser 创建人,空则用接收人中的第一个
* @param dueDays 没有未来的工艺预计完成时间时,截止为当前时间加该天数
* @param contentLead 正文第一句,空则用默认说明
* @param recipientType code=考勤工号,name=姓名
*/
public String createProcessPlanTodo(String productionOrderNo, String projectName, Date routeUptime,
File excelFile, String userIdsCsv, String createUser, int dueDays,
String contentLead, String recipientType) {
List<String> users = splitUsers(userIdsCsv);
if (users.isEmpty()) {
throw new IllegalStateException("待办接收人为空");
}
String creator = StringUtils.hasText(createUser) ? createUser.trim() : users.get(0);
String orderNo = StringUtils.hasText(productionOrderNo) ? productionOrderNo.trim() : "";
String name = StringUtils.hasText(projectName) ? projectName.trim() : "";
String fileName = excelFile != null ? excelFile.getName() : "";
String title = "工艺任务待处理" + (StringUtils.hasText(orderNo) ? " " + orderNo : "");
StringBuilder content = new StringBuilder();
content.append(StringUtils.hasText(contentLead) ? contentLead.trim() : "工艺计划表已下发至ERP数据推送机器人或登录EVO物料系统导出工艺计划表,请及时处理。");
if (StringUtils.hasText(orderNo)) {
content.append("\n项目令号:").append(orderNo);
}
if (StringUtils.hasText(name)) {
content.append("\n项目名称:").append(name);
}
if (StringUtils.hasText(fileName)) {
content.append("\n文件:").append(fileName);
}
return createToBeDoneUser(users, creator, title, content.toString(), resolveDueTime(routeUptime, dueDays), excelFile, recipientType);
}
/**
* 调用中转创建待办。
*
* @param users 接收人,类型由 recipientInfo / recipientType 决定
* @param createUser 创建人,类型同接收人
* @param dueTimeMillis 截止时间,毫秒时间戳
* @param attachment 可选附件,表单字段名 files
* @return 中转原始响应
*/
public String createToBeDoneUser(List<String> users, String createUser, String title, String content,
long dueTimeMillis, File attachment, String recipientType) {
if (!isConfigured()) {
throw new IllegalStateException("未配置钉钉待办中转账号(dingtalk.todo-username/password/uuid/todo-base-url)");
}
if (users == null || users.isEmpty()) {
throw new IllegalArgumentException("待办接收人不能为空");
}
if (!StringUtils.hasText(createUser)) {
throw new IllegalArgumentException("待办创建人不能为空");
}
if (!StringUtils.hasText(title) || !StringUtils.hasText(content)) {
throw new IllegalArgumentException("待办标题和内容不能为空");
}
if (dueTimeMillis <= 0) {
throw new IllegalArgumentException("待办截止时间无效");
}
String robotCode = resolveRobotCode();
String clientId = StringUtils.hasText(properties.getAppKey()) ? properties.getAppKey().trim() : robotCode;
String clientSecret = properties.getAppSecret();
if (!StringUtils.hasText(clientSecret)) {
throw new IllegalStateException("未配置 dingtalk.app-secret,无法创建待办");
}
JSONObject data = new JSONObject(true);
data.put("month", METHOD_CREATE_TO_BE_DONE_USER);
data.put("robotCode", robotCode);
data.put("clientId", clientId);
data.put("clientSecret", clientSecret.trim());
data.put("recipientType", normalizeRecipientType(recipientType));
data.put("recipientInfo", "unionId");
data.put("createUser", createUser.trim());
data.put("users", users);
data.put("title", title);
data.put("content", content);
data.put("dueTime", dueTimeMillis);
String body = postSend(data.toJSONString(), attachment, false);
log.info("钉钉待办已创建 title={} users={} dueTime={}", title, users.size(), dueTimeMillis);
return body;
}
private String postSend(String dataJson, File attachment, boolean retried) {
String token = currentToken(retried);
String url = join(properties.getTodoBaseUrl(), "/ding/send");
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("data", dataJson);
if (attachment != null && attachment.exists() && attachment.length() > 0) {
form.add("files", new FileSystemResource(attachment));
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
String headerName = defaultText(properties.getTodoTokenHeader(), "Authorization");
String prefix = properties.getTodoTokenPrefix() == null ? "" : properties.getTodoTokenPrefix();
headers.set(headerName, prefix + token);
log.info("创建钉钉待办请求 POST {} Content-Type=multipart/form-data file={} data={}",
url, attachment == null ? "" : attachment.getName(), dataJson);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, new HttpEntity<>(form, headers), String.class);
String body = response.getBody();
log.info("创建钉钉待办响应 status={} body={}", response.getStatusCodeValue(), body);
if (isAuthFailure(response.getStatusCodeValue(), body) && !retried) {
log.info("待办中转 token 失效,重新登录后重试");
cachedToken = null;
return postSend(dataJson, attachment, true);
}
assertBizOk(body, "创建待办");
return body;
}
private String currentToken(boolean forceLogin) {
if (!forceLogin && StringUtils.hasText(cachedToken)) {
return cachedToken;
}
synchronized (this) {
if (!forceLogin && StringUtils.hasText(cachedToken)) {
return cachedToken;
}
cachedToken = login();
return cachedToken;
}
}
private String login() {
String url = join(properties.getTodoBaseUrl(), "/login");
JSONObject body = new JSONObject(true);
body.put("username", properties.getTodoUsername().trim());
body.put("password", properties.getTodoPassword());
body.put("uuid", properties.getTodoUuid().trim());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String payload = body.toJSONString();
log.info("待办中转登录请求 POST {} Content-Type=application/json body={}", url, payload);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, new HttpEntity<>(payload, headers), String.class);
String raw = response.getBody();
log.info("待办中转登录响应 status={} body={}", response.getStatusCodeValue(), raw);
assertBizOk(raw, "待办中转登录");
JSONObject root = JSON.parseObject(raw);
String token = root == null ? null : root.getString("token");
if (!StringUtils.hasText(token)) {
throw new IllegalStateException("待办中转登录成功但未返回 token");
}
return token.trim();
}
/** 截止时间用工艺预计完成时间;未填或已过期时,用当前时间加截止天数。 */
private long resolveDueTime(Date routeUptime, int dueDays) {
long now = System.currentTimeMillis();
if (routeUptime != null && routeUptime.getTime() > now) {
return routeUptime.getTime();
}
int days = dueDays > 0 ? dueDays : 3;
return now + days * DAY_MILLIS;
}
private String resolveRobotCode() {
if (StringUtils.hasText(properties.getRobotCode())) {
return properties.getRobotCode().trim();
}
if (StringUtils.hasText(properties.getAppKey())) {
return properties.getAppKey().trim();
}
throw new IllegalStateException("未配置 dingtalk.robot-code,无法创建待办");
}
private static List<String> splitUsers(String userIdsCsv) {
if (!StringUtils.hasText(userIdsCsv)) {
return Collections.emptyList();
}
List<String> users = new ArrayList<>();
for (String part : userIdsCsv.split("[,,]")) {
if (StringUtils.hasText(part)) {
users.add(part.trim());
}
}
return users;
}
private static void assertBizOk(String body, String action) {
if (!StringUtils.hasText(body)) {
throw new IllegalStateException(action + "响应为空");
}
JSONObject root;
try {
root = JSON.parseObject(body);
} catch (Exception ex) {
throw new IllegalStateException(action + "响应无法解析: " + abbreviate(body));
}
if (root == null) {
throw new IllegalStateException(action + "响应为空");
}
Integer code = root.getInteger("code");
if (code == null || code != 200) {
String msg = firstText(root.getString("mes"), root.getString("msg"), abbreviate(body));
throw new IllegalStateException(action + "失败: " + msg);
}
}
private static boolean isAuthFailure(int httpStatus, String body) {
if (httpStatus == 401 || httpStatus == 403) {
return true;
}
if (!StringUtils.hasText(body)) {
return false;
}
try {
JSONObject root = JSON.parseObject(body);
if (root == null) {
return false;
}
Integer code = root.getInteger("code");
if (code != null && code == 200) {
return false;
}
if (code != null && (code == 401 || code == 403)) {
return true;
}
if (code != null) {
return false;
}
String msg = firstText(root.getString("mes"), root.getString("msg"), "");
return msg.contains("token") || msg.contains("登录") || msg.contains("未授权") || msg.contains("认证");
} catch (Exception ex) {
return false;
}
}
private static String join(String base, String path) {
String b = base == null ? "" : base.trim();
while (b.endsWith("/")) {
b = b.substring(0, b.length() - 1);
}
String p = path.startsWith("/") ? path : "/" + path;
return b + p;
}
private static String normalizeRecipientType(String recipientType) {
return "code".equalsIgnoreCase(defaultText(recipientType, "")) ? "code" : "name";
}
private static String defaultText(String value, String fallback) {
return StringUtils.hasText(value) ? value.trim() : fallback;
}
private static String firstText(String... values) {
if (values == null) {
return "";
}
for (String value : values) {
if (StringUtils.hasText(value)) {
return value;
}
}
return "";
}
private static String abbreviate(String text) {
if (text == null) {
return "";
}
return text.length() <= 300 ? text : text.substring(0, 300);
}
}

View File

@ -0,0 +1,104 @@
package com.ruoyi.common.helper;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.exception.SaTokenContextException;
import com.ruoyi.common.core.domain.model.LoginUser;
import java.util.HashMap;
import java.util.Map;
/**
* 给定时任务线程补登录用户。
* 不注册成 Spring Bean,避免和 Sa-Token 的 Web 上下文抢同一个注入点。
* Web 请求仍走原来的上下文;只有当前线程调用过 {@link #login(LoginUser)} 时,这里才会生效。
*/
public class JobLoginContext implements SaTokenSecondContext {
private static final JobLoginContext INSTANCE = new JobLoginContext();
private static final ThreadLocal<Map<String, Object>> STORE = new ThreadLocal<>();
private final SaStorage storage = new ThreadStorage();
private JobLoginContext() {
}
public static void login(LoginUser loginUser) {
if (SaManager.getSaTokenSecondContext() != INSTANCE) {
SaManager.setSaTokenSecondContext(INSTANCE);
}
Map<String, Object> map = new HashMap<>();
map.put(LoginHelper.LOGIN_USER_KEY, loginUser);
if (loginUser.getUserId() != null) {
map.put(LoginHelper.USER_KEY, loginUser.getUserId());
}
STORE.set(map);
}
public static void logout() {
STORE.remove();
}
@Override
public boolean isValid() {
return STORE.get() != null;
}
@Override
public SaRequest getRequest() {
throw new SaTokenContextException("定时任务登录上下文不提供 Request");
}
@Override
public SaResponse getResponse() {
throw new SaTokenContextException("定时任务登录上下文不提供 Response");
}
@Override
public SaStorage getStorage() {
return storage;
}
@Override
public boolean matchPath(String pattern, String path) {
return false;
}
private static final class ThreadStorage implements SaStorage {
@Override
public Object getSource() {
return STORE.get();
}
@Override
public Object get(String key) {
Map<String, Object> map = STORE.get();
return map == null ? null : map.get(key);
}
@Override
public SaStorage set(String key, Object value) {
Map<String, Object> map = STORE.get();
if (map == null) {
map = new HashMap<>();
STORE.set(map);
}
map.put(key, value);
return this;
}
@Override
public SaStorage delete(String key) {
Map<String, Object> map = STORE.get();
if (map != null) {
map.remove(key);
}
return this;
}
}
}

View File

@ -0,0 +1,85 @@
package com.ruoyi.common.utils;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Collection;
import java.util.Date;
/**
* 项目整体出图时间驱动的延期/完成判定。
* 工作日按一周六天(周一至周六),周日不计入延期天数。
*/
public final class DrawingDelayCalculator {
public static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");
/** 方案设计完成 */
public static final long BOM_STATUS_SCHEME_DONE = 0L;
/** 设计出图完成 */
public static final long BOM_STATUS_DRAWING_DONE = 1L;
private DrawingDelayCalculator() {
}
public static boolean allProductsHaveDrawPath(Collection<String> drawPaths) {
if (drawPaths == null || drawPaths.isEmpty()) {
return false;
}
for (String path : drawPaths) {
if (path == null || path.trim().isEmpty()) {
return false;
}
}
return true;
}
/**
* 出图时间之后到 now 之间的工作日天数(不含出图当天,周日不计)。
*/
public static long delayWorkdays(Date drawingTime, Date now) {
if (drawingTime == null || now == null || !now.after(drawingTime)) {
return 0L;
}
LocalDate start = toLocalDate(drawingTime);
LocalDate end = toLocalDate(now);
long days = 0L;
for (LocalDate d = start.plusDays(1); !d.isAfter(end); d = d.plusDays(1)) {
if (d.getDayOfWeek() != DayOfWeek.SUNDAY) {
days++;
}
}
return days;
}
public static DrawingDelayResult evaluate(Date drawingTime, Date now, boolean drawingComplete, Long bomStatus) {
boolean overdue = drawingTime != null && now != null && now.after(drawingTime);
long days = delayWorkdays(drawingTime, now);
DrawingDelayResult result = new DrawingDelayResult();
result.setDrawingComplete(drawingComplete);
result.setWhetherDelay(overdue ? 1 : 0);
result.setDelayDay(overdue ? days : 0L);
if (drawingComplete) {
result.setCurrentlyDelayed(false);
result.setProgressLabel("设计出图完成");
result.setBomStatus(promoteBomStatus(bomStatus));
} else {
result.setCurrentlyDelayed(overdue);
result.setProgressLabel(overdue ? "延期" : "进行中");
result.setBomStatus(bomStatus);
}
return result;
}
private static Long promoteBomStatus(Long bomStatus) {
if (bomStatus == null || bomStatus == BOM_STATUS_SCHEME_DONE) {
return BOM_STATUS_DRAWING_DONE;
}
return bomStatus;
}
private static LocalDate toLocalDate(Date date) {
return date.toInstant().atZone(ZONE).toLocalDate();
}
}

View File

@ -0,0 +1,28 @@
package com.ruoyi.common.utils;
import lombok.Data;
/**
* 项目出图延期计算结果。
*/
@Data
public class DrawingDelayResult {
/** 已填产品是否全部有图纸路径 */
private boolean drawingComplete;
/** 超期标识:0否 1是(完成后仍保留) */
private Integer whetherDelay;
/** 延期工作日天数(完成后冻结) */
private Long delayDay;
/** 回写的项目状态;完成时 0→1,已进入后续流程则保持不变 */
private Long bomStatus;
/** 当前仍处于延期中(未完成且已过出图时间) */
private boolean currentlyDelayed;
/** 进行中 / 延期 / 设计出图完成 */
private String progressLabel;
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.common.utils;
/**
* 技术预研令号:令号中含 JSYY 时不属于生产令号,禁止更新物料清单、推送工艺。
*/
public final class JsyyOrderNo {
public static final String MARK = "JSYY";
public static final String MSG = "JSYY技术预研令号不属于生产令号,严禁直接用于生产执行,禁止更新物料清单、禁止推送工艺";
private JsyyOrderNo() {
}
public static boolean matches(String orderNo) {
return orderNo != null && orderNo.toUpperCase().contains(MARK);
}
public static String denyIfMatches(String orderNo) {
return matches(orderNo) ? MSG : null;
}
}

View File

@ -0,0 +1,108 @@
package com.ruoyi.common.utils;
/**
* 项目状态(process_order_pro.bom_status)操作锁。
* <ul>
* <li>8 工艺审核完成:禁止出图、禁止上传工艺路线</li>
* <li>6 开始生产 / 7 生产完成:禁止出图、上传工艺路线、推送工艺、更新物料清单</li>
* </ul>
*/
public final class ProjectStatusLock {
/** 方案设计完成 / 设计出图中 */
public static final long SCHEME_DONE = 0L;
/** 设计出图完成 */
public static final long DRAWING_DONE = 1L;
/** 工艺待审核 */
public static final long ROUTE_PENDING = 2L;
/** 已推送BOM */
public static final long BOM_PUSHED = 3L;
/** 已推送工艺 */
public static final long ROUTE_PUSHED = 4L;
/** 开始生产 */
public static final long IN_PRODUCTION = 6L;
/** 生产完成 */
public static final long PRODUCTION_DONE = 7L;
/** 工艺审核完成 */
public static final long ROUTE_APPROVED = 8L;
/** 工艺审核拒绝 */
public static final long ROUTE_REJECTED = 9L;
public enum Action {
/** 执行出图 */
DRAWING("出图"),
/** 上传/导入工艺路线 */
UPLOAD_ROUTE("上传工艺路线"),
/** 推送工艺到金蝶 */
PUSH_ROUTE("推送工艺"),
/** 更新物料清单到金蝶 */
UPDATE_MATERIAL("更新物料清单");
private final String label;
Action(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
private ProjectStatusLock() {
}
public static String statusLabel(Long bomStatus) {
if (bomStatus == null) {
return "未知";
}
long s = bomStatus;
if (s == SCHEME_DONE) return "方案设计完成";
if (s == DRAWING_DONE) return "设计出图完成";
if (s == ROUTE_PENDING) return "工艺待审核";
if (s == BOM_PUSHED) return "已推送BOM";
if (s == ROUTE_PUSHED) return "已推送工艺";
if (s == 5L || s == IN_PRODUCTION) return "开始生产";
if (s == PRODUCTION_DONE) return "生产完成";
if (s == ROUTE_APPROVED) return "工艺审核完成";
if (s == ROUTE_REJECTED) return "工艺审核拒绝";
return String.valueOf(bomStatus);
}
public static boolean isLocked(Long bomStatus, Action action) {
return denyIfLocked(bomStatus, action) != null;
}
/**
* @return 禁止时返回提示文案;允许时返回 null
*/
public static String denyIfLocked(Long bomStatus, Action action) {
if (bomStatus == null || action == null) {
return null;
}
long s = bomStatus;
boolean productionLocked = s == 5L || s == IN_PRODUCTION || s == PRODUCTION_DONE;
boolean routeApproved = s == ROUTE_APPROVED;
switch (action) {
case DRAWING:
case UPLOAD_ROUTE:
if (productionLocked || routeApproved) {
return buildMsg(bomStatus, action);
}
return null;
case PUSH_ROUTE:
case UPDATE_MATERIAL:
if (productionLocked) {
return buildMsg(bomStatus, action);
}
return null;
default:
return null;
}
}
private static String buildMsg(Long bomStatus, Action action) {
return "当前项目状态为「" + statusLabel(bomStatus) + "」,不允许" + action.getLabel();
}
}

View File

@ -0,0 +1,65 @@
package com.ruoyi.common.utils.file;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* PLM 落盘图纸文件名与物料编码(图号)匹配。
* <p>
* 标准名 {@code 图号_名称.pdf};历史名可能带 {@code -14位以上数字} 时间戳。
* 不能用「图号-」做前缀匹配,否则 {@code QXCL3015040.01.0} 会误删 {@code QXCL3015040.01.0-MP*} 毛坯图。
*/
public final class PlmDrawingFileNames {
private static final Pattern TIMESTAMP_SUFFIX = Pattern.compile("-\\d{14,}$");
private PlmDrawingFileNames() {
}
/**
* @param fileName 文件名或带路径的文件名
* @param materialCode 物料编码/图号
* @param separator 图号与名称分隔符,默认 {@code _}
* @param extension 不含点或含点,如 {@code pdf} / {@code .dwg}
*/
public static boolean matchesMaterialCode(String fileName, String materialCode,
String separator, String extension) {
if (fileName == null || materialCode == null) {
return false;
}
String code = materialCode.trim();
if (code.isEmpty()) {
return false;
}
String name = fileNameOnly(fileName);
String ext = normalizeExtension(extension);
if (ext.isEmpty() || !name.toLowerCase(Locale.ROOT).endsWith(ext)) {
return false;
}
String base = name.substring(0, name.length() - ext.length());
base = TIMESTAMP_SUFFIX.matcher(base).replaceFirst("");
if (base.equalsIgnoreCase(code)) {
return true;
}
String sep = (separator == null || separator.isEmpty()) ? "_" : separator;
String prefix = code + sep;
return base.regionMatches(true, 0, prefix, 0, prefix.length());
}
private static String fileNameOnly(String fileName) {
String s = fileName.trim();
int slash = Math.max(s.lastIndexOf('/'), s.lastIndexOf('\\'));
return slash >= 0 ? s.substring(slash + 1) : s;
}
private static String normalizeExtension(String extension) {
if (extension == null) {
return "";
}
String e = extension.trim().toLowerCase(Locale.ROOT);
if (e.isEmpty()) {
return "";
}
return e.startsWith(".") ? e : ("." + e);
}
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.system.constant;
/**
* 可以勾选是否创建钉钉待办的业务方法。
*/
public final class DingTodoSceneCodes {
private DingTodoSceneCodes() {
}
/** 出图完成后,工艺计划表发给工艺接收人 */
public static final String DRAWING_PLAN_EXCEL = "DRAWING_PLAN_EXCEL";
/** 手动上传工艺计划表后,发给生管 */
public static final String ROUTE_UPLOAD_PLAN = "ROUTE_UPLOAD_PLAN";
/** PLM 总装与工艺都到齐后,计划表发给生管 */
public static final String PLM_PLAN_COMPLETE = "PLM_PLAN_COMPLETE";
/** 接收人留空时,用 dingtalk.notify-user-id */
public static final String USER_SOURCE_NOTIFY = "NOTIFY";
/** 接收人留空时,用 dingtalk.plan-user-id */
public static final String USER_SOURCE_PLAN = "PLAN";
}

View File

@ -0,0 +1,75 @@
package com.ruoyi.system.constant;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.ProcessOrderPro;
import java.util.Locale;
/**
* 项目令号出图模式 {@code process_order_pro.drawing_mode}。
* 执行出图 / 添加产品前必须显式选择,不允许为空。
*/
public final class DrawingMode {
/** 正常:每个产品一行一个 ProductInfo,不按图纸路径合并 */
public static final String NORMAL = "normal";
/** 零部件:同一图纸路径下多个子件合成一个 ProductInfo */
public static final String PART = "part";
private DrawingMode() {
}
public static boolean isValid(String mode) {
if (StringUtils.isBlank(mode)) {
return false;
}
String m = mode.trim().toLowerCase(Locale.ROOT);
return NORMAL.equals(m) || PART.equals(m);
}
public static String normalize(String mode) {
if (StringUtils.isBlank(mode)) {
return null;
}
return mode.trim().toLowerCase(Locale.ROOT);
}
/**
* 出图模式不能为空,必须为 normal / part。
*/
public static void requireConfigured(ProcessOrderPro order) {
if (order == null) {
throw new IllegalArgumentException("项目不存在");
}
requireConfigured(order.getDrawingMode());
}
/**
* 出图模式不能为空,必须为 normal / part。
*/
public static void requireConfigured(String drawingMode) {
String mode = normalize(drawingMode);
if (!isValid(mode)) {
throw new IllegalArgumentException("出图模式不能为空,请先选择出图模式(正常/零部件)");
}
}
/**
* 是否零部件出图模式。仅认显式 {@link #PART};未设置视为未配置(由 {@link #requireConfigured} 拦截)。
*/
public static boolean isPartMode(ProcessOrderPro order) {
if (order == null) {
return false;
}
return PART.equals(normalize(order.getDrawingMode()));
}
/** 图纸路径比较用:去空白、统一斜杠 */
public static String normalizeDrawPath(String drawPath) {
if (StringUtils.isBlank(drawPath)) {
return "";
}
return drawPath.trim().replace('\\', '/');
}
}

View File

@ -0,0 +1,114 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bo.DesignTaskDesignDimBo;
import com.ruoyi.system.domain.bo.DesignTaskQueryBo;
import com.ruoyi.system.domain.bo.DesignTaskReportBo;
import com.ruoyi.system.domain.vo.DesignAccountSyncVo;
import com.ruoyi.system.domain.vo.DesignTaskBoardVo;
import com.ruoyi.system.domain.vo.DesignTaskReportVo;
import com.ruoyi.system.domain.vo.DesignTaskSummaryVo;
import com.ruoyi.system.domain.vo.DesignTaskVo;
import com.ruoyi.system.service.IDesignTaskService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 设计个人工作台
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/designTask")
public class DesignTaskController extends BaseController {
private final IDesignTaskService designTaskService;
@SaCheckPermission("system:designTask:list")
@GetMapping("/summary")
public R<DesignTaskSummaryVo> summary() {
return R.ok(designTaskService.summaryMine());
}
@SaCheckPermission("system:designTask:list")
@GetMapping("/board")
public R<DesignTaskBoardVo> board(String scope) {
return R.ok(designTaskService.boardMine(scope));
}
@SaCheckPermission("system:designTask:list")
@GetMapping("/mine")
public TableDataInfo<DesignTaskVo> mine(DesignTaskQueryBo bo, PageQuery pageQuery) {
return designTaskService.queryMine(bo, pageQuery);
}
@SaCheckPermission("system:designTask:report")
@Log(title = "设计报工", businessType = BusinessType.INSERT)
@RepeatSubmit
@PostMapping("/report")
public R<Long> report(@Validated @RequestBody DesignTaskReportBo bo) {
return R.ok(designTaskService.report(bo));
}
@SaCheckPermission("system:designTask:report")
@Log(title = "接收设计任务", businessType = BusinessType.UPDATE)
@RepeatSubmit
@PostMapping("/accept/{taskId}")
public R<Void> accept(@NotNull @PathVariable Long taskId) {
designTaskService.accept(taskId);
return R.ok();
}
@SaCheckPermission("system:designTask:report")
@Log(title = "标注设计类型", businessType = BusinessType.UPDATE)
@PutMapping("/designDim")
public R<Void> designDim(@Validated @RequestBody DesignTaskDesignDimBo bo) {
designTaskService.updateDesignDim(bo);
return R.ok();
}
@SaCheckPermission("system:designTask:list")
@GetMapping("/reports/{taskId}")
public R<List<DesignTaskReportVo>> reports(@NotNull @PathVariable Long taskId) {
return R.ok(designTaskService.listMyReports(taskId));
}
@SaCheckPermission("system:designTask:report")
@Log(title = "删除设计报工", businessType = BusinessType.DELETE)
@DeleteMapping("/reports/{reportId}")
public R<Void> deleteReport(@NotNull @PathVariable Long reportId) {
designTaskService.deleteReport(reportId);
return R.ok();
}
@SaCheckPermission("system:orderPro:list")
@GetMapping("/byOrder/{orderId}")
public R<List<DesignTaskVo>> byOrder(@NotNull @PathVariable Long orderId) {
return R.ok(designTaskService.listByOrder(orderId));
}
@SaCheckPermission("system:designTask:sync")
@Log(title = "同步设计账号", businessType = BusinessType.OTHER)
@PostMapping("/syncAccounts")
public R<DesignAccountSyncVo> syncAccounts() {
return R.ok(designTaskService.syncAccounts());
}
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.http.HttpRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 北通道实验台设备状态
*/
@Slf4j
@RestController
@RequestMapping("/open/devstat")
public class DevStatController {
private static final String STAT_URL = "http://192.168.5.25:9001/devstat/stat";
private final ObjectMapper objectMapper = new ObjectMapper();
@SaIgnore
@GetMapping("/stat")
public R<Object> stat() {
try {
String body = HttpRequest.get(STAT_URL).timeout(4000).execute().body();
return R.ok(objectMapper.readValue(body, Object.class));
} catch (Exception e) {
log.warn("北通道实验台状态获取失败: {}", e.getMessage());
return R.fail("实验台状态接口不可达");
}
}
}

View File

@ -0,0 +1,53 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.DingTodoLog;
import com.ruoyi.system.domain.DingTodoScene;
import com.ruoyi.system.service.IDingTodoDispatchService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 选择哪些方法创建钉钉待办,并查看发送记录。
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/dingTodo")
public class DingTodoController extends BaseController {
private final IDingTodoDispatchService dingTodoDispatchService;
@SaCheckPermission("system:dingTodo:list")
@GetMapping("/scenes")
public R<List<DingTodoScene>> scenes() {
return R.ok(dingTodoDispatchService.listScenes());
}
@SaCheckPermission("system:dingTodo:edit")
@Log(title = "钉钉待办方法", businessType = BusinessType.UPDATE)
@PutMapping("/scenes")
public R<Void> updateScene(@RequestBody DingTodoScene scene) {
dingTodoDispatchService.updateScene(scene);
return R.ok();
}
@SaCheckPermission("system:dingTodo:list")
@GetMapping("/logs")
public TableDataInfo<DingTodoLog> logs(DingTodoLog query, PageQuery pageQuery) {
return dingTodoDispatchService.queryLogs(query, pageQuery);
}
}

View File

@ -0,0 +1,57 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bo.DrawingRouteOperLogBo;
import com.ruoyi.system.domain.vo.DrawingRouteOperLogVo;
import com.ruoyi.system.service.DrawingRouteOperLogRenotifyService;
import com.ruoyi.system.service.IDrawingRouteOperLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull;
/**
* 出图 / 工艺上传 / 钉钉推送留痕
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/drawingRouteOperLog")
public class DrawingRouteOperLogController extends BaseController {
private final IDrawingRouteOperLogService drawingRouteOperLogService;
private final DrawingRouteOperLogRenotifyService drawingRouteOperLogRenotifyService;
@SaCheckPermission("system:drawingRouteOperLog:list")
@GetMapping("/list")
public TableDataInfo<DrawingRouteOperLogVo> list(DrawingRouteOperLogBo bo, PageQuery pageQuery) {
return drawingRouteOperLogService.queryPageList(bo, pageQuery);
}
@SaCheckPermission("system:drawingRouteOperLog:query")
@GetMapping("/{id}")
public R<DrawingRouteOperLogVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(drawingRouteOperLogService.queryDetail(id));
}
/**
* 按这条日志重发对应钉钉通知。出图执行、工艺上传不能重发。
*/
@SaCheckPermission("system:drawingRouteOperLog:query")
@Log(title = "重新通知", businessType = BusinessType.OTHER)
@PostMapping("/{id}/renotify")
public R<Void> renotify(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(drawingRouteOperLogRenotifyService.renotify(id));
}
}

View File

@ -0,0 +1,112 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.vo.mes.MesDrawingListVo;
import com.ruoyi.system.service.IMesDrawingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* MES 图纸开放接口:按生产令号查询路径与下载地址,并按图号下载 PDF / DWG。
* <p>
* 鉴权:请求头 {@code X-App-Key} / {@code X-App-Secret},或 query 参数 {@code appKey} / {@code appSecret}。
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/open/mes/drawing")
public class MesDrawingController extends BaseController {
private final IMesDrawingService mesDrawingService;
/**
* 按生产令号列出图纸。可选 materialCode 只返回该图号。
*/
@SaIgnore
@GetMapping("/list")
public R<MesDrawingListVo> list(
@RequestHeader(value = "X-App-Key", required = false) String headerAppKey,
@RequestHeader(value = "X-App-Secret", required = false) String headerAppSecret,
@RequestParam(value = "appKey", required = false) String appKey,
@RequestParam(value = "appSecret", required = false) String appSecret,
@RequestParam("productionOrderNo") String productionOrderNo,
@RequestParam(value = "materialCode", required = false) String materialCode) {
mesDrawingService.assertAuth(firstNonBlank(headerAppKey, appKey), firstNonBlank(headerAppSecret, appSecret));
return R.ok(mesDrawingService.listDrawings(productionOrderNo, materialCode));
}
/**
* 按生产令号 + 物料编码下载图纸。fileType:pdf(默认)或 dwg。
*/
@SaIgnore
@GetMapping("/download")
public void download(
@RequestHeader(value = "X-App-Key", required = false) String headerAppKey,
@RequestHeader(value = "X-App-Secret", required = false) String headerAppSecret,
@RequestParam(value = "appKey", required = false) String appKey,
@RequestParam(value = "appSecret", required = false) String appSecret,
@RequestParam("productionOrderNo") String productionOrderNo,
@RequestParam("materialCode") String materialCode,
@RequestParam(value = "fileType", required = false) String fileType,
HttpServletResponse response) {
try {
mesDrawingService.assertAuth(firstNonBlank(headerAppKey, appKey), firstNonBlank(headerAppSecret, appSecret));
File file = mesDrawingService.resolveDownloadFile(productionOrderNo, materialCode, fileType);
response.setContentType("application/octet-stream");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
response.setContentLengthLong(file.length());
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int length;
while ((length = fis.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
os.flush();
}
} catch (ServiceException e) {
writeJsonFail(response, e.getMessage());
} catch (Exception e) {
log.error("MES下载图纸失败 productionOrderNo={} materialCode={}", productionOrderNo, materialCode, e);
writeJsonFail(response, "下载失败: " + e.getMessage());
}
}
private static String firstNonBlank(String a, String b) {
return StringUtils.isNotBlank(a) ? a.trim() : StringUtils.trimToEmpty(b);
}
private void writeJsonFail(HttpServletResponse response, String msg) {
try {
if (!response.isCommitted()) {
response.reset();
}
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(JSONObject.toJSONString(R.fail(msg)));
} catch (IOException ex) {
log.error("写入MES图纸错误响应失败", ex);
}
}
}

View File

@ -0,0 +1,77 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.bo.OrderProProgressReportBo;
import com.ruoyi.system.domain.vo.OrderProProgressDesignerSummaryVo;
import com.ruoyi.system.domain.vo.OrderProProgressReportExportVo;
import com.ruoyi.system.domain.vo.OrderProProgressReportOverviewVo;
import com.ruoyi.system.domain.vo.OrderProProgressReportVo;
import com.ruoyi.system.service.IDrawingWorkloadService;
import com.ruoyi.system.service.IOrderProProgressReportService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 生产项目进度报表
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/orderProProgress")
public class OrderProProgressReportController extends BaseController {
private final IOrderProProgressReportService orderProProgressReportService;
private final IDrawingWorkloadService drawingWorkloadService;
@SaCheckPermission("system:orderProProgress:list")
@GetMapping("/overview")
public R<OrderProProgressReportOverviewVo> overview(OrderProProgressReportBo bo) {
return R.ok(orderProProgressReportService.queryOverview(bo));
}
@SaCheckPermission("system:orderProProgress:list")
@GetMapping("/designerSummary")
public R<List<OrderProProgressDesignerSummaryVo>> designerSummary(OrderProProgressReportBo bo) {
return R.ok(orderProProgressReportService.queryDesignerSummary(bo));
}
@SaCheckPermission("system:orderProProgress:list")
@GetMapping("/list")
public TableDataInfo<OrderProProgressReportVo> list(OrderProProgressReportBo bo, PageQuery pageQuery) {
return orderProProgressReportService.queryPageList(bo, pageQuery);
}
@SaCheckPermission("system:orderProProgress:list")
@Log(title = "读取图纸工作量", businessType = BusinessType.OTHER)
@PostMapping("/syncWorkload")
public R<Map<String, Object>> syncWorkload(@RequestBody List<Long> ids) {
return R.ok(drawingWorkloadService.syncFromFtpBatch(ids));
}
@SaCheckPermission("system:orderProProgress:export")
@Log(title = "生产项目进度报表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(OrderProProgressReportBo bo, HttpServletResponse response) {
List<OrderProProgressReportExportVo> exportList = orderProProgressReportService.queryList(bo).stream()
.map(OrderProProgressReportExportVo::from)
.collect(Collectors.toList());
ExcelUtil.exportExcel(exportList, "生产项目进度报表", OrderProProgressReportExportVo.class, response);
}
}

View File

@ -314,6 +314,12 @@ public class ProcessOrderProController extends BaseController {
String s = iProcessOrderProService.executDrawing(orderPro); String s = iProcessOrderProService.executDrawing(orderPro);
return R.ok(s); return R.ok(s);
} }
@SaCheckPermission("system:orderPro:query")
@GetMapping("/drawingState/{id}")
public R<ProcessOrderProVo> captureDrawingState(@PathVariable Long id) {
return R.ok(iProcessOrderProService.captureCurrentDrawingState(id));
}
@GetMapping("/getstate") @GetMapping("/getstate")
public R<JSONObject> getPlotState() { public R<JSONObject> getPlotState() {
String result = HttpUtils.sendGet("http://192.168.14.218:9000/getstate", "UTF-8"); String result = HttpUtils.sendGet("http://192.168.14.218:9000/getstate", "UTF-8");
@ -2002,6 +2008,7 @@ public class ProcessOrderProController extends BaseController {
*/ */
@SaCheckPermission("system:orderPro:sendProcessRoutePlanExcel") @SaCheckPermission("system:orderPro:sendProcessRoutePlanExcel")
@Log(title = "发送工艺计划表", businessType = BusinessType.OTHER) @Log(title = "发送工艺计划表", businessType = BusinessType.OTHER)
@RepeatSubmit
@PostMapping("/sendProcessRoutePlanExcel/{id}") @PostMapping("/sendProcessRoutePlanExcel/{id}")
public R<Void> sendProcessRoutePlanExcel(@NotNull(message = "项目ID不能为空") @PathVariable Long id) { public R<Void> sendProcessRoutePlanExcel(@NotNull(message = "项目ID不能为空") @PathVariable Long id) {
return iProcessOrderProService.sendProcessRoutePlanExcel(id); return iProcessOrderProService.sendProcessRoutePlanExcel(id);

View File

@ -499,7 +499,7 @@ public class ProcessRouteController extends BaseController {
} }
String todoProjectName = orderForLock == null ? null : orderForLock.getProductionName(); String todoProjectName = orderForLock == null ? null : orderForLock.getProductionName();
Date todoProjectEndTime = orderForLock == null ? null : orderForLock.getProjectEndTime(); Date todoRouteUptime = orderForLock == null ? null : orderForLock.getRouteUptime();
String planUserIds = dingTalkProperties.getPlanUserId(); String planUserIds = dingTalkProperties.getPlanUserId();
// 异步:按 plan-user-id 发文件 + 发工艺审核卡片(落盘文件保留) // 异步:按 plan-user-id 发文件 + 发工艺审核卡片(落盘文件保留)
String finalNickName = currentUserNickName; String finalNickName = currentUserNickName;
@ -516,7 +516,7 @@ public class ProcessRouteController extends BaseController {
DingTodoSceneCodes.ROUTE_UPLOAD_PLAN, DingTodoSceneCodes.ROUTE_UPLOAD_PLAN,
productionOrderNo, productionOrderNo,
todoProjectName, todoProjectName,
todoProjectEndTime, todoRouteUptime,
uploadFile, uploadFile,
planUserIds); planUserIds);
if (StringUtils.isNotBlank(todoError) && filePush != null) { if (StringUtils.isNotBlank(todoError) && filePush != null) {

View File

@ -0,0 +1,132 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bo.VmiMaterialBo;
import com.ruoyi.system.domain.bo.VmiPurchaseReqLogBo;
import com.ruoyi.system.domain.bo.VmiReplenishConfirmBo;
import com.ruoyi.system.domain.bo.VmiReplenishPreviewBo;
import com.ruoyi.system.domain.bo.VmiStockAlertLogBo;
import com.ruoyi.system.domain.vo.VmiMaterialVo;
import com.ruoyi.system.domain.vo.VmiPurchaseReqLogVo;
import com.ruoyi.system.domain.vo.VmiReplenishPreviewItemVo;
import com.ruoyi.system.domain.vo.VmiStockAlertLogVo;
import com.ruoyi.system.service.IVmiMaterialService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
/**
* VMI物料安全库存
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/vmiMaterial")
public class VmiMaterialController extends BaseController {
private final IVmiMaterialService vmiMaterialService;
@SaCheckPermission("system:vmiMaterial:list")
@GetMapping("/list")
public TableDataInfo<VmiMaterialVo> list(VmiMaterialBo bo, PageQuery pageQuery) {
return vmiMaterialService.queryPageList(bo, pageQuery);
}
@SaCheckPermission("system:vmiMaterial:query")
@GetMapping("/{id}")
public R<VmiMaterialVo> getInfo(@NotNull @PathVariable Long id) {
return R.ok(vmiMaterialService.queryById(id));
}
@SaCheckPermission("system:vmiMaterial:add")
@Log(title = "VMI物料", businessType = BusinessType.INSERT)
@RepeatSubmit
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody VmiMaterialBo bo) {
return toAjax(vmiMaterialService.insertByBo(bo));
}
@SaCheckPermission("system:vmiMaterial:edit")
@Log(title = "VMI物料", businessType = BusinessType.UPDATE)
@RepeatSubmit
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody VmiMaterialBo bo) {
return toAjax(vmiMaterialService.updateByBo(bo));
}
@SaCheckPermission("system:vmiMaterial:remove")
@Log(title = "VMI物料", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty @PathVariable Long[] ids) {
return toAjax(vmiMaterialService.deleteWithValidByIds(Arrays.asList(ids)));
}
@SaCheckPermission("system:vmiMaterial:edit")
@Log(title = "VMI低库存扫描", businessType = BusinessType.OTHER)
@PostMapping("/scan")
public R<Integer> scan() {
return R.ok(vmiMaterialService.scanLowStock());
}
@SaCheckPermission("system:vmiMaterial:edit")
@PostMapping("/confirm/preview")
public R<List<VmiReplenishPreviewItemVo>> confirmPreview(@RequestBody VmiReplenishPreviewBo bo) {
return R.ok(vmiMaterialService.previewReplenish(bo));
}
@SaCheckPermission("system:vmiMaterial:edit")
@Log(title = "VMI补货确认", businessType = BusinessType.OTHER)
@PostMapping("/confirm")
public R<Integer> confirm(@RequestBody VmiReplenishConfirmBo bo) {
return R.ok(vmiMaterialService.confirmReplenish(bo));
}
@SaCheckPermission("system:vmiMaterial:edit")
@Log(title = "VMI忽略补货", businessType = BusinessType.OTHER)
@PostMapping("/ignore/{alertId}")
public R<Void> ignore(@NotNull @PathVariable Long alertId) {
vmiMaterialService.ignoreAlert(alertId);
return R.ok();
}
@SaCheckPermission("system:vmiMaterial:edit")
@Log(title = "VMI需求预测通知", businessType = BusinessType.OTHER)
@PostMapping("/forecast/notify/{id}")
public R<Void> notifyForecast(@NotNull @PathVariable Long id) {
vmiMaterialService.notifyForecast(id);
return R.ok();
}
@SaCheckPermission("system:vmiMaterial:list")
@GetMapping("/alert/list")
public TableDataInfo<VmiStockAlertLogVo> alertList(VmiStockAlertLogBo bo, PageQuery pageQuery) {
return vmiMaterialService.queryAlertPage(bo, pageQuery);
}
@SaCheckPermission("system:vmiMaterial:list")
@GetMapping("/req/list")
public TableDataInfo<VmiPurchaseReqLogVo> reqList(VmiPurchaseReqLogBo bo, PageQuery pageQuery) {
return vmiMaterialService.queryReqPage(bo, pageQuery);
}
}

View File

@ -0,0 +1,105 @@
package com.ruoyi.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bo.WorkAlertGroupPushBo;
import com.ruoyi.system.domain.bo.WorkAlertGroupPushConfigBo;
import com.ruoyi.system.domain.vo.WorkAlertGroupPushConfigVo;
import com.ruoyi.system.service.IWorkAlertGroupPushConfigService;
import com.ruoyi.system.service.WorkAlertPushScheduleService;
import com.ruoyi.system.service.support.WorkAlertPushTypeRegistry;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* 指定钉钉群会话推送预警数据(与预警推送配置同一套任务数据)
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/workAlertGroupPush")
public class WorkAlertGroupPushController extends BaseController {
private final WorkAlertPushScheduleService workAlertPushScheduleService;
private final IWorkAlertGroupPushConfigService workAlertGroupPushConfigService;
@SaCheckPermission("system:workAlertGroupPush:list")
@GetMapping("/pushTypes")
public R<List<Map<String, String>>> pushTypes() {
List<Map<String, String>> list = new ArrayList<>();
for (WorkAlertPushTypeRegistry.PushType type : WorkAlertPushTypeRegistry.groupConversationTypes()) {
list.add(type.toOption());
}
return R.ok(list);
}
@SaCheckPermission("system:workAlertGroupPush:send")
@Log(title = "指定群预警推送", businessType = BusinessType.OTHER)
@PostMapping("/send")
public R<Void> send(@Validated @RequestBody WorkAlertGroupPushBo bo) {
workAlertPushScheduleService.executeGroupPush(
bo.getPushCode(), bo.getOpenConversationId(), bo.getRobotCode());
return R.ok();
}
@SaCheckPermission("system:workAlertGroupPush:list")
@GetMapping("/schedule/list")
public R<List<WorkAlertGroupPushConfigVo>> scheduleList() {
return R.ok(workAlertGroupPushConfigService.queryList());
}
@SaCheckPermission("system:workAlertGroupPush:list")
@GetMapping("/schedule/{id}")
public R<WorkAlertGroupPushConfigVo> scheduleInfo(@NotNull @PathVariable Long id) {
return R.ok(workAlertGroupPushConfigService.queryById(id));
}
@SaCheckPermission("system:workAlertGroupPush:send")
@Log(title = "群预警定时配置", businessType = BusinessType.INSERT)
@PostMapping("/schedule")
public R<Void> addSchedule(@Validated(AddGroup.class) @RequestBody WorkAlertGroupPushConfigBo bo) {
return toAjax(workAlertGroupPushConfigService.insertByBo(bo));
}
@SaCheckPermission("system:workAlertGroupPush:send")
@Log(title = "群预警定时配置", businessType = BusinessType.UPDATE)
@PutMapping("/schedule")
public R<Void> editSchedule(@Validated(EditGroup.class) @RequestBody WorkAlertGroupPushConfigBo bo) {
return toAjax(workAlertGroupPushConfigService.updateByBo(bo));
}
@SaCheckPermission("system:workAlertGroupPush:send")
@Log(title = "群预警定时配置", businessType = BusinessType.DELETE)
@DeleteMapping("/schedule/{ids}")
public R<Void> removeSchedule(@NotEmpty @PathVariable Long[] ids) {
return toAjax(workAlertGroupPushConfigService.deleteWithValidByIds(Arrays.asList(ids)));
}
@SaCheckPermission("system:workAlertGroupPush:send")
@Log(title = "群预警定时配置", businessType = BusinessType.OTHER)
@PostMapping("/schedule/trigger/{id}")
public R<Void> triggerSchedule(@NotNull @PathVariable Long id) {
workAlertGroupPushConfigService.trigger(id);
return R.ok();
}
}

View File

@ -0,0 +1,54 @@
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 lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 已经发起的钉钉待办 ding_todo_log
*/
@Data
@TableName("ding_todo_log")
public class DingTodoLog implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String sceneCode;
private String sceneName;
private String productionOrderNo;
private String projectName;
private String title;
private String content;
private String userIds;
private String createUser;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date dueTime;
private String fileName;
/** SUCCESS 或 FAIL */
private String status;
private String errorMsg;
private String responseBody;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@ -0,0 +1,54 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 哪些业务方法要创建钉钉待办 ding_todo_scene
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("ding_todo_scene")
public class DingTodoScene extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 方法编码,与代码里的调用点对应 */
private String sceneCode;
/** 页面上看到的方法名称 */
private String sceneName;
/** 0 不发待办,1 发送待办 */
private String enabled;
/**
* 接收人。类型为 name 时填姓名,多个用逗号,例如田志阳;类型为 code 时填考勤工号。
* 类型为 code 且这里为空时,用 notify-user-id 或 plan-user-id。
*/
private String userIds;
/** NOTIFY 或 PLAN,仅 recipientType=code 且接收人为空时使用 */
private String userSource;
/** code=考勤工号,name=姓名 */
private String recipientType;
/** 创建人,类型与接收人一致。空则用接收人里的第一个 */
private String createUser;
/** 没有未来的项目结束时间时,截止为当前时间加该天数 */
private Integer dueDays;
/** 待办正文第一句 */
private String contentLead;
private String remark;
}

View File

@ -0,0 +1,59 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 出图 / 工艺上传 / 钉钉推送业务留痕。
*/
@Data
@TableName("drawing_route_oper_log")
public class DrawingRouteOperLog {
@TableId(type = IdType.AUTO)
private Long id;
/** DRAWING_EXEC / DRAWING_NOTIFY / ROUTE_UPLOAD / ROUTE_NOTIFY / SKIP_PROCESS_NOTIFY / ELECTRICAL_BOM_NOTIFY */
private String bizType;
/** MANUAL / PLM */
private String channel;
private String apiPath;
private Long projectId;
private String productionOrderNo;
private String drawingMode;
/** MARKDOWN / FILE / CARD / MARKDOWN_AND_FILE / FILE_AND_CARD */
private String notifyType;
private String notifyUserIds;
private String messageTitle;
private String messageContent;
private String fileName;
/** SUCCESS / FAIL / PARTIAL */
private String status;
private String errorMsg;
private String summaryJson;
private String operator;
private String clientIp;
private Long durationMs;
private Date operTime;
}

View File

@ -0,0 +1,37 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* 设计报工明细
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("process_design_report")
public class ProcessDesignReport extends BaseEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private Long taskId;
private Date reportDate;
private BigDecimal workHours;
private Integer progress;
private String content;
private String reportBy;
private Date reportTime;
}

View File

@ -0,0 +1,57 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* 设计个人任务
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("process_design_task")
public class ProcessDesignTask extends BaseEntity {
public static final int STATUS_PENDING = 0;
public static final int STATUS_WORKING = 1;
public static final int STATUS_DONE = 2;
/** 进度达到 80 且未到 100 */
public static final int STATUS_DRAWING = 3;
/** 进度达到 100,仍可继续补报工时 */
public static final int STATUS_REVIEW = 4;
public static final int STATUS_REVOKED = 9;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private Long orderProId;
private String productionOrderNo;
/** 设计人登录名,等于字典 plan_proer 的键值 */
private String designerUserName;
/** 设计类型:2D 二维,3D 三维 */
private String designDim;
/** 0待接收 1设计中 2已完成 3设计出图中 4工艺待审核 9已撤回 */
private Integer status;
private Integer progress;
private BigDecimal totalHours;
private Date assignTime;
private Date startTime;
private Date finishTime;
private String remark;
}

View File

@ -45,6 +45,22 @@ public class ProcessOrderPro extends BaseEntity {
* 出图模式:{@link com.ruoyi.system.constant.DrawingMode#NORMAL} / {@link com.ruoyi.system.constant.DrawingMode#PART};执行出图前必填 * 出图模式:{@link com.ruoyi.system.constant.DrawingMode#NORMAL} / {@link com.ruoyi.system.constant.DrawingMode#PART};执行出图前必填
*/ */
private String drawingMode; private String drawingMode;
/**
* 最近一次出图 processstate:4 完成,5 失败
*/
private Integer drawingState;
/**
* 最近一次出图提示,例如「图号唯一性检查失败!」
*/
private String drawingStateMsg;
/**
* 最近一次出图输出路径
*/
private String drawingStatePath;
/**
* 最近一次出图状态写入时间
*/
private Date drawingStateTime;
/** /**
* 是否做工艺:0否 1是 * 是否做工艺:0否 1是
*/ */

View File

@ -0,0 +1,25 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("ratio_model")
public class RatioModel extends BaseEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 所属速比 */
private Long ratioConfigId;
/** 减速机型号 */
private String modelCode;
private Integer status;
}

View File

@ -0,0 +1,77 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* VMI物料安全库存对象 vmi_material
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("vmi_material")
public class VmiMaterial extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键 */
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 物料编码 */
private String materialCode;
/** 名称 */
private String materialName;
/** 材料 */
private String materialQuality;
/** 供应商编码 */
private String supplierCode;
/** 供应商 */
private String supplierName;
/** 最小包装量 */
private BigDecimal minPackQty;
/** 建议最高安全库存 */
private BigDecimal maxSafeQty;
/** 建议最低安全库存 */
private BigDecimal minSafeQty;
/** 仓储部VMI负责人钉钉userId,多个逗号分隔 */
private String dingUserId;
/** 需求预测数量 */
private BigDecimal forecastQty;
/** 需求预测说明(旺季、订单波动等) */
private String forecastRemark;
/** 即时库存(最近一次扫描) */
private BigDecimal lastStockQty;
/** 最近一次扫描的预计可用库存 */
private BigDecimal lastAvailableQty;
/** 最近一次扫描时间 */
private Date lastCheckTime;
/** 库存状态:NORMAL正常 / LOW低于安全库存 */
private String stockStatus;
/** 是否启用:0停用 1启用 */
private String enabled;
/** 备注 */
private String remark;
}

View File

@ -0,0 +1,55 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
* VMI采购申请推送记录对象 vmi_purchase_req_log
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("vmi_purchase_req_log")
public class VmiPurchaseReqLog extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键 */
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 对应的低库存触发记录ID */
private Long alertLogId;
/** VMI物料主键 */
private Long materialId;
/** 物料编码 */
private String materialCode;
/** 物料名称 */
private String materialName;
/** 供应商编码 */
private String supplierCode;
/** 采购申请数量 */
private BigDecimal reqQty;
/** 金蝶采购申请单号 */
private String kingdeeBillNo;
/** 金蝶采购申请FID */
private String kingdeeFid;
/** 推送结果:SUCCESS成功 / FAIL失败 */
private String pushStatus;
/** 金蝶返回或失败原因 */
private String pushMsg;
}

View File

@ -0,0 +1,62 @@
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.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* VMI低库存触发记录对象 vmi_stock_alert_log
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("vmi_stock_alert_log")
public class VmiStockAlertLog extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键 */
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** VMI物料主键 */
private Long materialId;
/** 物料编码 */
private String materialCode;
/** 物料名称 */
private String materialName;
/** 触发时的最低安全库存 */
private BigDecimal minSafeQty;
/** 触发时的即时库存 */
private BigDecimal currentQty;
/** 触发时的预计可用库存 */
private BigDecimal availableQty;
/** 处理状态:PENDING待确认 / CONFIRMED已确认 / IGNORED已忽略 */
private String alertStatus;
/** 钉钉推送结果:SUCCESS成功 / FAIL失败 */
private String dingPushStatus;
/** 钉钉推送返回信息 */
private String dingPushMsg;
/** 确认人或忽略人 */
private String confirmBy;
/** 确认或忽略时间 */
private Date confirmTime;
/** 备注 */
private String remark;
}

View File

@ -0,0 +1,50 @@
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;
/**
* 指定群预警定时推送配置 work_alert_group_push_config
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("work_alert_group_push_config")
public class WorkAlertGroupPushConfig extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 配置名称 */
private String configName;
/** 群会话 ID */
private String openConversationId;
/** 机器人 Code */
private String robotCode;
/** 推送任务编码,逗号分隔 */
private String pushCodes;
/** 每日推送时间 HH:mm */
private String pushTime;
/** 是否启用 0否 1是 */
private String enabled;
/** 上次推送日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date lastPushDate;
/** 备注 */
private String remark;
}

View File

@ -0,0 +1,19 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 标注任务的二维 / 三维
*/
@Data
public class DesignTaskDesignDimBo {
@NotNull(message = "任务不能为空")
private Long taskId;
@NotBlank(message = "请选择二维或三维")
private String designDim;
}

View File

@ -0,0 +1,30 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
/**
* 我的设计任务查询
*/
@Data
public class DesignTaskQueryBo {
/** 0待接收 1设计中 2已完成 3设计出图中 4工艺待审核 9已撤回。空则不含已撤回 */
private Integer status;
private String productionOrderNo;
/** true 只看逾期,false 只看未逾期 */
private Boolean overdue;
/** mine 我的,all 全部,pending 待我接收 */
private String scope;
/** 生产令号、项目名称、设计师 */
private String keyword;
/** 2D、3D,空为全部;none 为未标注 */
private String designDim;
/** overdue 已逾期,today 今天到期,upcoming 未逾期 */
private String due;
}

View File

@ -0,0 +1,33 @@
package com.ruoyi.system.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.Date;
/**
* 设计报工
*/
@Data
public class DesignTaskReportBo {
@NotNull(message = "任务不能为空")
private Long taskId;
@NotNull(message = "请填写本次工时")
private BigDecimal workHours;
@NotNull(message = "请填写当前进度")
private Integer progress;
@NotBlank(message = "请填写工作说明")
@Size(max = 1000, message = "工作说明不能超过1000个字符")
private String content;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date reportDate;
}

View File

@ -0,0 +1,23 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class DrawingRouteOperLogBo extends BaseEntity {
private Long id;
/** DRAWING_EXEC / DRAWING_NOTIFY / ROUTE_UPLOAD / ROUTE_NOTIFY */
private String bizType;
/** MANUAL / PLM */
private String channel;
private String productionOrderNo;
/** SUCCESS / FAIL / PARTIAL */
private String status;
}

View File

@ -0,0 +1,23 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 批量设置项目状态入参。
* 不走整单编辑校验(设计人等),只改 bomStatus。
*/
@Data
public class OrderProBomStatusBo {
/** 选中的项目主键 */
@NotEmpty(message = "项目不能为空")
private List<Long> ids;
/** 项目状态(字典 process_status) */
@NotNull(message = "项目状态不能为空")
private Long bomStatus;
}

View File

@ -0,0 +1,34 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 生产项目进度报表查询。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class OrderProProgressReportBo extends BaseEntity {
private String productionOrderNo;
private String productionName;
/** 设计人(字典值,模糊匹配) */
private String drawingBy;
/** 仅查询未填写设计人的项目 */
private Boolean unassignedDesigner;
/** 手动 / PLM */
private String source;
private String isEnterpriseStandard;
private List<String> isEnterpriseStandardList;
private String createBy;
}

View File

@ -0,0 +1,67 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* VMI物料安全库存业务对象
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class VmiMaterialBo extends BaseEntity {
/** 主键 */
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/** 物料编码 */
@NotBlank(message = "物料编码不能为空", groups = { AddGroup.class, EditGroup.class })
private String materialCode;
/** 名称 */
private String materialName;
/** 材料 */
private String materialQuality;
/** 供应商编码 */
private String supplierCode;
/** 供应商 */
private String supplierName;
/** 最小包装量 */
private BigDecimal minPackQty;
/** 建议最高安全库存 */
private BigDecimal maxSafeQty;
/** 建议最低安全库存 */
@NotNull(message = "建议最低安全库存不能为空", groups = { AddGroup.class, EditGroup.class })
private BigDecimal minSafeQty;
/** 仓储部VMI负责人钉钉userId,多个逗号分隔 */
private String dingUserId;
/** 需求预测数量 */
private BigDecimal forecastQty;
/** 需求预测说明(旺季、订单波动等) */
private String forecastRemark;
/** 是否启用:0停用 1启用 */
private String enabled;
/** 备注 */
private String remark;
/** 库存状态:NORMAL正常 / LOW低于安全库存 */
private String stockStatus;
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* VMI采购申请推送记录查询对象
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class VmiPurchaseReqLogBo extends BaseEntity {
/** 主键 */
private Long id;
/** 物料编码 */
private String materialCode;
/** 推送结果:SUCCESS成功 / FAIL失败 */
private String pushStatus;
/** 金蝶采购申请单号 */
private String kingdeeBillNo;
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* VMI补货确认请求对象(支持单条或批量勾选)
*/
@Data
public class VmiReplenishConfirmBo {
/** 单条触发记录ID(兼容旧用法) */
private Long alertId;
/** 批量勾选的触发记录ID(兼容;优先使用 lines) */
private List<Long> alertIds;
/** 单条确认时的采购申请数量(兼容) */
private BigDecimal reqQty;
/** 带数量的明细行(预览窗口确认后提交) */
private List<VmiReplenishLineBo> lines;
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
import java.math.BigDecimal;
/**
* VMI补货确认明细行
*/
@Data
public class VmiReplenishLineBo {
/** 触发记录ID */
private Long alertId;
/** 采购申请数量(管理员可改) */
private BigDecimal reqQty;
/** 到货时间,格式 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss */
private String arrivalDate;
}

View File

@ -0,0 +1,16 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
import java.util.List;
/**
* VMI补货预览请求
*/
@Data
public class VmiReplenishPreviewBo {
private Long alertId;
private List<Long> alertIds;
}

View File

@ -0,0 +1,22 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* VMI低库存触发记录查询对象
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class VmiStockAlertLogBo extends BaseEntity {
/** 主键 */
private Long id;
/** 物料编码 */
private String materialCode;
/** 处理状态:PENDING待确认 / CONFIRMED已确认 / IGNORED已忽略 */
private String alertStatus;
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.system.domain.bo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 指定钉钉群会话推送预警数据
*/
@Data
public class WorkAlertGroupPushBo {
@NotBlank(message = "推送编码不能为空")
private String pushCode;
@NotBlank(message = "群会话ID不能为空")
private String openConversationId;
/** 机器人 Code,空则使用默认预警推送机器人 */
private String robotCode;
}

View File

@ -0,0 +1,41 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
/**
* 指定群预警定时推送配置业务对象
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WorkAlertGroupPushConfigBo extends BaseEntity {
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
private String configName;
@NotBlank(message = "群会话ID不能为空", groups = { AddGroup.class, EditGroup.class })
private String openConversationId;
private String robotCode;
@NotBlank(message = "请选择推送任务", groups = { AddGroup.class, EditGroup.class })
private String pushCodes;
@NotBlank(message = "推送时间不能为空", groups = { AddGroup.class, EditGroup.class })
@Pattern(regexp = "^([01]\\d|2[0-3]):[0-5]\\d$", message = "推送时间格式应为 HH:mm", groups = { AddGroup.class, EditGroup.class })
private String pushTime;
@NotBlank(message = "启用状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String enabled;
private String remark;
}

View File

@ -0,0 +1,98 @@
package com.ruoyi.system.domain.bo.plm;
import com.ruoyi.common.utils.StringUtils;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* PLM 总图号(图号/物料编码以 {@code .00} 结尾)识别与归属。
*/
public final class PlmGeneralDrawingUtils {
public static final String GENERAL_DRAWING_SUFFIX = ".00";
private PlmGeneralDrawingUtils() {
}
/** 工艺状态:1=已审核 */
public static boolean isRouteApproved(Long routeStatus) {
return routeStatus != null && routeStatus == 1L;
}
/** 是否总图号:trim 后以 {@code .00} 结尾。 */
public static boolean isGeneralDrawingNo(String code) {
String s = StringUtils.trimToEmpty(code);
return s.endsWith(GENERAL_DRAWING_SUFFIX);
}
/** {@code FS26062.00} → {@code FS26062};非总图号原样返回 trim 值。 */
public static String generalDrawingPrefix(String generalDrawingNo) {
String s = StringUtils.trimToEmpty(generalDrawingNo);
if (s.endsWith(GENERAL_DRAWING_SUFFIX)) {
return s.substring(0, s.length() - GENERAL_DRAWING_SUFFIX.length());
}
return s;
}
public static Set<String> collectGeneralDrawingNos(Collection<String> codes) {
Set<String> result = new LinkedHashSet<>();
if (codes == null) {
return result;
}
for (String code : codes) {
String s = StringUtils.trimToEmpty(code);
if (isGeneralDrawingNo(s)) {
result.add(s);
}
}
return result;
}
/**
* 物料是否归属某总图号:等于总图号,或以「去掉 .00 后的前缀 + .」开头。
*/
public static boolean belongsToGeneralDrawing(String materialCode, String generalDrawingNo) {
String code = StringUtils.trimToEmpty(materialCode);
String general = StringUtils.trimToEmpty(generalDrawingNo);
if (code.isEmpty() || general.isEmpty() || !isGeneralDrawingNo(general)) {
return false;
}
if (code.equals(general)) {
return true;
}
String prefix = generalDrawingPrefix(general);
return StringUtils.isNotBlank(prefix) && code.startsWith(prefix + ".");
}
/** 在候选总图号中取最长匹配(更具体的总图优先)。 */
public static String resolveOwnerGeneralDrawing(String materialCode, Collection<String> generalDrawings) {
String best = null;
if (generalDrawings == null) {
return null;
}
for (String general : generalDrawings) {
if (!belongsToGeneralDrawing(materialCode, general)) {
continue;
}
if (best == null || general.length() > best.length()) {
best = general;
}
}
return best;
}
public static Set<String> intersection(Set<String> left, Set<String> right) {
Set<String> overlap = new LinkedHashSet<>();
if (left == null || right == null || left.isEmpty() || right.isEmpty()) {
return overlap;
}
for (String item : left) {
if (right.contains(item)) {
overlap.add(item);
}
}
return overlap;
}
}

View File

@ -0,0 +1,18 @@
package com.ruoyi.system.domain.dto;
import lombok.Data;
import java.util.Date;
/**
* 进度报表时间聚合(按项目或令号)。
*/
@Data
public class OrderProProgressTimeAgg {
private Long id;
private String productionOrderNo;
private Date eventTime;
}

View File

@ -0,0 +1,40 @@
package com.ruoyi.system.domain.dto.ratio;
import lombok.Data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Data
public class RatioBomParseResult {
private List<SheetBom> sheets = new ArrayList<>();
private List<String> skippedSheets = new ArrayList<>();
@Data
public static class SheetBom {
private String sheetName;
private String ratioValue;
private List<LineBom> commonParts = new ArrayList<>();
private List<ModelBom> models = new ArrayList<>();
}
@Data
public static class ModelBom {
private String modelCode;
private List<LineBom> parts = new ArrayList<>();
}
@Data
public static class LineBom {
private String partCategory;
private String materialCode;
private String materialName;
private String material;
private BigDecimal bomQty;
/** Excel 行号,从 1 开始 */
private int excelRow;
}
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.system.domain.dto.ratio;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class RatioImportResultDTO {
private String message;
private int sheetCount;
private int modelCount;
private int commonCount;
private int specialCount;
private List<String> details = new ArrayList<>();
private List<String> skippedSheets = new ArrayList<>();
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.system.domain.dto.ratio;
import lombok.Data;
@Data
public class RatioModelOptionDTO {
private Long id;
private String modelCode;
}

View File

@ -0,0 +1,23 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 设计账号同步结果
*/
@Data
public class DesignAccountSyncVo {
private int createdUsers;
private int attachedRoles;
private int skipped;
private int backfillCreated;
private List<String> messages = new ArrayList<>();
}

View File

@ -0,0 +1,38 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 设计工作台顶部卡片
*/
@Data
public class DesignTaskBoardVo {
private BigDecimal weekHours = BigDecimal.ZERO;
private BigDecimal weekTarget = new BigDecimal("40");
private BigDecimal todayHours = BigDecimal.ZERO;
private BigDecimal todayTarget = new BigDecimal("8");
private long todayReportCount;
private long workingCount;
private long pendingCount;
private long overdueCount;
private long mineCount;
private long allCount;
private long weekNewCount;
private List<DesignTaskDayHoursVo> last7 = new ArrayList<>();
}

View File

@ -0,0 +1,22 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 近 7 天中的一天工时
*/
@Data
public class DesignTaskDayHoursVo {
/** yyyy-MM-dd */
private String date;
/** 一 到 日 */
private String label;
private BigDecimal hours = BigDecimal.ZERO;
private Boolean today;
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 设计报工明细
*/
@Data
public class DesignTaskReportVo {
private Long id;
private Long taskId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date reportDate;
private BigDecimal workHours;
private Integer progress;
private String content;
private String reportBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date reportTime;
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 首页设计任务摘要
*/
@Data
public class DesignTaskSummaryVo {
private long pendingCount;
private long overdueCount;
private long finishedThisMonth;
private List<DesignTaskVo> recent = new ArrayList<>();
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 设计任务展示
*/
@Data
public class DesignTaskVo {
private Long id;
private Long orderProId;
private String productionOrderNo;
private String productionName;
private String projectType;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date drawingTime;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date projectEndTime;
private Long bomStatus;
private Integer whetherDelay;
private String designerUserName;
private String designerName;
/** 2D / 3D,未标注为空 */
private String designDim;
private Integer status;
private Integer progress;
private BigDecimal totalHours;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date assignTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date finishTime;
private String remark;
/** 预计出图日早于今天,且任务未完成、未撤回 */
private Boolean overdue;
/** 该项目除已撤回外的设计任务都已完成 */
private Boolean allDesignersDone;
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 出图 / 工艺 / 钉钉推送留痕列表与详情。
*/
@Data
public class DrawingRouteOperLogVo {
private Long id;
private String bizType;
private String channel;
private String apiPath;
private Long projectId;
private String productionOrderNo;
private String drawingMode;
private String notifyType;
private String notifyUserIds;
private String messageTitle;
private String messageContent;
private String fileName;
private String status;
private String errorMsg;
private String summaryJson;
private String operator;
private String clientIp;
private Long durationMs;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date operTime;
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 临时导入:按生产令号更新出图时间、项目完成时间、工艺完成时间、计划排产时间、BOM/物料更新、设计人。
* 空单元格不更新。
*/
@Data
@ExcelIgnoreUnannotated
public class OrderProDrawingImportVo {
@ExcelProperty(value = "生产令号")
private String productionOrderNo;
@ExcelProperty(value = "出图时间")
private String drawingTime;
@ExcelProperty(value = "项目完成时间")
private String projectEndTime;
@ExcelProperty(value = "工艺完成时间")
private String routeUptime;
@ExcelProperty(value = "计划排产时间")
private String planUptime;
@ExcelProperty(value = "BOM/物料更新")
private String bomUptime;
@ExcelProperty(value = "设计人")
private String drawingBy;
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
/**
* 生产项目进度报表:按设计人汇总。
*/
@Data
public class OrderProProgressDesignerSummaryVo {
/** 设计人(字典 plan_proer 的值) */
private String drawingBy;
/** 参与项目数。同一项目多名设计人时,分别计入每位设计人。 */
private long projectCount;
/** 已有实际出图时间的项目数 */
private long drawn;
/** 出图逾期项目数(预计出图当天完成不算逾期) */
private long delayed;
/** 未逾期项目数 */
private long onTime;
/** A4 图纸数量合计 */
private long a4Total;
/** 等效 A4 合计 */
private long equivalentA4Total;
}

View File

@ -0,0 +1,140 @@
package com.ruoyi.system.domain.vo;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.common.utils.StringUtils;
import lombok.Data;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 生产项目进度报表导出。
*/
@Data
@ExcelIgnoreUnannotated
public class OrderProProgressReportExportVo {
private static final Map<String, String> PROJECT_TYPE_LABELS = new HashMap<>();
static {
PROJECT_TYPE_LABELS.put("CP", "产品");
PROJECT_TYPE_LABELS.put("FB", "非标");
PROJECT_TYPE_LABELS.put("BG", "变更");
PROJECT_TYPE_LABELS.put("QB", "企标");
PROJECT_TYPE_LABELS.put("EY", "研发");
PROJECT_TYPE_LABELS.put("GZ", "固资");
PROJECT_TYPE_LABELS.put("ZP", "展品");
PROJECT_TYPE_LABELS.put("KC", "库存");
PROJECT_TYPE_LABELS.put("产品", "产品");
PROJECT_TYPE_LABELS.put("非标", "非标");
PROJECT_TYPE_LABELS.put("变更", "变更");
PROJECT_TYPE_LABELS.put("研发", "研发");
PROJECT_TYPE_LABELS.put("企标", "企标");
PROJECT_TYPE_LABELS.put("库存", "库存");
PROJECT_TYPE_LABELS.put("固资", "固资");
PROJECT_TYPE_LABELS.put("展品", "展品");
}
@ExcelProperty(value = "生产令号")
private String productionOrderNo;
@ExcelProperty(value = "预计出图时间")
private String drawingTime;
@ExcelProperty(value = "实际出图时间")
private String actualDrawingTime;
@ExcelProperty(value = "出图是否逾期")
private String drawingOverdueLabel;
@ExcelProperty(value = "项目名称")
private String productionName;
@ExcelProperty(value = "设计人")
private String drawingBy;
@ExcelProperty(value = "来源")
private String source;
@ExcelProperty(value = "项目类型")
private String projectTypeLabel;
@ExcelProperty(value = "项目完成时间")
private String projectEndTime;
@ExcelProperty(value = "工艺预计完成时间")
private String routeUptime;
@ExcelProperty(value = "实际完成时间")
private String actualCompleteTime;
@ExcelProperty(value = "计划排产时间")
private String planUptime;
@ExcelProperty(value = "BOM物料更新时间")
private String bomUptime;
@ExcelProperty(value = "A4图纸数量")
private Integer a4Count;
@ExcelProperty(value = "等效A4")
private Integer equivalentA4Total;
@ExcelProperty(value = "创建人")
private String createBy;
public static OrderProProgressReportExportVo from(OrderProProgressReportVo vo) {
OrderProProgressReportExportVo row = new OrderProProgressReportExportVo();
if (vo == null) {
return row;
}
row.setProductionOrderNo(vo.getProductionOrderNo());
row.setDrawingTime(formatDate(vo.getDrawingTime()));
row.setActualDrawingTime(formatDateTime(vo.getActualDrawingTime()));
row.setDrawingOverdueLabel(formatOverdue(vo.getDrawingOverdue()));
row.setProductionName(vo.getProductionName());
row.setDrawingBy(vo.getDrawingBy());
row.setSource(formatSource(vo.getSource()));
row.setProjectTypeLabel(StringUtils.isNotBlank(vo.getProjectTypeLabel())
? vo.getProjectTypeLabel()
: formatProjectType(vo.getIsEnterpriseStandard()));
row.setProjectEndTime(formatDateTime(vo.getProjectEndTime()));
row.setRouteUptime(formatDateTime(vo.getRouteUptime()));
row.setActualCompleteTime(formatDateTime(vo.getActualCompleteTime()));
row.setPlanUptime(formatDateTime(vo.getPlanUptime()));
row.setBomUptime(formatDateTime(vo.getBomUptime()));
row.setA4Count(vo.getA4Count());
row.setEquivalentA4Total(vo.getEquivalentA4Total());
row.setCreateBy(vo.getCreateBy());
return row;
}
private static String formatSource(String value) {
return StringUtils.isNotBlank(value) ? value.trim() : "手动";
}
private static String formatProjectType(String code) {
if (StringUtils.isBlank(code)) {
return "-";
}
return PROJECT_TYPE_LABELS.getOrDefault(code.trim(), code.trim());
}
private static String formatOverdue(Boolean overdue) {
if (overdue == null) {
return "";
}
return overdue ? "逾期" : "未逾期";
}
private static String formatDate(Date date) {
return date == null ? "" : DateUtil.format(date, "yyyy-MM-dd");
}
private static String formatDateTime(Date date) {
return date == null ? "" : DateUtil.format(date, "yyyy-MM-dd HH:mm:ss");
}
}

View File

@ -0,0 +1,34 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
/**
* 生产项目进度报表顶部统计。
*/
@Data
public class OrderProProgressReportOverviewVo {
/** 项目总数 */
private long total;
/** 已有实际出图时间 */
private long drawn;
/** 出图逾期:实际出图日晚于预计出图日;预计出图当天完成不算逾期 */
private long delayed;
/** 已有工艺实际完成时间 */
private long routeDone;
/** 已填 BOM 物料更新时间 */
private long bomUpdated;
/** 已读取图纸工作量的项目数 */
private long workloadReady;
/** A4 图纸数量合计 */
private long a4Total;
/** 等效 A4 合计 */
private long equivalentA4Total;
}

View File

@ -0,0 +1,76 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 生产项目进度报表。
*/
@Data
public class OrderProProgressReportVo {
private Long id;
/** 生产令号 */
private String productionOrderNo;
/** 预计出图时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date drawingTime;
/** 实际出图时间:产品图纸最后一次上传时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date actualDrawingTime;
/**
* 出图是否逾期。预计出图当天及之前完成不算逾期;
* 尚无实际出图时,过了预计出图当天算逾期。
*/
private Boolean drawingOverdue;
/** 项目名称 */
private String productionName;
/** 设计人(字典 plan_proer,多人逗号分隔) */
private String drawingBy;
/** 来源:手动 / PLM */
private String source;
/** 项目类型原始值 */
private String isEnterpriseStandard;
/** 项目类型展示名 */
private String projectTypeLabel;
/** 项目完成时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date projectEndTime;
/** 工艺预计完成时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date routeUptime;
/** 实际完成时间:工艺实际上传完成 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date actualCompleteTime;
/** 计划排产时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date planUptime;
/** BOM 物料更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bomUptime;
/** A4 图纸数量(图纸工作量汇总) */
private Integer a4Count;
/** 等效 A4 合计(图纸工作量汇总) */
private Integer equivalentA4Total;
/** 创建人 */
private String createBy;
}

View File

@ -64,6 +64,22 @@ public class ProcessOrderProVo {
@ExcelProperty(value = "出图模式") @ExcelProperty(value = "出图模式")
private String drawingMode; private String drawingMode;
/** 最近一次出图 processstate:4 完成,5 失败 */
@ExcelIgnore
private Integer drawingState;
/** 最近一次出图提示 */
@ExcelIgnore
private String drawingStateMsg;
/** 最近一次出图输出路径 */
@ExcelIgnore
private String drawingStatePath;
/** 最近一次出图状态写入时间 */
@ExcelIgnore
private Date drawingStateTime;
/** /**
* 是否做工艺:0否 1是 * 是否做工艺:0否 1是
*/ */

View File

@ -0,0 +1,54 @@
package com.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import java.math.BigDecimal;
/**
* VMI低库存钉钉提醒 Excel 行
*/
@Data
@ExcelIgnoreUnannotated
@ColumnWidth(16)
public class VmiLowStockAlertExportVo {
@ExcelProperty("物料编码")
@ColumnWidth(18)
private String materialCode;
@ExcelProperty("名称")
@ColumnWidth(22)
private String materialName;
@ExcelProperty("材料")
private String materialQuality;
@ExcelProperty("供应商")
@ColumnWidth(14)
private String supplierName;
@ExcelProperty("即时库存")
private BigDecimal currentQty;
@ExcelProperty("预计可用")
private BigDecimal availableQty;
@ExcelProperty("最低安全库存")
private BigDecimal minSafeQty;
@ExcelProperty("最高安全库存")
private BigDecimal maxSafeQty;
@ExcelProperty("最小包装量")
private BigDecimal minPackQty;
@ExcelProperty("建议补货数量")
private BigDecimal suggestQty;
@ExcelProperty("备注")
@ColumnWidth(20)
private String remark;
}

View File

@ -0,0 +1,77 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* VMI物料安全库存视图对象
*/
@Data
public class VmiMaterialVo {
/** 主键 */
private Long id;
/** 物料编码 */
private String materialCode;
/** 名称 */
private String materialName;
/** 材料 */
private String materialQuality;
/** 供应商编码 */
private String supplierCode;
/** 供应商 */
private String supplierName;
/** 最小包装量 */
private BigDecimal minPackQty;
/** 建议最高安全库存 */
private BigDecimal maxSafeQty;
/** 建议最低安全库存 */
private BigDecimal minSafeQty;
/** 仓储部VMI负责人钉钉userId,多个逗号分隔 */
private String dingUserId;
/** 需求预测数量 */
private BigDecimal forecastQty;
/** 需求预测说明(旺季、订单波动等) */
private String forecastRemark;
/** 即时库存 */
private BigDecimal lastStockQty;
/** 最近一次扫描的预计可用库存 */
private BigDecimal lastAvailableQty;
/** 最近一次扫描时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date lastCheckTime;
/** 库存状态:NORMAL正常 / LOW低于安全库存 */
private String stockStatus;
/** 是否启用:0停用 1启用 */
private String enabled;
/** 备注 */
private String remark;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@ -0,0 +1,54 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* VMI采购申请推送记录视图对象
*/
@Data
public class VmiPurchaseReqLogVo {
/** 主键 */
private Long id;
/** 对应的低库存触发记录ID */
private Long alertLogId;
/** VMI物料主键 */
private Long materialId;
/** 物料编码 */
private String materialCode;
/** 物料名称 */
private String materialName;
/** 供应商编码 */
private String supplierCode;
/** 采购申请数量 */
private BigDecimal reqQty;
/** 金蝶采购申请单号 */
private String kingdeeBillNo;
/** 金蝶采购申请FID */
private String kingdeeFid;
/** 推送结果:SUCCESS成功 / FAIL失败 */
private String pushStatus;
/** 金蝶返回或失败原因 */
private String pushMsg;
/** 操作人 */
private String createBy;
/** 推送时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@ -0,0 +1,42 @@
package com.ruoyi.system.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* VMI补货确认预览行(推送金蝶前供管理员核对/修改数量)
*/
@Data
public class VmiReplenishPreviewItemVo {
private Long alertId;
private Long materialId;
private String materialCode;
private String materialName;
private String supplierCode;
private String supplierName;
/** 触发时即时库存 */
private BigDecimal currentQty;
/** 触发时预计可用 */
private BigDecimal availableQty;
private BigDecimal minSafeQty;
private BigDecimal maxSafeQty;
private BigDecimal minPackQty;
/** 系统按规则计算的建议补货数量 */
private BigDecimal suggestQty;
/** 实际申请数量(默认同 suggestQty,前端可改) */
private BigDecimal reqQty;
}

View File

@ -0,0 +1,64 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* VMI低库存触发记录视图对象
*/
@Data
public class VmiStockAlertLogVo {
/** 主键 */
private Long id;
/** VMI物料主键 */
private Long materialId;
/** 物料编码 */
private String materialCode;
/** 物料名称 */
private String materialName;
/** 供应商编码(来自VMI物料) */
private String supplierCode;
/** 供应商名称(来自VMI物料) */
private String supplierName;
/** 触发时的最低安全库存 */
private BigDecimal minSafeQty;
/** 触发时的即时库存 */
private BigDecimal currentQty;
/** 触发时的预计可用库存 */
private BigDecimal availableQty;
/** 处理状态:PENDING待确认 / CONFIRMED已确认 / IGNORED已忽略 */
private String alertStatus;
/** 钉钉推送结果:SUCCESS成功 / FAIL失败 */
private String dingPushStatus;
/** 钉钉推送返回信息 */
private String dingPushMsg;
/** 确认人或忽略人 */
private String confirmBy;
/** 确认或忽略时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date confirmTime;
/** 备注 */
private String remark;
/** 触发时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 指定群预警定时推送配置视图对象
*/
@Data
public class WorkAlertGroupPushConfigVo {
private Long id;
private String configName;
private String openConversationId;
private String robotCode;
private String pushCodes;
/** 任务中文名,逗号分隔 */
private String pushCodeNames;
private String pushTime;
private String enabled;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date lastPushDate;
private String remark;
}

View File

@ -0,0 +1,68 @@
package com.ruoyi.system.domain.vo.mes;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* MES 图纸清单中的一张图(同一物料编码下 PDF / DWG 合并为一条)。
*/
@Data
public class MesDrawingItemVo {
/** 推送明细 ID,磁盘扫描补全的图纸可能为空 */
private Long itemId;
/** 生产令号 */
private String productionOrderNo;
/** 物料编码 / 图号 */
private String materialCode;
/** 物料名称 */
private String materialName;
/** PDF 文件名 */
private String pdfFileName;
/** DWG 文件名 */
private String dwgFileName;
/** PDF 相对路径,如 令号/图号_名称.pdf */
private String pdfRelativePath;
/** DWG 相对路径,如 令号/dwg/图号_名称.dwg */
private String dwgRelativePath;
/** PDF 下载地址(相对本系统根路径,MES 需拼接主机) */
private String pdfDownloadUrl;
/** DWG 下载地址 */
private String dwgDownloadUrl;
/** PDF 是否存在 */
private Boolean pdfExists;
/** DWG 是否存在 */
private Boolean dwgExists;
/** PDF 字节数 */
private Long pdfSize;
/** DWG 字节数 */
private Long dwgSize;
/** 单台数量 */
private java.math.BigDecimal unitQty;
/** 本批数量 */
private java.math.BigDecimal batchQty;
/** 是否变更图纸 */
private Boolean isDrawingChanged;
/** 最近一次接收时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date receiveTime;
}

View File

@ -0,0 +1,30 @@
package com.ruoyi.system.domain.vo.mes;
import lombok.Data;
import java.util.List;
/**
* MES 按生产令号查询图纸清单。
*/
@Data
public class MesDrawingListVo {
/** 生产令号 */
private String productionOrderNo;
/** 项目名称 */
private String productionName;
/** 图纸是否已齐:0否 1是 */
private Integer drawingQtyIssued;
/** 是否允许下载:0否 1是 */
private Integer isDownload;
/** 图纸条数 */
private Integer itemCount;
/** 图纸列表 */
private List<MesDrawingItemVo> items;
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.system.job;
import com.ruoyi.system.service.IOrderProDrawingDelayService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 每日刷新项目出图延期天数(工作日一周六天)。
* XXL-JOB Cron 建议:0 10 0 * * ? (每天 00:10)
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DrawingDelayRefreshJob {
private final IOrderProDrawingDelayService orderProDrawingDelayService;
@XxlJob("drawingDelayRefreshHandler")
public void drawingDelayRefreshHandler() {
XxlJobHelper.log("开始刷新项目出图延期状态...");
try {
orderProDrawingDelayService.refreshAll();
XxlJobHelper.handleSuccess("刷新完成");
} catch (Exception e) {
log.error("刷新项目出图延期状态异常", e);
XxlJobHelper.handleFail("刷新异常: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,32 @@
package com.ruoyi.system.job;
import com.ruoyi.system.service.IVmiMaterialService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 扫描VMI物料即时库存,低于最低安全库存则钉钉提醒仓储部VMI负责人。
* XXL-JOB JobHandler:vmiSafeStockCheckHandler
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class VmiSafeStockCheckJob {
private final IVmiMaterialService vmiMaterialService;
@XxlJob("vmiSafeStockCheckHandler")
public void vmiSafeStockCheckHandler() {
XxlJobHelper.log("开始扫描VMI低库存...");
try {
int hit = vmiMaterialService.scanLowStock();
XxlJobHelper.handleSuccess("扫描完成,新触发 " + hit + " 条");
} catch (Exception e) {
log.error("VMI低库存扫描异常", e);
XxlJobHelper.handleFail("扫描异常: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,10 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.DingTodoLog;
/**
* 钉钉待办记录
*/
public interface DingTodoLogMapper extends BaseMapperPlus<DingTodoLogMapper, DingTodoLog, DingTodoLog> {
}

View File

@ -0,0 +1,10 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.DingTodoScene;
/**
* 钉钉待办场景
*/
public interface DingTodoSceneMapper extends BaseMapperPlus<DingTodoSceneMapper, DingTodoScene, DingTodoScene> {
}

View File

@ -0,0 +1,8 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.DrawingRouteOperLog;
import com.ruoyi.system.domain.vo.DrawingRouteOperLogVo;
public interface DrawingRouteOperLogMapper extends BaseMapperPlus<DrawingRouteOperLogMapper, DrawingRouteOperLog, DrawingRouteOperLogVo> {
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.domain.dto.OrderProProgressTimeAgg;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 生产项目进度报表时间聚合。
*/
public interface OrderProProgressReportMapper {
List<OrderProProgressTimeAgg> selectActualDrawingTimes(@Param("ids") List<Long> ids);
List<OrderProProgressTimeAgg> selectActualRouteLogTimes(@Param("orderNos") List<String> orderNos);
List<OrderProProgressTimeAgg> selectActualRouteCreateTimes(@Param("orderNos") List<String> orderNos);
/** PLM 图纸推送接收记录的接收时间。 */
List<OrderProProgressTimeAgg> selectPlmDrawingReceiveTimes(@Param("orderNos") List<String> orderNos);
/** PLM 工艺路线接收记录的接收时间。 */
List<OrderProProgressTimeAgg> selectPlmRouteReceiveTimes(@Param("orderNos") List<String> orderNos);
}

View File

@ -0,0 +1,10 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.ProcessDesignReport;
/**
* 设计报工明细
*/
public interface ProcessDesignReportMapper extends BaseMapperPlus<ProcessDesignReportMapper, ProcessDesignReport, ProcessDesignReport> {
}

View File

@ -0,0 +1,10 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.ProcessDesignTask;
/**
* 设计个人任务
*/
public interface ProcessDesignTaskMapper extends BaseMapperPlus<ProcessDesignTaskMapper, ProcessDesignTask, ProcessDesignTask> {
}

View File

@ -0,0 +1,7 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.RatioModel;
public interface RatioModelMapper extends BaseMapperPlus<RatioModelMapper, RatioModel, RatioModel> {
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.VmiMaterial;
import com.ruoyi.system.domain.vo.VmiMaterialVo;
/**
* VMI物料安全库存 Mapper
*/
public interface VmiMaterialMapper extends BaseMapperPlus<VmiMaterialMapper, VmiMaterial, VmiMaterialVo> {
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.VmiPurchaseReqLog;
import com.ruoyi.system.domain.vo.VmiPurchaseReqLogVo;
/**
* VMI采购申请推送记录 Mapper
*/
public interface VmiPurchaseReqLogMapper extends BaseMapperPlus<VmiPurchaseReqLogMapper, VmiPurchaseReqLog, VmiPurchaseReqLogVo> {
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.VmiStockAlertLog;
import com.ruoyi.system.domain.vo.VmiStockAlertLogVo;
/**
* VMI低库存触发记录 Mapper
*/
public interface VmiStockAlertLogMapper extends BaseMapperPlus<VmiStockAlertLogMapper, VmiStockAlertLog, VmiStockAlertLogVo> {
}

View File

@ -0,0 +1,11 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.WorkAlertGroupPushConfig;
import com.ruoyi.system.domain.vo.WorkAlertGroupPushConfigVo;
/**
* 指定群预警定时推送配置 Mapper
*/
public interface WorkAlertGroupPushConfigMapper extends BaseMapperPlus<WorkAlertGroupPushConfigMapper, WorkAlertGroupPushConfig, WorkAlertGroupPushConfigVo> {
}

View File

@ -0,0 +1,248 @@
package com.ruoyi.system.service;
import cn.hutool.core.io.IoUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.config.DrawingFileQueryProperties;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.constant.ProcessOrderProSource;
import com.ruoyi.system.domain.ProcessOrderPro;
import com.ruoyi.system.mapper.ProcessOrderProMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 按图号转发图纸/模型下载。仅 PLM 来源项目允许调用。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DrawingFileQueryService {
private static final Set<String> FILE_TYPES = new HashSet<>(Arrays.asList(
"sldprt", "sldasm", "dwg", "pdf", "x_t"
));
private static final Pattern FILENAME_STAR = Pattern.compile("filename\\*=(?:UTF-8'')?([^;]+)", Pattern.CASE_INSENSITIVE);
private static final Pattern FILENAME = Pattern.compile("filename=\"?([^\";]+)\"?", Pattern.CASE_INSENSITIVE);
private final DrawingFileQueryProperties properties;
private final ProcessOrderProMapper processOrderProMapper;
public void download(String productionOrderNo, String materialCode, String fileType, HttpServletResponse response) {
String orderNo = StringUtils.trimToEmpty(productionOrderNo);
String code = normalizeMaterialCode(materialCode);
String type = normalizeFileType(fileType);
assertPlmOrder(orderNo);
String url = buildUrl(code, type);
HttpResponse remote = null;
try {
remote = HttpRequest.get(url).timeout(properties.getReadTimeoutMs()).executeAsync();
int status = remote.getStatus();
String contentType = StringUtils.trimToEmpty(remote.header("Content-Type"));
if (status < 200 || status >= 300 || isErrorContent(contentType)) {
String body = "";
try {
body = remote.body();
} catch (Exception ignored) {
// 错误正文读不到时只返回状态码
}
log.warn("图纸文件服务返回异常 status={} url={} body={}", status, url, abbreviate(body));
throw new ServiceException(briefError(body, status));
}
String filename = resolveFilename(remote.header("Content-Disposition"), code, type);
String responseType = StringUtils.isBlank(contentType) ? "application/octet-stream" : contentType;
response.setContentType(responseType);
String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8.name()).replace("+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded);
long length = remote.contentLength();
if (length > 0) {
response.setContentLengthLong(length);
}
try (InputStream in = remote.bodyStream(); OutputStream out = response.getOutputStream()) {
IoUtil.copy(in, out);
out.flush();
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
if (isConnectFailure(e)) {
String target = StringUtils.trimToEmpty(properties.getBaseUrl());
log.warn("图纸文件服务连不上 url={} msg={}", url, rootMessage(e));
if (!response.isCommitted()) {
throw new ServiceException("无法连接图纸文件服务 " + target + ",连接被拒绝。请确认该地址和端口对本机开放");
}
return;
}
log.error("下载图纸模型失败 order={} code={} type={}", orderNo, code, type, e);
if (!response.isCommitted()) {
throw new ServiceException("图纸模型下载失败,请稍后重试");
}
} finally {
if (remote != null) {
remote.close();
}
}
}
private static boolean isConnectFailure(Throwable error) {
Throwable current = error;
while (current != null) {
if (current instanceof java.net.SocketException || current instanceof java.net.ConnectException) {
return true;
}
String message = current.getMessage();
if (message != null && (message.contains("Permission denied: connect")
|| message.contains("connect timed out")
|| message.contains("Connection refused")
|| message.contains("访问权限不允许"))) {
return true;
}
current = current.getCause();
}
return false;
}
private static String rootMessage(Throwable error) {
Throwable current = error;
while (current.getCause() != null) {
current = current.getCause();
}
return StringUtils.trimToEmpty(current.getMessage());
}
private void assertPlmOrder(String orderNo) {
if (StringUtils.isBlank(orderNo)) {
throw new ServiceException("生产令号不能为空");
}
ProcessOrderPro order = processOrderProMapper.selectOne(
Wrappers.<ProcessOrderPro>lambdaQuery()
.eq(ProcessOrderPro::getProductionOrderNo, orderNo)
.last("LIMIT 1"));
if (order == null) {
throw new ServiceException("未找到项目令号: " + orderNo);
}
String source = StringUtils.trimToEmpty(order.getSource());
if (!ProcessOrderProSource.PLM.equalsIgnoreCase(source)) {
throw new ServiceException("仅 PLM 来源项目可下载图纸模型");
}
}
private String buildUrl(String materialCode, String fileType) {
String base = StringUtils.trimToEmpty(properties.getBaseUrl());
if (base.endsWith("/")) {
base = base.substring(0, base.length() - 1);
}
String path = StringUtils.trimToEmpty(properties.getDownloadPath());
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.isBlank(base)) {
throw new ServiceException("图纸文件服务地址未配置");
}
return base + path
+ "?propertyValue=" + urlEncode(materialCode)
+ "&fileType=" + urlEncode(fileType);
}
private static String normalizeMaterialCode(String materialCode) {
String code = StringUtils.trimToEmpty(materialCode);
if (StringUtils.isBlank(code) || code.length() > 128
|| code.contains("..") || code.indexOf('/') >= 0 || code.indexOf('\\') >= 0
|| code.indexOf('\r') >= 0 || code.indexOf('\n') >= 0) {
throw new ServiceException("物料编码不合法");
}
return code;
}
private static String normalizeFileType(String fileType) {
String type = StringUtils.trimToEmpty(fileType).toLowerCase(Locale.ROOT);
if (!FILE_TYPES.contains(type)) {
throw new ServiceException("不支持的图纸格式");
}
return type;
}
private static boolean isErrorContent(String contentType) {
String type = contentType.toLowerCase(Locale.ROOT);
return type.contains("json") || type.contains("text/html");
}
private static String abbreviate(String body) {
String text = StringUtils.trimToEmpty(body).replaceAll("\\s+", " ");
return text.length() > 200 ? text.substring(0, 200) : text;
}
private static String briefError(String body, int status) {
String text = StringUtils.trimToEmpty(body).replaceAll("\\s+", " ");
if (text.length() > 200) {
text = text.substring(0, 200);
}
if (StringUtils.isBlank(text) || text.startsWith("<") || text.startsWith("{")) {
return "图纸模型下载失败(HTTP " + status + ")";
}
return text;
}
private static String resolveFilename(String disposition, String materialCode, String fileType) {
String name = parseDispositionFilename(disposition);
if (StringUtils.isBlank(name)) {
name = materialCode + "." + fileType;
}
name = name.replace("\\", "/");
int slash = name.lastIndexOf('/');
if (slash >= 0) {
name = name.substring(slash + 1);
}
name = name.replace("\r", "").replace("\n", "").replace("\"", "").trim();
return StringUtils.isBlank(name) ? materialCode + "." + fileType : name;
}
private static String parseDispositionFilename(String disposition) {
if (StringUtils.isBlank(disposition)) {
return "";
}
Matcher star = FILENAME_STAR.matcher(disposition);
if (star.find()) {
return urlDecode(star.group(1).trim());
}
Matcher plain = FILENAME.matcher(disposition);
if (plain.find()) {
return plain.group(1).trim();
}
return "";
}
private static String urlEncode(String value) {
try {
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
} catch (Exception e) {
throw new ServiceException("参数编码失败");
}
}
private static String urlDecode(String value) {
try {
return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
} catch (Exception e) {
return value;
}
}
}

View File

@ -0,0 +1,178 @@
package com.ruoyi.system.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.DrawingRouteOperLog;
import com.ruoyi.system.domain.ProcessOrderPro;
import com.ruoyi.system.mapper.DrawingRouteOperLogMapper;
import com.ruoyi.system.mapper.ProcessOrderProMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 按推送日志重发对应钉钉通知。执行出图、工艺上传本身不是通知,不在这里重放。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DrawingRouteOperLogRenotifyService {
private final DrawingRouteOperLogMapper drawingRouteOperLogMapper;
private final ProcessOrderProMapper processOrderProMapper;
private final PlmPlanCompleteNotifyService plmPlanCompleteNotifyService;
private final PlmDrawingArriveNotifyService plmDrawingArriveNotifyService;
private final SkipProcessNotifyService skipProcessNotifyService;
private final IProcessOrderProService processOrderProService;
private final ProcessPlanExcelPushService processPlanExcelPushService;
private final IDrawingRouteOperLogService drawingRouteOperLogService;
public String renotify(Long id) {
if (id == null) {
throw new ServiceException("日志ID不能为空");
}
DrawingRouteOperLog row = drawingRouteOperLogMapper.selectById(id);
if (row == null) {
throw new ServiceException("日志不存在");
}
if (IDrawingRouteOperLogService.STATUS_SKIP.equals(row.getStatus())) {
throw new ServiceException("这条是跳过记录,没有发出去的通知");
}
String bizType = StringUtils.trimToEmpty(row.getBizType());
String result;
if (IDrawingRouteOperLogService.BIZ_ROUTE_NOTIFY.equals(bizType)) {
result = renotifyRoute(row);
} else if (IDrawingRouteOperLogService.BIZ_DRAWING_NOTIFY.equals(bizType)) {
result = renotifyDrawing(row);
} else if (IDrawingRouteOperLogService.BIZ_SKIP_PROCESS_NOTIFY.equals(bizType)) {
result = skipProcessNotifyService.resendDrawingComplete(loadOrder(row));
} else if (IDrawingRouteOperLogService.BIZ_ELECTRICAL_BOM_NOTIFY.equals(bizType)) {
result = skipProcessNotifyService.resendElectricalBom(loadOrder(row), savedCount(row));
} else {
throw new ServiceException("该类型不能重新通知");
}
return requireSent(result);
}
/** PLM 到齐通知重导计划表发给生管;手动上传只重发已落盘的计划表,不重发审核卡片。 */
private String renotifyRoute(DrawingRouteOperLog row) {
if (isPlmRouteNotify(row)) {
return plmPlanCompleteNotifyService.notifyIfAssemblyAndRouteComplete(row.getProductionOrderNo(), null);
}
return resendSavedPlanFile(row);
}
private String renotifyDrawing(DrawingRouteOperLog row) {
if (IDrawingRouteOperLogService.CHANNEL_PLM.equals(row.getChannel())) {
return plmDrawingArriveNotifyService.resend(row.getProductionOrderNo(), row.getSummaryJson());
}
ProcessOrderPro order = loadOrder(row);
if (order == null || order.getId() == null) {
return "项目不存在,未发送";
}
R<Void> sent = processOrderProService.sendProcessRoutePlanExcel(order.getId());
if (sent == null) {
return "发送失败:没有返回结果";
}
if (sent.getCode() != R.SUCCESS) {
throw new ServiceException(StringUtils.defaultIfBlank(sent.getMsg(), "发送失败"));
}
String msg = StringUtils.defaultIfBlank(sent.getMsg(), "已重新发送出图后推送");
if (msg.contains("跳过")) {
return "项目无需做工艺,未推送给工艺";
}
return "已重新发送出图后推送 令号=" + order.getProductionOrderNo();
}
private String resendSavedPlanFile(DrawingRouteOperLog row) {
String orderNo = StringUtils.trimToEmpty(row.getProductionOrderNo());
String fileName = StringUtils.trimToEmpty(row.getFileName());
if (StringUtils.isBlank(orderNo) || StringUtils.isBlank(fileName)) {
return "缺少令号或文件名,未发送";
}
File file = new File(new File(processPlanExcelPushService.getPlanExcelBaseDir(), orderNo), fileName);
if (!file.exists() || file.length() <= 0) {
return "服务器上找不到已落盘的计划表,未发送";
}
long started = System.currentTimeMillis();
ProcessPlanExcelPushService.FilePushResult filePush =
processPlanExcelPushService.sendToPlanUsers(file, fileName);
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("resend", true);
summary.put("sourceLogId", row.getId());
summary.put("fileStatus", filePush.resolveStatus());
drawingRouteOperLogService.record(IDrawingRouteOperLogService.BIZ_ROUTE_NOTIFY, logRow -> {
logRow.setChannel(IDrawingRouteOperLogService.CHANNEL_MANUAL);
logRow.setApiPath("/system/drawingRouteOperLog/renotify");
logRow.setProjectId(row.getProjectId());
logRow.setProductionOrderNo(orderNo);
logRow.setNotifyType(IDrawingRouteOperLogService.NOTIFY_FILE);
logRow.setNotifyUserIds(filePush.getUserIds());
logRow.setMessageTitle("重新发送工艺计划表");
logRow.setMessageContent("按日志重发已落盘计划表,不重发审核卡片");
logRow.setFileName(fileName);
logRow.setStatus(filePush.resolveStatus());
logRow.setErrorMsg(filePush.getErrorMsg());
logRow.setSummaryJson(JSON.toJSONString(summary));
logRow.setDurationMs(System.currentTimeMillis() - started);
});
if (StringUtils.isNotBlank(filePush.getErrorMsg())) {
return "计划表重发未完成:" + filePush.getErrorMsg();
}
return "已重发工艺计划表给生管 令号=" + orderNo + " 文件=" + fileName + "(未重发审核卡片)";
}
private static boolean isPlmRouteNotify(DrawingRouteOperLog row) {
if (IDrawingRouteOperLogService.CHANNEL_PLM.equals(row.getChannel())) {
return true;
}
String apiPath = StringUtils.defaultString(row.getApiPath());
return apiPath.contains("/system/plm/");
}
private ProcessOrderPro loadOrder(DrawingRouteOperLog row) {
if (row.getProjectId() != null) {
ProcessOrderPro byId = processOrderProMapper.selectById(row.getProjectId());
if (byId != null) {
return byId;
}
}
String orderNo = StringUtils.trimToEmpty(row.getProductionOrderNo());
if (StringUtils.isBlank(orderNo)) {
return null;
}
return processOrderProMapper.selectByProjectNumber(orderNo);
}
private static int savedCount(DrawingRouteOperLog row) {
if (StringUtils.isBlank(row.getSummaryJson())) {
return 0;
}
try {
JSONObject obj = JSON.parseObject(row.getSummaryJson());
return obj == null ? 0 : obj.getIntValue("savedCount");
} catch (Exception e) {
return 0;
}
}
private static String requireSent(String result) {
String msg = StringUtils.defaultIfBlank(result, "重新通知没有返回结果");
if (msg.contains("未发送")
|| msg.contains("暂不发送")
|| msg.contains("发送失败")
|| msg.contains("未完成")
|| msg.contains("未推送")
|| msg.contains("失败")) {
throw new ServiceException(msg);
}
return msg;
}
}

View File

@ -0,0 +1,46 @@
package com.ruoyi.system.service;
import com.ruoyi.common.utils.ProjectStatusLock;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.ProcessOrderPro;
/**
* 发送计划表、创建待办前,确认项目已经出图成功。
*/
public final class DrawingSuccess {
private DrawingSuccess() {
}
/**
* @return 不能发送或创建待办时的提示;可以继续时返回 null
*/
public static String denyIfNotSuccess(ProcessOrderPro order) {
if (order == null) {
return "项目不存在,不能发送计划表或创建待办";
}
Integer state = order.getDrawingState();
String msg = StringUtils.trimToEmpty(order.getDrawingStateMsg());
if (state != null) {
if (state == 4 && !messageFailed(msg)) {
return null;
}
if (StringUtils.isNotBlank(msg)) {
return "项目尚未出图成功:" + msg;
}
if (state == 5) {
return "项目出图失败,不能发送计划表或创建待办";
}
return "项目出图尚未完成,不能发送计划表或创建待办";
}
Long bomStatus = order.getBomStatus();
if (bomStatus != null && bomStatus != ProjectStatusLock.SCHEME_DONE) {
return null;
}
return "项目尚未出图成功,不能发送计划表或创建待办";
}
private static boolean messageFailed(String msg) {
return msg.contains("失败") || msg.contains("未找到") || msg.contains("错误") || msg.contains("异常");
}
}

View File

@ -0,0 +1,59 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.ProcessOrderPro;
import com.ruoyi.system.domain.bo.DesignTaskDesignDimBo;
import com.ruoyi.system.domain.bo.DesignTaskQueryBo;
import com.ruoyi.system.domain.bo.DesignTaskReportBo;
import com.ruoyi.system.domain.vo.DesignAccountSyncVo;
import com.ruoyi.system.domain.vo.DesignTaskBoardVo;
import com.ruoyi.system.domain.vo.DesignTaskReportVo;
import com.ruoyi.system.domain.vo.DesignTaskSummaryVo;
import com.ruoyi.system.domain.vo.DesignTaskVo;
import java.util.Collection;
import java.util.List;
/**
* 设计个人任务
*/
public interface IDesignTaskService {
DesignTaskSummaryVo summaryMine();
/**
* @param scope all 且当前用户是管理员时,汇总全部设计人;否则只统计本人
*/
DesignTaskBoardVo boardMine(String scope);
TableDataInfo<DesignTaskVo> queryMine(DesignTaskQueryBo bo, PageQuery pageQuery);
/**
* @return 新报工记录 id
*/
Long report(DesignTaskReportBo bo);
void accept(Long taskId);
void updateDesignDim(DesignTaskDesignDimBo bo);
void deleteReport(Long reportId);
List<DesignTaskReportVo> listMyReports(Long taskId);
List<DesignTaskVo> listByOrder(Long orderId);
DesignAccountSyncVo syncAccounts();
/**
* 按项目当前设计人同步任务。backfill 为 true 时,新任务按项目状态补历史;false 时新任务一律待接收。
*
* @return 新插入的任务数
*/
int syncOrder(ProcessOrderPro order, boolean backfill);
void withdrawOpenByOrderIds(Collection<Long> orderIds);
void updateOrderNo(Long orderId, String productionOrderNo);
}

View File

@ -0,0 +1,29 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.DingTodoLog;
import com.ruoyi.system.domain.DingTodoScene;
import java.io.File;
import java.util.Date;
import java.util.List;
/**
* 按数据库里勾选的方法创建钉钉待办,并把结果写入 ding_todo_log。
*/
public interface IDingTodoDispatchService {
List<DingTodoScene> listScenes();
void updateScene(DingTodoScene scene);
TableDataInfo<DingTodoLog> queryLogs(DingTodoLog query, PageQuery pageQuery);
/**
* 该场景未勾选时什么都不做,返回 null。
* 勾选后发送失败则写入记录并返回失败原因。
*/
String sendIfEnabled(String sceneCode, String productionOrderNo, String projectName, Date routeUptime,
File excelFile, String fallbackUserIds);
}

View File

@ -0,0 +1,54 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.DrawingRouteOperLog;
import com.ruoyi.system.domain.bo.DrawingRouteOperLogBo;
import com.ruoyi.system.domain.vo.DrawingRouteOperLogVo;
import java.util.function.Consumer;
/**
* 出图 / 工艺上传 / 钉钉推送留痕。写入失败不影响主流程。
* <p>
* 业务类型:执行出图、出图后推送、工艺上传、上传后钉钉、无需做工艺通知、电气 BOM 通知。
*/
public interface IDrawingRouteOperLogService {
String BIZ_DRAWING_EXEC = "DRAWING_EXEC";
String BIZ_DRAWING_NOTIFY = "DRAWING_NOTIFY";
String BIZ_ROUTE_UPLOAD = "ROUTE_UPLOAD";
String BIZ_ROUTE_NOTIFY = "ROUTE_NOTIFY";
/** 无需做工艺且出图完成后通知生管 */
String BIZ_SKIP_PROCESS_NOTIFY = "SKIP_PROCESS_NOTIFY";
/** 电气 BOM 导入后的钉钉通知 */
String BIZ_ELECTRICAL_BOM_NOTIFY = "ELECTRICAL_BOM_NOTIFY";
String CHANNEL_MANUAL = "MANUAL";
String CHANNEL_PLM = "PLM";
String STATUS_SUCCESS = "SUCCESS";
String STATUS_FAIL = "FAIL";
String STATUS_PARTIAL = "PARTIAL";
/** 无需做工艺,出图后推送未发给工艺 */
String STATUS_SKIP = "SKIP";
String NOTIFY_MARKDOWN = "MARKDOWN";
String NOTIFY_FILE = "FILE";
String NOTIFY_CARD = "CARD";
String NOTIFY_MARKDOWN_AND_FILE = "MARKDOWN_AND_FILE";
String NOTIFY_FILE_AND_CARD = "FILE_AND_CARD";
/**
* 追加一条记录;自动补操作人、IP、时间。内部吞掉异常。
*/
void record(String bizType, Consumer<DrawingRouteOperLog> filler);
String currentOperator();
String currentClientIp();
TableDataInfo<DrawingRouteOperLogVo> queryPageList(DrawingRouteOperLogBo bo, PageQuery pageQuery);
DrawingRouteOperLogVo queryDetail(Long id);
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.vo.mes.MesDrawingListVo;
import java.io.File;
/**
* MES 开放图纸查询 / 下载。
*/
public interface IMesDrawingService {
/**
* 校验 MES appKey / appSecret。
*/
void assertAuth(String appKey, String appSecret);
/**
* 按生产令号列出图纸;materialCode 非空时只返回该图号。
*/
MesDrawingListVo listDrawings(String productionOrderNo, String materialCode);
/**
* 解析可下载文件。fileType:pdf / dwg。
*/
File resolveDownloadFile(String productionOrderNo, String materialCode, String fileType);
}

View File

@ -0,0 +1,13 @@
package com.ruoyi.system.service;
/**
* 按项目整体出图时间刷新设计出图延期/完成状态。
*/
public interface IOrderProDrawingDelayService {
/** 刷新所有仍处于「方案设计完成」且已填出图时间的项目 */
void refreshAll();
/** 刷新单个项目;已进入出图完成及后续状态的不再改延期天数 */
void refreshByProjectId(Long projectId);
}

View File

@ -0,0 +1,24 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.bo.OrderProProgressReportBo;
import com.ruoyi.system.domain.vo.OrderProProgressDesignerSummaryVo;
import com.ruoyi.system.domain.vo.OrderProProgressReportOverviewVo;
import com.ruoyi.system.domain.vo.OrderProProgressReportVo;
import java.util.List;
/**
* 生产项目进度报表。
*/
public interface IOrderProProgressReportService {
TableDataInfo<OrderProProgressReportVo> queryPageList(OrderProProgressReportBo bo, PageQuery pageQuery);
List<OrderProProgressReportVo> queryList(OrderProProgressReportBo bo);
OrderProProgressReportOverviewVo queryOverview(OrderProProgressReportBo bo);
List<OrderProProgressDesignerSummaryVo> queryDesignerSummary(OrderProProgressReportBo bo);
}

View File

@ -85,6 +85,11 @@ public interface IProcessOrderProService {
String executDrawing(ProcessOrderProBo orderPro); String executDrawing(ProcessOrderProBo orderPro);
/**
* 若出图服务当前结果属于该令号,写入项目最后一次出图状态;否则返回已保存的结果。
*/
ProcessOrderProVo captureCurrentDrawingState(Long id);
/** /**
* 上传PDF并生成zip包 * 上传PDF并生成zip包
* @param id 项目ID * @param id 项目ID

View File

@ -0,0 +1,13 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.dto.ratio.RatioImportResultDTO;
import org.springframework.web.multipart.MultipartFile;
public interface IRatioBomImportService {
/**
* 按机型和速比 Sheet 导入通用件、减速机型号专用件。
* 同一机型下的同一个速比会先清掉原有型号和物料明细,再写入本次文件的内容。
*/
RatioImportResultDTO importExcel(MultipartFile file, String seriesType);
}

View File

@ -0,0 +1,50 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.bo.VmiMaterialBo;
import com.ruoyi.system.domain.bo.VmiPurchaseReqLogBo;
import com.ruoyi.system.domain.bo.VmiReplenishConfirmBo;
import com.ruoyi.system.domain.bo.VmiReplenishPreviewBo;
import com.ruoyi.system.domain.bo.VmiStockAlertLogBo;
import com.ruoyi.system.domain.vo.VmiMaterialVo;
import com.ruoyi.system.domain.vo.VmiPurchaseReqLogVo;
import com.ruoyi.system.domain.vo.VmiReplenishPreviewItemVo;
import com.ruoyi.system.domain.vo.VmiStockAlertLogVo;
import java.util.Collection;
import java.util.List;
public interface IVmiMaterialService {
TableDataInfo<VmiMaterialVo> queryPageList(VmiMaterialBo bo, PageQuery pageQuery);
VmiMaterialVo queryById(Long id);
Boolean insertByBo(VmiMaterialBo bo);
Boolean updateByBo(VmiMaterialBo bo);
Boolean deleteWithValidByIds(Collection<Long> ids);
int scanLowStock();
/**
* 确认补货前预览:返回库存与建议申请数量,供管理员修改。
*/
List<VmiReplenishPreviewItemVo> previewReplenish(VmiReplenishPreviewBo bo);
/**
* 确认补货:按供应商合并生成金蝶采购申请(仅保存,不自动审批)。
* @return 生成的采购申请单数量
*/
int confirmReplenish(VmiReplenishConfirmBo bo);
void ignoreAlert(Long alertId);
void notifyForecast(Long id);
TableDataInfo<VmiStockAlertLogVo> queryAlertPage(VmiStockAlertLogBo bo, PageQuery pageQuery);
TableDataInfo<VmiPurchaseReqLogVo> queryReqPage(VmiPurchaseReqLogBo bo, PageQuery pageQuery);
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.bo.WorkAlertGroupPushConfigBo;
import com.ruoyi.system.domain.vo.WorkAlertGroupPushConfigVo;
import java.util.Collection;
import java.util.List;
/**
* 指定群预警定时推送配置
*/
public interface IWorkAlertGroupPushConfigService {
List<WorkAlertGroupPushConfigVo> queryList();
WorkAlertGroupPushConfigVo queryById(Long id);
Boolean insertByBo(WorkAlertGroupPushConfigBo bo);
Boolean updateByBo(WorkAlertGroupPushConfigBo bo);
Boolean deleteWithValidByIds(Collection<Long> ids);
void trigger(Long id);
}

Some files were not shown because too many files have changed in this diff Show More