29 Commits

Author SHA1 Message Date
3bce685f3a Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes 2026-06-18 15:18:14 +08:00
c54d54b40f 桌面端密炼计划、胶料快检调整 2026-06-18 15:18:11 +08:00
geht
a9b3720446 Merge branch '20260519-3.9.2版本-葛昊天分支' 2026-06-18 10:56:14 +08:00
geht
73a22b5ed9 中间表采集新增采集配置,实现可视化可控采集 2026-06-18 10:55:11 +08:00
372dc10be2 桌面端新增密炼计划获取 2026-06-17 17:52:31 +08:00
816af5df6e 密炼生产计划维护表更改 2026-06-17 16:39:39 +08:00
2ec2b6a628 Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes 2026-06-17 15:47:59 +08:00
7d7198b802 密炼生产计划优化 2026-06-17 15:47:53 +08:00
1c982052d3 Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes 2026-06-17 15:41:13 +08:00
94b15e5237 桌面端胶料快检实验标准添加 2026-06-17 15:41:06 +08:00
geht
ba77a95554 优化中间库配置加载逻辑,新增启动时加载最近更新的配置功能,提升系统稳定性和灵活性 2026-06-17 10:58:34 +08:00
geht
e28352f8ea 优化生产环境多数据源报错 2026-06-16 18:41:35 +08:00
geht
97a1543e97 frontend build dist update 2026-06-16 18:19:57 +08:00
geht
d40a339aec 优化半自动更新功能,修复已知问题并增强稳定性 2026-06-16 18:12:24 +08:00
geht
54a132de46 优化半自动更新功能,提升用户体验 2026-06-16 17:31:40 +08:00
geht
c96833b339 Merge branch '20260519-3.9.2版本-葛昊天分支' 2026-06-16 16:59:54 +08:00
geht
9147e62977 半自动更新 2026-06-16 16:59:05 +08:00
380de8c54b Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes 2026-06-16 15:35:57 +08:00
7f0f8a3a2f 快检标准新增 2026-06-16 15:35:54 +08:00
geht
44f9a3a197 Merge branch '20260519-3.9.2版本-葛昊天分支' 2026-06-16 14:06:08 +08:00
geht
276c808d7a 上辅机中间库的连接,改成可视化连接方式,可手动配置,放入第三方配置。 2026-06-16 14:05:10 +08:00
be215d604d Merge remote-tracking branch 'origin/生产及设备基础资料' 2026-06-15 16:41:47 +08:00
geht
33b969fc70 优化混炼示方新增时选择机台逻辑 2026-06-15 15:19:47 +08:00
geht
ece8e590e4 混炼示方优化 2026-06-12 19:38:33 +08:00
75bc744fc8 Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes 2026-06-11 10:06:33 +08:00
3431cc6b17 生产环节优化 2026-06-11 10:06:26 +08:00
geht
5cb24c582d MES审批复用钉钉审批设置 2026-06-10 16:57:07 +08:00
geht
617d47a3db MES本地审批共用钉钉审批等配置 2026-06-10 16:33:44 +08:00
cbbbabe4cf 设备停机记录、设备报警记录新增 2026-06-05 14:17:43 +08:00
2872 changed files with 40805 additions and 316 deletions

View File

@@ -0,0 +1,67 @@
---
name: karpathy-guidelines
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
license: MIT
---
# Karpathy Guidelines
Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

31
build-frontend.bat Normal file
View File

@@ -0,0 +1,31 @@
@echo off
setlocal
set REPO=%~dp0
set WEBDIST=%REPO%web-dist
echo [1/4] build frontend (pnpm install + build) ...
cd /d %REPO%jeecgboot-vue3
call pnpm install
if errorlevel 1 ( echo [ERROR] pnpm install failed & pause & exit /b 1 )
call pnpm run build
if errorlevel 1 ( echo [ERROR] frontend build failed & pause & exit /b 1 )
if not exist "%REPO%jeecgboot-vue3\dist\index.html" ( echo [ERROR] dist/index.html not found & pause & exit /b 1 )
echo [2/4] refresh web-dist folder ...
if exist "%WEBDIST%" rmdir /S /Q "%WEBDIST%"
mkdir "%WEBDIST%"
xcopy "%REPO%jeecgboot-vue3\dist\*" "%WEBDIST%\" /E /Y /I >nul
echo [3/4] git add and commit web-dist ...
cd /d %REPO%
git add web-dist
git commit -m "frontend build dist update"
echo [4/4] git push ...
git push
if errorlevel 1 ( echo [ERROR] git push failed & pause & exit /b 1 )
echo.
echo ===== FRONTEND BUILT AND PUSHED. Now run deploy-frontend.bat on the server. =====
endlocal
pause

26
build.bat Normal file
View File

@@ -0,0 +1,26 @@
@echo off
setlocal
echo ========================================
echo QH-MES Backend Quick Build
echo ========================================
cd /d %~dp0jeecg-boot
echo [1/2] Building start module and its dependencies...
call mvn clean package -pl jeecg-module-system/jeecg-system-start -am -DskipTests -T 1C
if errorlevel 1 (
echo.
echo [FAILED] Build error, please check the log above.
pause
exit /b 1
)
echo.
echo [2/2] Build SUCCESS!
echo Output: %~dp0jeecg-boot\jeecg-module-system\jeecg-system-start\target\jeecg-system-start-3.9.2.jar
echo.
explorer "%~dp0jeecg-boot\jeecg-module-system\jeecg-system-start\target"
endlocal
pause

27
deploy-frontend.bat Normal file
View File

@@ -0,0 +1,27 @@
@echo off
setlocal
set SRC=D:\qhmes-src
set GIT="C:\Program Files\Git\cmd\git.exe"
set WEB=D:\qhmes\nginx-1.30.1\html\jeecg
set NGINX_DIR=D:\qhmes\nginx-1.30.1
echo [1/3] git pull ...
cd /d %SRC%
%GIT% pull
if errorlevel 1 ( echo [ERROR] git pull failed & pause & exit /b 1 )
if not exist "%SRC%\web-dist\index.html" ( echo [ERROR] web-dist not found in repo, run build-frontend.bat first & pause & exit /b 1 )
echo [2/3] replace nginx frontend files ...
if exist "%WEB%" rmdir /S /Q "%WEB%"
mkdir "%WEB%"
xcopy "%SRC%\web-dist\*" "%WEB%\" /E /Y /I >nul
if errorlevel 1 ( echo [ERROR] copy failed & pause & exit /b 1 )
echo [3/3] reload nginx (ignore error if nginx not running) ...
cd /d %NGINX_DIR%
nginx.exe -s reload
echo.
echo ===== FRONTEND DEPLOY DONE =====
endlocal
pause

36
deploy-server.bat Normal file
View File

@@ -0,0 +1,36 @@
@echo off
setlocal
set SRC=D:\qhmes-src
set GIT="C:\Program Files\Git\cmd\git.exe"
set DEPLOY_DIR=D:\qhmes
set JAR_NAME=jeecg-system-start-3.9.2.jar
set SVC=D:\qhmes\qhmes-service.exe
set BUILT_JAR=%SRC%\jeecg-boot\jeecg-module-system\jeecg-system-start\target\%JAR_NAME%
echo [1/5] git pull ...
cd /d %SRC%
%GIT% pull
if errorlevel 1 ( echo [ERROR] git pull failed & pause & exit /b 1 )
echo [2/5] maven package with prod profile ...
cd /d %SRC%\jeecg-boot
call mvn clean package -pl jeecg-module-system/jeecg-system-start -am -DskipTests -P prod -T 1C
if errorlevel 1 ( echo [ERROR] build failed & pause & exit /b 1 )
if not exist "%BUILT_JAR%" ( echo [ERROR] built jar not found & pause & exit /b 1 )
echo [3/5] stop service ...
"%SVC%" stop
timeout /t 6 /nobreak >nul
echo [4/5] copy new jar ...
copy /Y "%BUILT_JAR%" "%DEPLOY_DIR%\%JAR_NAME%"
if errorlevel 1 ( echo [ERROR] copy failed, jar locked? & pause & exit /b 1 )
echo [5/5] start service ...
"%SVC%" start
echo.
echo ===== DEPLOY DONE =====
echo Check log: powershell -Command "Get-Content D:\qhmes\logs\qhmes-service.out.log -Wait -Tail 50"
endlocal
pause

View File

@@ -0,0 +1,282 @@
# QH-MES 部署与热部署方案
> 本文档记录 QH-MES(jeecg-boot 3.9.2)在 Windows Server 上的部署方案,包含后端、前端的一键发版流程、关键配置、踩过的坑,以及未来上线客户正式服务器的规划。
> 最后更新:2026-06-17
---
## 一、环境概况
### 测试服务器(当前)
- **系统**:Windows Server 2016 Standard
- **JDK**:17.0.11(`C:\Program Files\Java\jdk-17`,注意 `JAVA_HOME` 要指根目录,**不能带 `\bin`**)
- **Maven**:3.9.8(`D:\apache-maven-3.9.8`,从本地 Win11 拷过去的,因服务器网络受限无法在线下载)
- **Git**:已装在 `C:\Program Files\Git`,但不在 PATH(用完整路径 `"C:\Program Files\Git\cmd\git.exe"` 调用)
- **Gitea**:跑在**同一台机器**,裸仓库在 `D:\gitea\data\gitea-repositories\chenx\qhmes.git`,Web 地址 `http://27.223.88.102:33000/chenx/qhmes`
- **Nginx**:1.30.1,装在 `D:\qhmes\nginx-1.30.1\`,前端文件目录 `D:\qhmes\nginx-1.30.1\html\jeecg\`
- **Node/pnpm/npm**:**服务器上没装**(前端不在服务器构建)
### 关键路径
| 用途 | 路径 |
|------|------|
| 后端运行目录(jar + WinSW 服务) | `D:\qhmes\` |
| 后端运行 jar | `D:\qhmes\jeecg-system-start-3.9.2.jar` |
| 服务器源码工作副本 | `D:\qhmes-src\`(从本地裸仓库 clone,分支 `main`) |
| 前端 nginx 目录 | `D:\qhmes\nginx-1.30.1\html\jeecg\` |
| WinSW 服务程序/配置 | `D:\qhmes\qhmes-service.exe` / `qhmes-service.xml` |
| 后端发版脚本 | `D:\deploy-server.bat`(放源码目录外,避免 git pull 自我覆盖) |
### 本地开发机(Win11)
- 项目路径:`D:\XSL-PROJECT\QH-MES\qhmes`
- 后端模块:`jeecg-boot/`,前端:`jeecgboot-vue3/`(同一个 git 仓库)
- 传文件到服务器的方式:**向日葵远程传输**(只能手动,不能脚本化)
---
## 二、后端部署(已跑通 ✅)
### 2.1 后端做成 Windows 服务(WinSW)
后端不再手动 `java -jar`(关窗口/断 RDP 就掉),改用 **WinSW** 注册成 Windows 服务,开机自启、崩溃自动重启。
- 服务 id:`qhmes`,名称:`QH-MES Backend`
- 配置文件 `D:\qhmes\qhmes-service.xml` 关键内容:
```xml
<executable>java</executable>
<arguments>-Xms1g -Xmx2g -jar "D:\qhmes\jeecg-system-start-3.9.2.jar"</arguments>
<workingdirectory>D:\qhmes</workingdirectory>
<logpath>D:\qhmes\logs</logpath>
<onfailure action="restart" delay="10 sec"/>
```
- **注意**:`<arguments>` 里**不加** `--spring.profiles.active`,因为 profile 已在打包时烤进 jar(见第四节)。
- 常用命令(管理员):
```
D:\qhmes\qhmes-service.exe install # 安装服务(一次性)
D:\qhmes\qhmes-service.exe start # 启动
D:\qhmes\qhmes-service.exe stop # 停止
```
- 查日志:
```powershell
Get-Content D:\qhmes\logs\qhmes-service.out.log -Wait -Tail 60
```
启动成功标志:`Started JeecgSystemApplication in xx seconds`,Tomcat 监听 8888。
### 2.2 一键发版脚本 `D:\deploy-server.bat`
流程:`git pull → mvn 打包(prod) → 停服务 → 换 jar → 启服务`。脚本自带 `git pull`,**双击即自动拉最新代码**。
```bat
@echo off
setlocal
set SRC=D:\qhmes-src
set GIT="C:\Program Files\Git\cmd\git.exe"
set DEPLOY_DIR=D:\qhmes
set JAR_NAME=jeecg-system-start-3.9.2.jar
set SVC=D:\qhmes\qhmes-service.exe
set BUILT_JAR=%SRC%\jeecg-boot\jeecg-module-system\jeecg-system-start\target\%JAR_NAME%
echo [1/5] git pull ...
cd /d %SRC%
%GIT% pull
if errorlevel 1 ( echo [ERROR] git pull failed & pause & exit /b 1 )
echo [2/5] maven package with prod profile ...
cd /d %SRC%\jeecg-boot
call mvn clean package -pl jeecg-module-system/jeecg-system-start -am -DskipTests -P prod -T 1C
if errorlevel 1 ( echo [ERROR] build failed & pause & exit /b 1 )
if not exist "%BUILT_JAR%" ( echo [ERROR] built jar not found & pause & exit /b 1 )
echo [3/5] stop service ...
"%SVC%" stop
timeout /t 6 /nobreak >nul
echo [4/5] copy new jar ...
copy /Y "%BUILT_JAR%" "%DEPLOY_DIR%\%JAR_NAME%"
if errorlevel 1 ( echo [ERROR] copy failed, jar locked? & pause & exit /b 1 )
echo [5/5] start service ...
"%SVC%" start
echo ===== DEPLOY DONE =====
endlocal
pause
```
### 2.3 后端发版流程
1. 本地改代码 → `git push`(推到 `main`)
2. RDP/向日葵到服务器 → 双击 `D:\deploy-server.bat`
3. 看日志确认 `Started ... in xx seconds`
> 首次构建会下载几百 MB 依赖(约 8 分钟),缓存在 `D:\maven-repo`,之后每次只需 1~3 分钟。
---
## 三、前端部署(脚本已就绪)
前端 `jeecgboot-vue3` 用 **pnpm** 构建,产物 `dist/`,放到 nginx 的 `html\jeecg\`。
因服务器没 Node 且网络受限,**前端不在服务器构建**,而是:**本地构建 → dist 走 git → 服务器拉取替换**。
> `jeecgboot-vue3/dist` 被 `.gitignore` 忽略,所以构建后复制到根目录 **`web-dist`** 文件夹(未被忽略)再提交。
### 3.1 本地构建脚本 `build-frontend.bat`(本地 Win11 双击)
核心构建步骤与官网一致:`pnpm install` + `pnpm run build`,之后自动复制 dist 到 web-dist 并 git push。
```bat
@echo off
setlocal
set REPO=%~dp0
set WEBDIST=%REPO%web-dist
echo [1/4] build frontend (pnpm install + build) ...
cd /d %REPO%jeecgboot-vue3
call pnpm install
if errorlevel 1 ( echo [ERROR] pnpm install failed & pause & exit /b 1 )
call pnpm run build
if errorlevel 1 ( echo [ERROR] frontend build failed & pause & exit /b 1 )
if not exist "%REPO%jeecgboot-vue3\dist\index.html" ( echo [ERROR] dist/index.html not found & pause & exit /b 1 )
echo [2/4] refresh web-dist folder ...
if exist "%WEBDIST%" rmdir /S /Q "%WEBDIST%"
mkdir "%WEBDIST%"
xcopy "%REPO%jeecgboot-vue3\dist\*" "%WEBDIST%\" /E /Y /I >nul
echo [3/4] git add and commit web-dist ...
cd /d %REPO%
git add web-dist
git commit -m "frontend build dist update"
echo [4/4] git push ...
git push
if errorlevel 1 ( echo [ERROR] git push failed & pause & exit /b 1 )
echo ===== FRONTEND BUILT AND PUSHED =====
endlocal
pause
```
### 3.2 服务器部署脚本 `D:\deploy-frontend.bat`(服务器双击)
```bat
@echo off
setlocal
set SRC=D:\qhmes-src
set GIT="C:\Program Files\Git\cmd\git.exe"
set WEB=D:\qhmes\nginx-1.30.1\html\jeecg
set NGINX_DIR=D:\qhmes\nginx-1.30.1
echo [1/3] git pull ...
cd /d %SRC%
%GIT% pull
if errorlevel 1 ( echo [ERROR] git pull failed & pause & exit /b 1 )
if not exist "%SRC%\web-dist\index.html" ( echo [ERROR] web-dist not found, run build-frontend.bat first & pause & exit /b 1 )
echo [2/3] replace nginx frontend files ...
if exist "%WEB%" rmdir /S /Q "%WEB%"
mkdir "%WEB%"
xcopy "%SRC%\web-dist\*" "%WEB%\" /E /Y /I >nul
if errorlevel 1 ( echo [ERROR] copy failed & pause & exit /b 1 )
echo [3/3] reload nginx (ignore error if not running) ...
cd /d %NGINX_DIR%
nginx.exe -s reload
echo ===== FRONTEND DEPLOY DONE =====
endlocal
pause
```
> **nginx 不用重启**:静态文件替换后自动生效。`nginx -s reload` 是平滑重载,**不要直接跑 `nginx.exe`**(会报端口占用)。
### 3.3 前端发版流程(两步双击)
1. 本地双击 `build-frontend.bat`(构建 + 推送 web-dist)
2. 服务器双击 `D:\deploy-frontend.bat`(拉取 + 替换 + reload)
3. 刷新浏览器
---
## 四、配置(profile)说明 —— 重点
### 4.1 哪个配置生效,由打包时的 Maven `-P` 决定
`application.yml``spring.profiles.active: '@profile.name@'` 是 Maven 占位符,打包时填入:
| 打包命令 | 生效配置 |
|---------|---------|
| `mvn package`(不带 -P) | **dev**(默认,`<activeByDefault>` 在 dev 上) |
| `mvn package -P prod` | **prod** |
| `mvn package -P test` | test |
> 发版脚本用的是 **`-P prod`**,所以烤进 jar 的是 prod 配置,运行时无需再加 `--spring.profiles.active`。
### 4.2 判断一个 jar 用的哪个配置
- 看启动日志:`The following 1 profile is active: "prod"`
- 解压 jar 看 `BOOT-INF/classes/application.yml``active` 的实际值
- 运行时 `--spring.profiles.active=xxx` 可强制覆盖
### 4.3 dev vs prod 的区别(本项目)
| 项 | dev | prod(当前测试服务器用) |
|----|-----|------|
| 端口 | 8888 | 8888(已改成与现网一致) |
| MySQL | `xsl.qdxsl.top:50768`(公网绕一圈) | `127.0.0.1:3306`(本机直连) |
| MySQL 密码 | 123456 | 123456(已改,原为 root) |
> **重要事实**:`xsl.qdxsl.top:50768` 与本机 `127.0.0.1:3306` 是**同一个生产库**(公网域名+端口转发到本机)。prod 走本机直连更快更稳。
> prod 配置的修改在 `application-prod.yml`,已带 `update-begin/update-end` 痕迹注释。
---
## 五、踩过的坑(避免重复)
1. **bat 文件乱码/无法执行**:bat 里写中文 + 存成 UTF-8,cmd 按 GBK 读会乱码,连 `@echo off` 都坏。→ **bat 只用英文 ASCII,不带 BOM**
2. **WinSW 报 `Invalid character in encoding`**:xml 里中文存成了 ANSI/GBK。→ 用 UTF-8 保存,或描述改英文。
3. **服务用 prod 启动报 `Access denied for user 'root'`**:prod 密码原写的 `root`,实际应为 `123456`
4. **PowerShell 下载报 `未能创建 SSL/TLS 安全通道`**:Server 2016 默认 TLS 1.0。→ 先 `[Net.ServicePointManager]::SecurityProtocol = ... -bor 3072` 开启 TLS 1.2。
5. **服务器下载被 `127.0.0.1:10080` 的本机 Apache 拦截 404**:服务器网络有本机代理/DNS 劫持,外网下载不通。→ Maven/Node 等**在本地下好,向日葵拷过去**。
6. **`mvn``JAVA_HOME not defined correctly`**:`JAVA_HOME` 误设成了 `...\jdk-17\bin`。→ 应为根目录 `...\jdk-17`
7. **`git clone D:\gitea\...qhmes.git` 拿到的没有源码**:那是 Gitea 裸仓库(只有 git 底层数据)。→ `git clone` 它到 `D:\qhmes-src` 得到工作副本。
8. **cmd 里 `cd D:\xxx` 不切盘**:要用 `cd /d D:\xxx`;`&` 是 PowerShell 语法,cmd 里不能用。
9. **PowerShell 粘贴 here-string 卡在 `>>`**:终止符 `'@` 没识别。→ 大段内容改用**记事本**另存为(All Files + ANSI),或写进 git 拉取。
---
## 六、未来上线客户正式服务器的规划(待落地)
**核心原则:测试服务器可以 git 拉源码+构建;客户正式服务器只部署"已构建的成品",不放源码、不装构建工具、不依赖我方 gitea。**
### 6.1 目标流程
```
我方(测试服务器/开发机):打包出成品(jar + 前端dist)并测好
↓ 向日葵/U盘/网盘 传过去
客户正式服务器:双击 deploy.bat → 停服务 → 换jar → 换前端 → 启服务
```
### 6.2 必做改造:配置外置(同一 jar 走天下)
现在 prod 配置(数据库/IP/密码)烤死在 jar 里,是测试服务器的。客户的库不同,需把配置挪到 jar 外:
```
D:\qhmes\
├── jeecg-system-start-3.9.2.jar ← 所有环境通用,不用为每个客户重打
└── config\
└── application-prod.yml ← 本机专属:客户的数据库/IP/密码
```
Spring Boot 自动优先读 jar 同级 `config\` 目录的配置(外部 > jar 内 classpath)。
### 6.3 待办(上线时找 Claude 做)
1. 把 prod 配置从 jar 内挪到外部 `config\`,jar 变环境无关
2. 写"打 release 包"脚本:一键产出 `jar + 前端dist + deploy.bat` 发布包
3. 写客户服务器 `deploy.bat`:只"换文件+重启",不构建、不拉源码
4. (建议)测试服务器也提前切到外置配置,与客户环境保持一致,上线零改动
---
## 七、快速命令速查
```powershell
# 后端发版
D:\deploy-server.bat # 服务器双击
# 前端发版
build-frontend.bat # 本地 Win11 双击
D:\deploy-frontend.bat # 服务器双击
# 看后端日志
Get-Content D:\qhmes\logs\qhmes-service.out.log -Wait -Tail 60
# 后端服务控制(管理员)
D:\qhmes\qhmes-service.exe stop|start|status
# nginx 平滑重载(在 nginx 目录下)
cd /d D:\qhmes\nginx-1.30.1
nginx.exe -s reload
```

View File

@@ -0,0 +1,60 @@
-- MES 设备报警记录设备停机记录菜单 + 导出按钮 + 租户 admin 授权 MySQL 业务表
-- 权限前缀mes:mes_xsl_equip_alarm_record:* / mes:mes_xsl_equip_downtime_record:*
-- 父菜单设备管理FlywayV3.9.2_124__mes_xsl_equip_mcs_alarm_downtime_menu.sql
SET NAMES utf8mb4;
SET @mes_tenant_id = 1002;
SET @mes_equip_pid = (
SELECT `id` FROM `sys_permission`
WHERE `del_flag` = 0 AND `menu_type` = 0 AND `name` = '设备管理'
LIMIT 1
);
SET @mes_equip_pid = IFNULL(@mes_equip_pid, '1860000000000000133');
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `keep_alive`, `internal_or_external`, `create_by`, `create_time`)
VALUES ('1860000000000000222', @mes_equip_pid, '设备报警记录', '/xslmes/mesXslEquipAlarmRecord', 'xslmes/mesXslEquipAlarmRecord/MesXslEquipAlarmRecordList', 'MesXslEquipAlarmRecordList', 1, NULL, '1', 13, 1, 0, 0, '1', 0, 1, 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `url` = VALUES(`url`), `component` = VALUES(`component`), `component_name` = VALUES(`component_name`),
`menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`), `sort_no` = VALUES(`sort_no`),
`is_route` = VALUES(`is_route`), `is_leaf` = VALUES(`is_leaf`), `hidden` = VALUES(`hidden`), `status` = VALUES(`status`), `del_flag` = VALUES(`del_flag`),
`keep_alive` = VALUES(`keep_alive`), `internal_or_external` = VALUES(`internal_or_external`);
UPDATE `sys_permission` SET `icon` = 'ant-design:alert-outlined' WHERE `id` = '1860000000000000222' AND `del_flag` = 0;
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`) VALUES
('1860000000000000223', '1860000000000000222', '导出', 2, 'mes:mes_xsl_equip_alarm_record:exportXls', '1', '1', 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`),
`status` = VALUES(`status`), `del_flag` = VALUES(`del_flag`);
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `keep_alive`, `internal_or_external`, `create_by`, `create_time`)
VALUES ('1860000000000000224', @mes_equip_pid, '设备停机记录', '/xslmes/mesXslEquipDowntimeRecord', 'xslmes/mesXslEquipDowntimeRecord/MesXslEquipDowntimeRecordList', 'MesXslEquipDowntimeRecordList', 1, NULL, '1', 15, 1, 0, 0, '1', 0, 1, 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `url` = VALUES(`url`), `component` = VALUES(`component`), `component_name` = VALUES(`component_name`),
`menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`), `sort_no` = VALUES(`sort_no`),
`is_route` = VALUES(`is_route`), `is_leaf` = VALUES(`is_leaf`), `hidden` = VALUES(`hidden`), `status` = VALUES(`status`), `del_flag` = VALUES(`del_flag`),
`keep_alive` = VALUES(`keep_alive`), `internal_or_external` = VALUES(`internal_or_external`);
UPDATE `sys_permission` SET `icon` = 'ant-design:pause-circle-outlined' WHERE `id` = '1860000000000000224' AND `del_flag` = 0;
INSERT INTO `sys_permission`(`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `status`, `del_flag`, `create_by`, `create_time`) VALUES
('1860000000000000225', '1860000000000000224', '导出', 2, 'mes:mes_xsl_equip_downtime_record:exportXls', '1', '1', 0, 'admin', NOW())
ON DUPLICATE KEY UPDATE
`parent_id` = VALUES(`parent_id`), `name` = VALUES(`name`), `menu_type` = VALUES(`menu_type`), `perms` = VALUES(`perms`), `perms_type` = VALUES(`perms_type`),
`status` = VALUES(`status`), `del_flag` = VALUES(`del_flag`);
INSERT INTO `sys_role_permission`(`id`, `role_id`, `permission_id`, `operate_date`, `operate_ip`)
SELECT REPLACE(UUID(), '-', ''), r.`id`, p.`id`, NOW(), '127.0.0.1'
FROM `sys_role` r
CROSS JOIN `sys_permission` p
WHERE r.`tenant_id` = @mes_tenant_id
AND r.`role_code` = 'admin'
AND p.`id` IN (
'1860000000000000222', '1860000000000000223',
'1860000000000000224', '1860000000000000225'
)
AND NOT EXISTS (
SELECT 1 FROM `sys_role_permission` rp
WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
);

View File

@@ -0,0 +1,79 @@
-- 密炼生产计划维护 菜单与权限挂载到 MES密炼工程
SET NAMES utf8mb4;
SET @mixer_parent_id = (
SELECT id
FROM sys_permission
WHERE name = 'MES密炼工程' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
);
SET @mixer_parent_id = IFNULL(@mixer_parent_id, (
SELECT id
FROM sys_permission
WHERE url = '/mes' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
));
SET @mixer_parent_id = IFNULL(@mixer_parent_id, '1860000000000000001');
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1860000000000000301',@mixer_parent_id,'密炼生产计划维护','/mes/mixingproductionplaninfo','mes/mixingproductionplaninfo/index','MesXslMixingProductionPlanList',1,NULL,'1',90,0,'ant-design:calendar-outlined',1,1,1,0,0,'密炼生产计划维护',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),
`name`=VALUES(`name`),
`url`=VALUES(`url`),
`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),
`menu_type`=VALUES(`menu_type`),
`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),
`is_leaf`=VALUES(`is_leaf`),
`keep_alive`=VALUES(`keep_alive`),
`icon`=VALUES(`icon`),
`status`=VALUES(`status`),
`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1860000000000000302','1860000000000000301','查询',2,'xslmes:mes_xsl_mixing_production_plan:list','1',1,'1',0,'admin',NOW()),
('1860000000000000303','1860000000000000301','整表保存',2,'xslmes:mes_xsl_mixing_production_plan:saveAll','1',2,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),
`perms`=VALUES(`perms`),
`sort_no`=VALUES(`sort_no`),
`status`=VALUES(`status`),
`del_flag`=VALUES(`del_flag`);
-- admin 角色授权
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
FROM sys_permission p
WHERE p.id IN (
'1860000000000000301',
'1860000000000000302', '1860000000000000303'
)
AND NOT EXISTS (
SELECT 1
FROM sys_role_permission rp
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b'
AND rp.permission_id = p.id
);
-- 强制修复确保菜单路由与组件路径正确
UPDATE sys_permission
SET
parent_id = @mixer_parent_id,
url = '/mes/mixingproductionplaninfo',
component = 'mes/mixingproductionplaninfo/index',
component_name = 'MesXslMixingProductionPlanList',
menu_type = 1,
is_route = 1,
is_leaf = 1,
hidden = 0,
status = '1',
del_flag = 0
WHERE id = '1860000000000000301';

View File

@@ -0,0 +1,78 @@
-- 原材料需求计划 菜单与权限挂载到 MES密炼工程兼容 MES管理
SET NAMES utf8mb4;
SET @raw_parent_id = (
SELECT id
FROM sys_permission
WHERE name = 'MES密炼工程' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
);
SET @raw_parent_id = IFNULL(@raw_parent_id, (
SELECT id
FROM sys_permission
WHERE url = '/mes' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
));
SET @raw_parent_id = IFNULL(@raw_parent_id, '1860000000000000001');
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1900000000000000710',@raw_parent_id,'原材料需求计划','/mes/rawmaterialdemandplan','mes/rawmaterialdemandplan/index','MesXslRawMaterialDemandPlanList',1,NULL,'1',95,0,'ant-design:ordered-list-outlined',1,1,1,0,0,'原材料需求计划',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),
`name`=VALUES(`name`),
`url`=VALUES(`url`),
`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),
`menu_type`=VALUES(`menu_type`),
`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),
`is_leaf`=VALUES(`is_leaf`),
`keep_alive`=VALUES(`keep_alive`),
`icon`=VALUES(`icon`),
`status`=VALUES(`status`),
`hidden`=VALUES(`hidden`),
`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1900000000000000711','1900000000000000710','导出',2,'xslmes:mes_xsl_raw_material_demand_plan:exportXls','1',1,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),
`perms`=VALUES(`perms`),
`sort_no`=VALUES(`sort_no`),
`status`=VALUES(`status`),
`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
FROM sys_permission p
WHERE p.id IN ('1900000000000000710', '1900000000000000711')
AND NOT EXISTS (
SELECT 1
FROM sys_role_permission rp
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b'
AND rp.permission_id = p.id
);
-- 强制修复 ID + 名称 + URL 三重兜底确保能显示
UPDATE sys_permission
SET
parent_id = @raw_parent_id,
url = '/mes/rawmaterialdemandplan',
component = 'mes/rawmaterialdemandplan/index',
component_name = 'MesXslRawMaterialDemandPlanList',
menu_type = 1,
is_route = 1,
is_leaf = 1,
hidden = 0,
status = '1',
del_flag = 0,
redirect = NULL
WHERE id = '1900000000000000710'
OR name = '原材料需求计划'
OR url = '/mes/rawmaterialdemandplan';

View File

@@ -0,0 +1,76 @@
-- 自动/人工小料需求计划 菜单与权限挂载到 MES密炼工程兼容 MES管理
SET NAMES utf8mb4;
SET @small_parent_id = (
SELECT id
FROM sys_permission
WHERE name = 'MES密炼工程' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
);
SET @small_parent_id = IFNULL(@small_parent_id, (
SELECT id
FROM sys_permission
WHERE url = '/mes' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
));
SET @small_parent_id = IFNULL(@small_parent_id, '1860000000000000001');
-- 自动小料需求计划
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1900000000000000720',@small_parent_id,'自动小料需求计划','/mes/autosmallmaterialdemandplan','mes/autosmallmaterialdemandplan/index','MesXslAutoSmallMaterialDemandPlanList',1,NULL,'1',96,0,'ant-design:ordered-list-outlined',1,1,1,0,0,'自动小料需求计划',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),`name`=VALUES(`name`),`url`=VALUES(`url`),`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),`menu_type`=VALUES(`menu_type`),`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),`is_leaf`=VALUES(`is_leaf`),`keep_alive`=VALUES(`keep_alive`),`icon`=VALUES(`icon`),
`status`=VALUES(`status`),`hidden`=VALUES(`hidden`),`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1900000000000000721','1900000000000000720','导出',2,'xslmes:mes_xsl_auto_small_material_demand_plan:exportXls','1',1,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),`perms`=VALUES(`perms`),`sort_no`=VALUES(`sort_no`),`status`=VALUES(`status`),`del_flag`=VALUES(`del_flag`);
-- 人工小料需求计划
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1900000000000000730',@small_parent_id,'人工小料需求计划','/mes/manualsmallmaterialdemandplan','mes/manualsmallmaterialdemandplan/index','MesXslManualSmallMaterialDemandPlanList',1,NULL,'1',97,0,'ant-design:ordered-list-outlined',1,1,1,0,0,'人工小料需求计划',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),`name`=VALUES(`name`),`url`=VALUES(`url`),`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),`menu_type`=VALUES(`menu_type`),`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),`is_leaf`=VALUES(`is_leaf`),`keep_alive`=VALUES(`keep_alive`),`icon`=VALUES(`icon`),
`status`=VALUES(`status`),`hidden`=VALUES(`hidden`),`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1900000000000000731','1900000000000000730','导出',2,'xslmes:mes_xsl_manual_small_material_demand_plan:exportXls','1',1,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),`perms`=VALUES(`perms`),`sort_no`=VALUES(`sort_no`),`status`=VALUES(`status`),`del_flag`=VALUES(`del_flag`);
-- admin 角色授权
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
FROM sys_permission p
WHERE p.id IN ('1900000000000000720','1900000000000000721','1900000000000000730','1900000000000000731')
AND NOT EXISTS (
SELECT 1 FROM sys_role_permission rp
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b'
AND rp.permission_id = p.id
);
-- 强制修复
UPDATE sys_permission
SET parent_id=@small_parent_id,url='/mes/autosmallmaterialdemandplan',component='mes/autosmallmaterialdemandplan/index',
component_name='MesXslAutoSmallMaterialDemandPlanList',menu_type=1,is_route=1,is_leaf=1,hidden=0,status='1',del_flag=0,redirect=NULL
WHERE id='1900000000000000720' OR name='自动小料需求计划' OR url='/mes/autosmallmaterialdemandplan';
UPDATE sys_permission
SET parent_id=@small_parent_id,url='/mes/manualsmallmaterialdemandplan',component='mes/manualsmallmaterialdemandplan/index',
component_name='MesXslManualSmallMaterialDemandPlanList',menu_type=1,is_route=1,is_leaf=1,hidden=0,status='1',del_flag=0,redirect=NULL
WHERE id='1900000000000000730' OR name='人工小料需求计划' OR url='/mes/manualsmallmaterialdemandplan';

View File

@@ -0,0 +1,75 @@
-- 自动/人工小料计划维护 菜单与权限挂载到 MES密炼工程兼容 MES管理
SET NAMES utf8mb4;
SET @plan_parent_id = (
SELECT id
FROM sys_permission
WHERE name = 'MES密炼工程' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
);
SET @plan_parent_id = IFNULL(@plan_parent_id, (
SELECT id
FROM sys_permission
WHERE url = '/mes' AND menu_type = 0 AND del_flag = 0
ORDER BY create_time ASC
LIMIT 1
));
SET @plan_parent_id = IFNULL(@plan_parent_id, '1860000000000000001');
-- 自动小料计划维护
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1900000000000000740',@plan_parent_id,'自动小料计划维护','/mes/autosmallmaterialplanmaintain','mes/autosmallmaterialplanmaintain/index','MesXslAutoSmallMaterialPlanMaintainList',1,NULL,'1',98,0,'ant-design:table-outlined',1,1,1,0,0,'自动小料计划维护',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),`name`=VALUES(`name`),`url`=VALUES(`url`),`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),`menu_type`=VALUES(`menu_type`),`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),`is_leaf`=VALUES(`is_leaf`),`keep_alive`=VALUES(`keep_alive`),`icon`=VALUES(`icon`),
`status`=VALUES(`status`),`hidden`=VALUES(`hidden`),`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1900000000000000741','1900000000000000740','整表保存',2,'xslmes:mes_xsl_auto_small_material_plan_maintain:saveAll','1',1,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),`perms`=VALUES(`perms`),`sort_no`=VALUES(`sort_no`),`status`=VALUES(`status`),`del_flag`=VALUES(`del_flag`);
-- 人工小料计划维护
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`url`,`component`,`component_name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`always_show`,`icon`,`is_route`,`is_leaf`,`keep_alive`,`hidden`,`hide_tab`,`description`,`status`,`del_flag`,`create_by`,`create_time`,`rule_flag`,`internal_or_external`)
VALUES
('1900000000000000750',@plan_parent_id,'人工小料计划维护','/mes/manualsmallmaterialplanmaintain','mes/manualsmallmaterialplanmaintain/index','MesXslManualSmallMaterialPlanMaintainList',1,NULL,'1',99,0,'ant-design:table-outlined',1,1,1,0,0,'人工小料计划维护',1,0,'admin',NOW(),0,0)
ON DUPLICATE KEY UPDATE
`parent_id`=VALUES(`parent_id`),`name`=VALUES(`name`),`url`=VALUES(`url`),`component`=VALUES(`component`),
`component_name`=VALUES(`component_name`),`menu_type`=VALUES(`menu_type`),`sort_no`=VALUES(`sort_no`),
`is_route`=VALUES(`is_route`),`is_leaf`=VALUES(`is_leaf`),`keep_alive`=VALUES(`keep_alive`),`icon`=VALUES(`icon`),
`status`=VALUES(`status`),`hidden`=VALUES(`hidden`),`del_flag`=VALUES(`del_flag`);
INSERT INTO sys_permission
(`id`,`parent_id`,`name`,`menu_type`,`perms`,`perms_type`,`sort_no`,`status`,`del_flag`,`create_by`,`create_time`)
VALUES
('1900000000000000751','1900000000000000750','整表保存',2,'xslmes:mes_xsl_manual_small_material_plan_maintain:saveAll','1',1,'1',0,'admin',NOW())
ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`),`perms`=VALUES(`perms`),`sort_no`=VALUES(`sort_no`),`status`=VALUES(`status`),`del_flag`=VALUES(`del_flag`);
-- admin授权
INSERT INTO sys_role_permission(id, role_id, permission_id, operate_date, operate_ip)
SELECT REPLACE(UUID(), '-', ''), 'f6817f48af4fb3af11b9e8bf182f618b', p.id, NOW(), '127.0.0.1'
FROM sys_permission p
WHERE p.id IN ('1900000000000000740','1900000000000000741','1900000000000000750','1900000000000000751')
AND NOT EXISTS (
SELECT 1 FROM sys_role_permission rp
WHERE rp.role_id = 'f6817f48af4fb3af11b9e8bf182f618b' AND rp.permission_id = p.id
);
-- 强制修正
UPDATE sys_permission
SET parent_id=@plan_parent_id,url='/mes/autosmallmaterialplanmaintain',component='mes/autosmallmaterialplanmaintain/index',
component_name='MesXslAutoSmallMaterialPlanMaintainList',menu_type=1,is_route=1,is_leaf=1,hidden=0,status='1',del_flag=0,redirect=NULL
WHERE id='1900000000000000740' OR name='自动小料计划维护' OR url='/mes/autosmallmaterialplanmaintain';
UPDATE sys_permission
SET parent_id=@plan_parent_id,url='/mes/manualsmallmaterialplanmaintain',component='mes/manualsmallmaterialplanmaintain/index',
component_name='MesXslManualSmallMaterialPlanMaintainList',menu_type=1,is_route=1,is_leaf=1,hidden=0,status='1',del_flag=0,redirect=NULL
WHERE id='1900000000000000750' OR name='人工小料计划维护' OR url='/mes/manualsmallmaterialplanmaintain';

View File

@@ -217,6 +217,12 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/xslmes/mesXslUnit/anon/**", "anon");
// MES密炼物料管理免密接口供桌面端调用
filterChainDefinitionMap.put("/mes/material/mixerMaterial/anon/**", "anon");
// MES密炼生产计划维护免密接口供桌面端快检记录调用
filterChainDefinitionMap.put("/xslmes/mesXslMixingProductionPlan/anon/**", "anon");
// MES胶料快检实验标准免密接口供桌面端快检记录调用
filterChainDefinitionMap.put("/xslmes/mesXslRubberQuickTestStd/anon/**", "anon");
// MES胶料快检记录免密接口供桌面端快检记录调用
filterChainDefinitionMap.put("/xslmes/mesXslRubberQuickTestRecord/anon/**", "anon");
// 打印模板免密接口(供桌面端调用)
filterChainDefinitionMap.put("/print/template/anon/**", "anon");
filterChainDefinitionMap.put("/print/bizTemplateBind/anon/**", "anon");

View File

@@ -944,6 +944,47 @@ jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue
-- author:GHT---date:20260610--for: 【IM审批通用化】MES发起审批按钮按ding_tpl_bind路由匹配(与钉钉按钮一致) -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalLaunchController.java
jeecgboot-vue3/src/components/ApprovalLaunch/index.vue
jeecgboot-vue3/src/views/approval/flow/launch.api.ts
-- author:GHT---date:20260610--for: 【IM审批通用化】IM工作通知公众号(同事列表置顶+审批消息统一推送) -----
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_147__sys_im_work_notify_user.sql
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/vo/SysImContactVO.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/ISysImChatService.java
jeecgboot-vue3/src/views/system/im/ImChat.vue
jeecgboot-vue3/src/views/system/im/imCache.ts
jeecgboot-vue3/src/views/system/im/ImCreateGroupModal.vue
-- author:GHT---date:20260610--for: 【IM审批通用化】审批待办IM发送修复(admin自审重复成员+独立事务防审批回滚) -----
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
-- author:GHT---date:20260610--for: 【IM审批通用化】IM卡片字段取值修复(下划线列名+valueMode与钉钉发起对齐) -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplImCardBuilder.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplBindFieldValueResolver.java
-- author:GHT---date:20260610--for: 【IM审批通用化】流转中实例支持补发IM审批卡片(审批台账入口) -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/IMesXslApprovalHandleService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalHandleServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalHandleController.java
jeecgboot-vue3/src/views/approval/flow/approvalHandle.api.ts
jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue
-- author:GHT---date:20260610--for: 【IM审批通用化】发起人=处理人时审批待办改由admin代发IM消息 -----
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/ISysImChatService.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalHandleServiceImpl.java
-- author:GHT---date:20260610--for: 【IM审批通用化】IM卡片复用钉钉模板字段+MES回调补齐stageKey走集成方案 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplImCardBuilder.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/IMesXslDingTplBindService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/impl/MesXslDingTplBindServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalHandleServiceImpl.java
jeecgboot-vue3/src/views/system/im/imBizRecordMessage.ts
jeecgboot-vue3/src/views/system/im/ImBizRecordMessageContent.vue
-- author:GHT---date:20260610--for: 【混炼示方】TCU温度条件新增是否附加/重量字段 -----
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_146__mes_xsl_mixing_spec_tcu_attach.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixingSpecTcu.java
@@ -993,3 +1034,149 @@ jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules
jeecgboot-vue3/src/views/xslmes/mesXslEquipPartMapping/MesXslEquipPartMappingList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipPartMapping/MesXslEquipPartMapping.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipPartMapping/MesXslEquipPartMapping.api.ts
-- author:GHT---date:20260616--for: 【MES上辅机】SQL Server中间库可视化配置第三方配置页+读写开关+热刷新数据源) ---
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_150__mes_xsl_mcs_db_config.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/entity/MesXslMcsDbConfig.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/mapper/MesXslMcsDbConfigMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslMcsDbConfigService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsDbConfigServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/datasource/McsDataSourceManager.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/datasource/McsDataSourceGuardAspect.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/config/McsDataSourceInitializer.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/controller/MesXslMcsDbConfigController.java
jeecgboot-vue3/src/views/system/appconfig/ThirdAppConfigList.vue
jeecgboot-vue3/src/views/system/appconfig/ThirdAppMcsDbConfigForm.vue
jeecgboot-vue3/src/views/system/appconfig/McsDbConfigModal.vue
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.data.ts
jeecgboot-vue3/src/views/system/appconfig/McsDbConfig.api.ts
-- author:jiangxh---date:20250603--for: 【MES】设备/质量管理主数据删除前引用校验(统一点检配置等下游阻断) ---
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslDeleteReferenceService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslDeleteReferenceServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslInspectMaintainItemController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipmentCategoryController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipmentTypeController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipmentPartController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipmentSubPartController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipmentLedgerController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslManufacturerController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslSparePartsCategoryController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDowntimeMainTypeController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDowntimeTypeController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipInspectConfigController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRubberQuickTestTypeController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRubberQuickTestDataPointController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRubberQuickTestMethodController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRubberQuickTestStdController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRubberSmallLockReasonController.java
-- author:jiangxh---date:20250604--for: 【MES】设备报警记录与设备停机记录SQL Server MCSToMES_MixAlarm 只读、列表回写 ReadTime/MES_Flag ---
jeecg-boot/db/mes-xsl-equip-mcs-alarm-downtime-menu-permission.sql
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_124__mes_xsl_equip_mcs_alarm_downtime_menu.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/vo/MesXslEquipAlarmRecordVO.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/vo/MesXslEquipDowntimeRecordVO.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/util/MesXslMcsMixAlarmConvertUtil.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslEquipAlarmRecordService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslEquipDowntimeRecordService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsMixAlarmReadMarker.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslEquipAlarmRecordServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslEquipDowntimeRecordServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipAlarmRecordController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslEquipDowntimeRecordController.java
jeecgboot-vue3/src/views/xslmes/mesXslEquipAlarmRecord/MesXslEquipAlarmRecord.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipAlarmRecord/MesXslEquipAlarmRecord.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipAlarmRecord/MesXslEquipAlarmRecordList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipAlarmRecord/components/MesXslEquipAlarmRecordModal.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipDowntimeRecord/MesXslEquipDowntimeRecord.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipDowntimeRecord/MesXslEquipDowntimeRecord.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslEquipDowntimeRecord/MesXslEquipDowntimeRecordList.vue
jeecgboot-vue3/src/views/xslmes/mesXslEquipDowntimeRecord/components/MesXslEquipDowntimeRecordModal.vue
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/datasource/McsDataSourceManager.java
-- author:GHT---date:20260616--for: 【MES上辅机】重启后中间库配置未自动加载租户ID硬编码为0---
原因McsDataSourceInitializer 启动时只查 tenant_id=0页面保存用当前租户如1002重启后查不到配置连接状态回退为 yml。
修改:新增 loadStartupConfig 按最近更新时间加载任意租户配置isDbConfigActive 同时校验数据源是否已注册。
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslMcsDbConfigService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsDbConfigServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/config/McsDataSourceInitializer.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/datasource/McsDataSourceManager.java
-- author:jiangxh---date:20260617--for: 【快检实验标准】补齐 tenant_id 为空的历史数据并恢复租户过滤 ---
原因:一条快检实验标准 tenant_id 为 NULL桌面端按 tenantId=1002 拉取时漏掉该条。
修改Flyway 将 NULL tenant_id 对齐为 1002新增保存时自动写入 tenant_id恢复 anon/list 与桌面端 tenantId 查询参数。
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_152__mes_xsl_rubber_quick_test_std_tenant_backfill.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslDesktopAnonController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRubberQuickTestStdServiceImpl.java
yy-admin-master/YY.Admin.Services/Service/RubberQuickTestStd/RubberQuickTestStdService.cs
-- author:GHT---date:20260617--for: 【MES上辅机】密炼动作秒级采集 + 通用中间表采集配置 ---
需求:密炼机动作维护数据从中间表 MCSToMES_MixAct 秒级采集(机台名称→设备名称、动作名称→动作名称、动作地址→动作代号),
在「密炼动作」页支持 启动/停止采集与设置时间间隔默认1秒采集配置落库为通用配置表(mes_xsl_mcs_sync_config)供后续功能复用。
设计:新增通用采集配置表 + McsSyncHandler 扩展点 + McsSyncScheduler(ThreadPoolTaskScheduler 动态重排+启动加载)
MixActSyncHandler 增量 Upsert(按机台编号+动作代号唯一),保留手动维护数据;密炼机动作维护补全 equip_id/equip_type 字段,
唯一性由全局唯一改为(设备+动作代号)同设备内唯一equipment_id 允许为空(采集未匹配台账时)。
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_153__mes_xsl_mcs_sync_config.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/entity/MesXslMcsSyncConfig.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/mapper/MesXslMcsSyncConfigMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslMcsSyncConfigService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsSyncConfigServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/controller/MesXslMcsSyncConfigController.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/McsSyncHandler.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/McsSyncScheduler.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/handler/MixActSyncHandler.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixerAction.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslMixerActionService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixerActionServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslMixerActionController.java
jeecgboot-vue3/src/views/xslmesMcs/mcsToMesMixAct/index.vue
jeecgboot-vue3/src/views/xslmesMcs/mcsToMesMixAct/McsToMesMixAct.api.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixerAction/MesXslMixerAction.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixerAction/MesXslMixerAction.api.ts
-- author:GHT---date:20260617--for: 【MES上辅机】密炼动作秒级采集 + 通用中间表采集配置 ---
-- author:GHT---date:20260617--for: 【MES上辅机】采集配置通用表/字段绑定 + 配置驱动采集 ---
需求在「MES上辅机数据」下新增「采集配置」左选中间库表、右选MES表(mes_xsl_前缀)下方左带出中间库字段、右由用户选MES接收字段
采集操作改为弹窗(是否采集+采集间隔),密炼动作页同样改为弹窗。
设计:统一为配置驱动——删除硬编码 MixActSyncHandler/McsSyncHandler新增 GenericMcsSyncEngine(JdbcTemplate跨库读源表→按"匹配键"Upsert写MES表
自动填充 id/时间/租户/del_flag纯字段拷贝)McsSyncScheduler 改为按 configId 调度;新增字段映射表 mes_xsl_mcs_sync_field 与配置头扩展(target_table/config_name等)
密炼动作(MIX_ACT)改造为预置配置+字段映射;新增 McsMetaMapper 查询SQLServer/MySQL表与字段元数据采集配置CRUD/详情/采集操作接口。
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_154__mes_xsl_mcs_sync_field.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/entity/MesXslMcsSyncConfig.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/entity/MesXslMcsSyncField.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/mapper/MesXslMcsSyncFieldMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/mapper/McsMetaMapper.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/GenericMcsSyncEngine.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/McsSyncScheduler.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslMcsSyncConfigService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsSyncConfigServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/controller/MesXslMcsSyncConfigController.java
删除jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/McsSyncHandler.java
删除jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/handler/MixActSyncHandler.java
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/index.vue
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/mcsSyncConfig.api.ts
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/mcsSyncConfig.data.ts
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/components/SyncConfigModal.vue
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/components/CollectModal.vue
jeecgboot-vue3/src/views/xslmesMcs/mcsToMesMixAct/index.vue
jeecgboot-vue3/src/views/xslmesMcs/mcsToMesMixAct/McsToMesMixAct.api.ts
-- author:GHT---date:20260617--for: 【MES上辅机】采集配置通用表/字段绑定 + 配置驱动采集 ---
-- author:GHT---date:20260617--for: 【MES上辅机】采集模式全量/时间/增量 + 批量增量写入(应对大表) ---
背景:原通用引擎每周期全表读源+全表读目标逐行Upsertautocommit逐行往返大表(上万~数十万)采集慢。
优化GenericMcsSyncEngine 改为「一次读现有建索引+内存比对+变更检测+batchUpdate分批」并新增三种采集模式(采集操作弹窗可配)
FULL全量匹配(小表全量Upsert)、TIME时间匹配(按时间列取当天/最近七天再Upsert目标侧按窗口匹配键定向IN读取)、
INCR增量匹配(按增量列高水位>last_watermark、ORDER BY ASC取TOP N仅追加并推进水位)。调度器落库 last_watermark。
mes_xsl_mcs_sync_config 增加 sync_mode/incr_column/time_window/batch_limit/last_watermark。
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_155__mes_xsl_mcs_sync_mode.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/entity/MesXslMcsSyncConfig.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/GenericMcsSyncEngine.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/sync/McsSyncScheduler.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/IMesXslMcsSyncConfigService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/service/impl/MesXslMcsSyncConfigServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/mcs/controller/MesXslMcsSyncConfigController.java
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/components/CollectModal.vue
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/mcsSyncConfig.api.ts
jeecgboot-vue3/src/views/xslmesMcs/mcsSyncConfig/mcsSyncConfig.data.ts
-- author:GHT---date:20260617--for: 【MES上辅机】采集模式全量/时间/增量 + 批量增量写入(应对大表) ---

View File

@@ -133,24 +133,18 @@ public class ApprovalCallbackDispatcher {
DING_LOG_TAG, callbackName, ctx.getAction(), ctx.getBizTable(), ctx.getBizDataId());
}
try {
switch (ctx.getAction()) {
case NODE_APPROVED:
//update-begin---author:cursor ---date:20260615 for【XSLMES-20260615-A05】审批回调分发改用if-else避免调试热更新缺失switch合成类-----------
ApprovalCallbackContext.Action action = ctx.getAction();
if (action == ApprovalCallbackContext.Action.NODE_APPROVED) {
cb.onNodeApproved(ctx);
break;
case APPROVED:
} else if (action == ApprovalCallbackContext.Action.APPROVED) {
cb.onApproved(ctx);
break;
case REJECTED:
} else if (action == ApprovalCallbackContext.Action.REJECTED) {
cb.onRejected(ctx);
break;
//update-begin---author:GHT ---date:2026-06-08 for【风险修复-R5】分发撤销回调-----------
case CANCELLED:
} else if (action == ApprovalCallbackContext.Action.CANCELLED) {
cb.onCancelled(ctx);
break;
//update-end---author:GHT ---date:2026-06-08 for【风险修复-R5】分发撤销回调-----------
default:
break;
}
//update-end---author:cursor ---date:20260615 for【XSLMES-20260615-A05】审批回调分发改用if-else避免调试热更新缺失switch合成类-----------
if (dingTalk) {
log.info("{} 业务回调完成 {} action={} bizTable={} bizDataId={}",
DING_LOG_TAG, callbackName, ctx.getAction(), ctx.getBizTable(), ctx.getBizDataId());

View File

@@ -7,6 +7,7 @@ import org.jeecg.modules.xslmes.approval.callback.IApprovalBizCallback;
import org.jeecg.modules.xslmes.common.XslMesBizConstants;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStd;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService;
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
import org.springframework.stereotype.Component;
/**
@@ -25,9 +26,13 @@ import org.springframework.stereotype.Component;
public class RubberQuickTestStdApprovalCallback implements IApprovalBizCallback {
private final IMesXslRubberQuickTestStdService stdService;
private final MesXslStompNotifyService stompNotify;
public RubberQuickTestStdApprovalCallback(IMesXslRubberQuickTestStdService stdService) {
public RubberQuickTestStdApprovalCallback(
IMesXslRubberQuickTestStdService stdService,
MesXslStompNotifyService stompNotify) {
this.stdService = stdService;
this.stompNotify = stompNotify;
}
@Override
@@ -55,5 +60,8 @@ public class RubberQuickTestStdApprovalCallback implements IApprovalBizCallback
.eq(MesXslRubberQuickTestStd::getId, bizDataId)
.set(MesXslRubberQuickTestStd::getAuditStatus, auditStatus)
.update();
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】审批联动 STOMP 同步桌面端-----------
stompNotify.publishRubberQuickTestStdChanged("audit", bizDataId);
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】审批联动 STOMP 同步桌面端-----------
}
}

View File

@@ -108,4 +108,20 @@ public class MesXslApprovalHandleController {
return Result.OK(approvalHandleService.pendingList(user));
}
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】待办列表-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】补发IM审批卡片(历史流转中实例)-----------
@Operation(summary = "审批办理-补发IM审批卡片(流转中)")
@PostMapping("/resendCard")
public Result<String> resendCard(@RequestBody Map<String, Object> body) {
String instanceId = body.get("instanceId") == null ? null : String.valueOf(body.get("instanceId"));
String bizTable = body.get("bizTable") == null ? null : String.valueOf(body.get("bizTable"));
String bizDataId = body.get("bizDataId") == null ? null : String.valueOf(body.get("bizDataId"));
if (oConvertUtils.isEmpty(instanceId)
&& (oConvertUtils.isEmpty(bizTable) || oConvertUtils.isEmpty(bizDataId))) {
return Result.error("请提供 instanceId 或 bizTable+bizDataId");
}
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
return approvalHandleService.resendCard(instanceId, bizTable, bizDataId, user);
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】补发IM审批卡片(历史流转中实例)-----------
}

View File

@@ -16,11 +16,13 @@ import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalHandleService;
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalInstanceService;
import org.jeecg.modules.xslmes.approval.vo.ApprovalGateVo;
import org.jeecg.modules.xslmes.common.MesXslTenantUtils;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingTplBindService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -61,19 +63,37 @@ public class MesXslApprovalLaunchController {
@Autowired
private JdbcTemplate jdbcTemplate;
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】MES发起按钮与钉钉绑定同路由匹配-----------
@Autowired
private IMesXslDingTplBindService dingTplBindService;
//update-end---author:GHT ---date:20260610 for【IM审批通用化】MES发起按钮与钉钉绑定同路由匹配-----------
/**
* 已发布审批流列表(按租户隔离),即"可发起的单据类型"。
* 同时按"功能模块(单据表)"自动反查其菜单路由填入 routePath供前端控制悬浮按钮仅在该功能页显示无需手工配置
* routePath 有值时:与钉钉审批按钮一致,先按 mes_xsl_ding_tpl_bind 解析当前页绑定,再返回该页业务表下已发布审批流
*/
@Operation(summary = "发起审批-已发布审批流列表")
@GetMapping("/publishedList")
public Result<List<MesXslApprovalFlow>> publishedList() {
public Result<List<MesXslApprovalFlow>> publishedList(
@RequestParam(name = "routePath", required = false) String routePath) {
QueryWrapper<MesXslApprovalFlow> qw = new QueryWrapper<>();
qw.eq("status", "1");
Integer tenantId = MesXslTenantUtils.resolveTenantId(null);
if (tenantId != null) {
qw.eq("tenant_id", tenantId);
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】MES发起按钮与钉钉绑定同路由匹配-----------
if (oConvertUtils.isNotEmpty(routePath)) {
if (dingTplBindService.resolveActiveByRoutePath(routePath.trim()) == null) {
return Result.OK(Collections.emptyList());
}
String bizTable = resolveTableByRoutePath(routePath);
if (oConvertUtils.isEmpty(bizTable) || !IDENTIFIER.matcher(bizTable).matches()) {
return Result.OK(Collections.emptyList());
}
qw.eq("biz_table", bizTable);
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】MES发起按钮与钉钉绑定同路由匹配-----------
qw.orderByDesc("create_time");
List<MesXslApprovalFlow> list = approvalFlowService.list(qw);
// 未手工指定 route_path 时,按单据表名自动反查菜单路由
@@ -90,6 +110,59 @@ public class MesXslApprovalLaunchController {
* 约定jeecg 代码生成的列表组件名为 表名驼峰 + List如 mes_xsl_formula_spec -> MesXslFormulaSpecList
* 对应 sys_permission.component 形如 xslmes/mesXslFormulaSpec/MesXslFormulaSpecList取其 url 即路由。
*/
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】按前端路由反查业务表(与钉钉绑定路由解析一致)-----------
private String resolveTableByRoutePath(String routePath) {
if (oConvertUtils.isEmpty(routePath)) {
return null;
}
String component = resolveComponentByRoutePath(routePath);
if (oConvertUtils.isEmpty(component)) {
return null;
}
String comp = component.contains("/") ? component.substring(component.lastIndexOf('/') + 1) : component;
if (comp.endsWith("List")) {
comp = comp.substring(0, comp.length() - "List".length());
}
return camelToUnderline(comp);
}
private String resolveComponentByRoutePath(String routePath) {
if (oConvertUtils.isEmpty(routePath)) {
return null;
}
String path = routePath.trim().replaceAll("/+$", "");
String sql = "SELECT component FROM sys_permission WHERE menu_type IN (0,1) "
+ "AND (del_flag = 0 OR del_flag IS NULL) AND url = ? "
+ "ORDER BY menu_type DESC LIMIT 1";
try {
List<String> list = jdbcTemplate.queryForList(sql, String.class, path);
return list.isEmpty() ? null : list.get(0);
} catch (Exception e) {
log.warn("反查菜单组件失败 routePath={}", routePath, e);
return null;
}
}
private String camelToUnderline(String camel) {
if (oConvertUtils.isEmpty(camel)) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < camel.length(); i++) {
char c = camel.charAt(i);
if (Character.isUpperCase(c)) {
if (i > 0) {
sb.append('_');
}
sb.append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】按前端路由反查业务表(与钉钉绑定路由解析一致)-----------
private String resolveRoutePathByTable(String table) {
if (oConvertUtils.isEmpty(table) || !IDENTIFIER.matcher(table).matches()) {
return null;

View File

@@ -57,6 +57,14 @@ public interface IMesXslApprovalHandleService {
Result<String> urge(String instanceId, LoginUser user);
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】催办接口-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
/**
* 补发当前节点审批卡片(用于历史数据未收到 IM 消息等场景)。
* instanceId 与 (bizTable+bizDataId) 二选一;仅审批中且 MES 通道实例可补发。
*/
Result<String> resendCard(String instanceId, String bizTable, String bizDataId, LoginUser user);
//update-end---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】待办列表查询-----
/**
* 查询当前用户的待办审批列表(状态为审批中且当前处理人包含该用户)。

View File

@@ -23,6 +23,7 @@ import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalHandleService;
import org.jeecg.modules.xslmes.approval.integration.entity.MesXslIntegrationPlan;
import org.jeecg.modules.xslmes.approval.integration.service.IMesXslIntegrationPlanService;
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalInstanceService;
import org.jeecg.modules.xslmes.dingtalk.service.DingTplImCardBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@@ -100,6 +101,11 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
@Autowired
private IMesXslIntegrationPlanService integrationPlanService;
//update-end---author:GHT ---date:20260605 for【XSLMES-20260605-K8R2】驳回回退改由集成方案 onReject 驱动-----------
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】IM卡片字段与钉钉模板绑定对齐-----------
@Autowired
private DingTplImCardBuilder dingTplImCardBuilder;
//update-end---author:GHT ---date:20260610 for【IM审批通用化】IM卡片字段与钉钉模板绑定对齐-----------
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】审批与业务单据联动回调-----
// ==================== 发起后进入首节点 ====================
@@ -691,11 +697,11 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
oConvertUtils.getString(inst.getApplyUserName(), inst.getApplyUser()), inst.getFlowName(), actionLabel);
msgType = "text";
}
SysUser applicant = getUserSafely(inst.getApplyUser());
String fromId = applicant == null ? null : applicant.getId();
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】待办卡片由系统账号发给处理人,支持发起人=处理人-----------
for (String uname : handlerUsernames) {
sendOne(fromId, uname, inst.getTenantId(), content, msgType);
sendApprovalHandlerNotify(uname, inst.getTenantId(), content, msgType);
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】待办卡片由系统账号发给处理人,支持发起人=处理人-----------
}
/** 抄送通知(无办理按钮) */
@@ -755,6 +761,24 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
}
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】审批待办IM通知-----------
private void sendApprovalHandlerNotify(String toUsername, Integer tenantId, String content, String msgType) {
String uname = toUsername == null ? "" : toUsername.trim();
if (oConvertUtils.isEmpty(uname)) {
return;
}
try {
SysUser to = sysUserService.getUserByName(uname);
if (to == null) {
return;
}
sysImChatService.sendApprovalHandlerMessage(to.getId(), tenantId, content, msgType);
} catch (Exception e) {
log.warn("发送审批待办IM消息失败 to={}", uname, e);
}
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】审批待办IM通知-----------
/** 构建 biz_record 卡片 JSON含 instanceId / actionLabel / canApprove与前端 ImBizRecordPayload 对齐 v=2 */
private String buildCardJson(MesXslApprovalInstance inst, String actionLabel, boolean canApprove, String routePath) {
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】区分审批卡片与抄送通知卡片-----
@@ -766,6 +790,15 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
*/
private String buildCardJson(MesXslApprovalInstance inst, String actionLabel, boolean canApprove, String routePath, boolean approvalCard) {
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】区分审批卡片与抄送通知卡片-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】优先按钉钉模板绑定构建卡片字段-----------
MesXslApprovalFlow flow = flowService.getById(inst.getFlowId());
JSONObject dingPayload = dingTplImCardBuilder.buildCardPayload(
inst, flow, actionLabel, canApprove, routePath, approvalCard);
if (dingPayload != null) {
return dingPayload.toJSONString();
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】优先按钉钉模板绑定构建卡片字段-----------
JSONArray fields = new JSONArray();
addField(fields, "审批流", inst.getFlowName());
addField(fields, "单据", safeTitle(inst));
@@ -1353,10 +1386,26 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
if (user != null) {
ctx.setOperatorUsername(user.getUsername());
ctx.setOperatorName(oConvertUtils.getString(user.getRealname(), user.getUsername()));
ctx.setOperatorTime(new Date());
} else {
ctx.setOperatorUsername("system");
ctx.setOperatorName("系统");
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】MES通道补齐stageKey与钉钉回调共用集成方案-----------
if (oConvertUtils.isNotEmpty(nodeId) && oConvertUtils.isNotEmpty(inst.getFlowId())) {
MesXslApprovalFlow flow = flowService.getById(inst.getFlowId());
if (flow != null && oConvertUtils.isNotEmpty(flow.getFlowConfig())) {
JSONObject root = safeParse(flow.getFlowConfig());
JSONObject node = root == null ? null : findNodeById(root, nodeId);
if (node != null) {
JSONObject props = node.getJSONObject("props");
if (props != null && props.containsKey("stageKey")) {
ctx.setStageKey(props.getString("stageKey"));
}
}
}
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】MES通道补齐stageKey与钉钉回调共用集成方案-----------
return ctx;
}
@@ -1455,7 +1504,7 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
if (oConvertUtils.isEmpty(uname)) {
continue;
}
sendOne(user.getId(), uname, inst.getTenantId(),
sendApprovalHandlerNotify(uname, inst.getTenantId(),
"【催办提醒】" + applicantName + " 催促您处理「" + safeTitle(inst) + "」,请尽快审批。", "text");
}
urgeTimeMap.put(instanceId, System.currentTimeMillis());
@@ -1463,6 +1512,77 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
}
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】催办发起人向当前处理人发催办提醒-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
@Override
public Result<String> resendCard(String instanceId, String bizTable, String bizDataId, LoginUser user) {
if (user == null) {
return Result.error("请先登录");
}
MesXslApprovalInstance inst = resolveRunningInstance(instanceId, bizTable, bizDataId);
if (inst == null) {
return Result.error("未找到审批中的 MES 审批实例");
}
if (!"0".equals(inst.getStatus())) {
return Result.error("该审批已结束,无法补发卡片");
}
if (!canResendCard(user, inst)) {
return Result.error("仅发起人或当前处理人可以补发审批卡片");
}
if (oConvertUtils.isEmpty(inst.getCurrentHandlers())) {
return Result.error("当前无待处理人,无法补发");
}
MesXslApprovalFlow flow = flowService.getById(inst.getFlowId());
if (flow == null) {
return Result.error("审批流不存在");
}
List<String> handlers = new ArrayList<>();
for (String uname : inst.getCurrentHandlers().split(",")) {
if (oConvertUtils.isNotEmpty(uname)) {
handlers.add(uname.trim());
}
}
if (handlers.isEmpty()) {
return Result.error("当前无待处理人,无法补发");
}
String actionLabel = oConvertUtils.getString(inst.getCurrentNodeName(), "审批");
sendApprovalCard(inst, flow, actionLabel, handlers);
return Result.OK("已向 " + handlers.size() + " 位处理人补发审批卡片,请在 IM 中查看与 admin 的会话");
}
private MesXslApprovalInstance resolveRunningInstance(String instanceId, String bizTable, String bizDataId) {
if (oConvertUtils.isNotEmpty(instanceId)) {
MesXslApprovalInstance inst = instanceService.getById(instanceId);
return inst == null || !"0".equals(inst.getStatus()) ? null : inst;
}
if (oConvertUtils.isEmpty(bizTable) || oConvertUtils.isEmpty(bizDataId)
|| !IDENTIFIER.matcher(bizTable).matches()) {
return null;
}
return instanceService.lambdaQuery()
.eq(MesXslApprovalInstance::getBizTable, bizTable)
.eq(MesXslApprovalInstance::getBizDataId, bizDataId)
.eq(MesXslApprovalInstance::getStatus, "0")
.orderByDesc(MesXslApprovalInstance::getCreateTime)
.last("LIMIT 1")
.one();
}
private boolean canResendCard(LoginUser user, MesXslApprovalInstance inst) {
if (user.getUsername().equals(inst.getApplyUser())) {
return true;
}
if (oConvertUtils.isEmpty(inst.getCurrentHandlers())) {
return false;
}
for (String uname : inst.getCurrentHandlers().split(",")) {
if (user.getUsername().equals(uname == null ? "" : uname.trim())) {
return true;
}
}
return false;
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
// ==================== 待办列表 ====================
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】待办列表查询当前用户的待处理审批实例-----

View File

@@ -16,6 +16,10 @@ import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.entity.MesXslFormulaSpec;
import org.jeecg.modules.xslmes.entity.MesXslFormulaSpecLine;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecDownStep;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecMaterial;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecStep;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecTcu;
import org.jeecg.modules.xslmes.vo.MesXslFormulaSpecEditChangeItemVO;
import org.jeecg.modules.xslmes.vo.MesXslMixingSpecPage;
@@ -82,6 +86,35 @@ public final class MesXslFormulaSpecEditLogDiffUtil {
return toJson(snapshot);
}
//update-begin---author:cursor ---date:20260612 for【XSLMES-20260612-A03】混炼示方历史快照反序列化-----------
public static MesXslMixingSpecPage parseMixingSnapshotJson(String snapshotJson) {
if (StringUtils.isBlank(snapshotJson)) {
return null;
}
JSONObject root = parseObject(snapshotJson);
if (root == null) {
return null;
}
JSONObject mainObj = root.getJSONObject(SECTION_MAIN);
if (mainObj == null) {
return null;
}
MesXslMixingSpecPage page = mainObj.toJavaObject(MesXslMixingSpecPage.class);
page.setMaterialList(parseSnapshotList(root.getJSONArray(SECTION_MATERIAL), MesXslMixingSpecMaterial.class));
page.setStepList(parseSnapshotList(root.getJSONArray(SECTION_STEP), MesXslMixingSpecStep.class));
page.setDownStepList(parseSnapshotList(root.getJSONArray(SECTION_DOWN_STEP), MesXslMixingSpecDownStep.class));
page.setTcuList(parseSnapshotList(root.getJSONArray(SECTION_TCU), MesXslMixingSpecTcu.class));
return page;
}
private static <T> List<T> parseSnapshotList(JSONArray array, Class<T> clazz) {
if (array == null || array.isEmpty()) {
return new ArrayList<>();
}
return array.toJavaList(clazz);
}
//update-end---author:cursor ---date:20260612 for【XSLMES-20260612-A03】混炼示方历史快照反序列化-----------
public static List<MesXslFormulaSpecEditChangeItemVO> compare(
String specType, String beforeSnapshot, String afterSnapshot) {
List<MesXslFormulaSpecEditChangeItemVO> items = new ArrayList<>();

View File

@@ -0,0 +1,176 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslSmallMaterialDemandPlanSummary;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@Tag(name = "自动小料需求计划")
@RestController
@RequestMapping("/xslmes/mesXslAutoSmallMaterialDemandPlan")
public class MesXslAutoSmallMaterialDemandPlanController {
private static final String TABLE_NAME = "mes_xsl_auto_small_material_demand_plan";
@Autowired private JdbcTemplate jdbcTemplate;
@Operation(summary = "自动小料需求计划-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslSmallMaterialDemandPlanSummary>> queryPageList(
MesXslSmallMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params);
String countSql = "SELECT COUNT(1) FROM (" + groupedSql + ") t";
Long total = jdbcTemplate.queryForObject(countSql, Long.class, params.toArray());
long totalCount = total == null ? 0L : total;
int offset = Math.max((pageNo - 1) * pageSize, 0);
String pageSql = groupedSql + buildOrderBy(groupedByMachine) + " LIMIT ? OFFSET ?";
List<Object> pageParams = new ArrayList<>(params);
pageParams.add(pageSize);
pageParams.add(offset);
List<MesXslSmallMaterialDemandPlanSummary> rows = queryRows(pageSql, pageParams, groupedByMachine);
Page<MesXslSmallMaterialDemandPlanSummary> page = new Page<>(pageNo, pageSize, totalCount);
page.setRecords(rows);
return Result.OK(page);
}
@RequiresPermissions("xslmes:mes_xsl_auto_small_material_demand_plan:exportXls")
@RequestMapping("/exportXls")
public ModelAndView exportXls(
HttpServletRequest request,
MesXslSmallMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params) + buildOrderBy(groupedByMachine);
List<MesXslSmallMaterialDemandPlanSummary> exportList = queryRows(groupedSql, params, groupedByMachine);
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "自动小料需求计划");
mv.addObject(NormalExcelConstants.CLASS, MesXslSmallMaterialDemandPlanSummary.class);
mv.addObject(
NormalExcelConstants.PARAMS,
new ExportParams(
"自动小料需求计划",
"导出人:" + (sysUser == null ? "admin" : sysUser.getRealname()),
"自动小料需求计划",
ExcelType.XSSF));
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
if (oConvertUtils.isNotEmpty(exportFields)) {
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
}
return mv;
}
private String buildGroupedSql(
MesXslSmallMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
String statDateExpr = "DATE_FORMAT(t.stat_date, '%Y-%m-%d')";
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
if (groupedByMachine) {
sql.append(machineExpr).append(" AS machineName, ");
} else {
sql.append("'' AS machineName, ");
}
sql.append(rawMaterialExpr)
.append(" AS rawMaterialName, ")
.append("SUM(COALESCE(t.demand_weight,0)) AS demandWeight, ")
.append("MAX(")
.append(statDateExpr)
.append(") AS statDate ")
.append("FROM ")
.append(TABLE_NAME)
.append(" t ")
.append("WHERE (t.del_flag = 0 OR t.del_flag IS NULL) ");
if (query != null && StringUtils.isNotBlank(query.getStatDate())) {
sql.append("AND ").append(statDateExpr).append(" = ? ");
params.add(query.getStatDate().trim());
}
if (query != null && StringUtils.isNotBlank(query.getRawMaterialName())) {
sql.append("AND ").append(rawMaterialExpr).append(" LIKE ? ");
params.add("%" + query.getRawMaterialName().trim() + "%");
}
if (groupedByMachine && query != null && StringUtils.isNotBlank(query.getMachineName())) {
sql.append("AND ").append(machineExpr).append(" LIKE ? ");
params.add("%" + query.getMachineName().trim() + "%");
}
sql.append("GROUP BY ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
sql.append(rawMaterialExpr);
return sql.toString();
}
private List<MesXslSmallMaterialDemandPlanSummary> queryRows(
String sql, List<Object> params, boolean groupedByMachine) {
return jdbcTemplate.query(
sql,
rs -> {
List<MesXslSmallMaterialDemandPlanSummary> list = new ArrayList<>();
int seq = 1;
while (rs.next()) {
MesXslSmallMaterialDemandPlanSummary row = new MesXslSmallMaterialDemandPlanSummary();
row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : "");
row.setRawMaterialName(trim(rs.getString("rawMaterialName")));
row.setDemandWeight(rs.getBigDecimal("demandWeight"));
row.setStatDate(trim(rs.getString("statDate")));
row.setId(buildRowId(row, groupedByMachine, seq++));
list.add(row);
}
return list;
},
params.toArray());
}
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName";
}
return " ORDER BY rawMaterialName";
}
private String buildRowId(MesXslSmallMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getRawMaterialName()) + "_" + seq;
}
private String trim(String value) {
return value == null ? "" : value.trim();
}
}

View File

@@ -0,0 +1,61 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.xslmes.entity.MesXslAutoSmallMaterialPlanMaintain;
import org.jeecg.modules.xslmes.service.IMesXslAutoSmallMaterialPlanMaintainService;
import org.jeecg.modules.xslmes.vo.MesXslAutoSmallMaterialPlanMaintainSaveAllVO;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "自动小料计划维护")
@RestController
@RequestMapping("/xslmes/mesXslAutoSmallMaterialPlanMaintain")
public class MesXslAutoSmallMaterialPlanMaintainController
extends JeecgController<
MesXslAutoSmallMaterialPlanMaintain, IMesXslAutoSmallMaterialPlanMaintainService> {
private final IMesXslAutoSmallMaterialPlanMaintainService service;
public MesXslAutoSmallMaterialPlanMaintainController(
IMesXslAutoSmallMaterialPlanMaintainService service) {
this.service = service;
}
@Operation(summary = "自动小料计划维护-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslAutoSmallMaterialPlanMaintain>> queryPageList(
MesXslAutoSmallMaterialPlanMaintain model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "50") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslAutoSmallMaterialPlanMaintain> queryWrapper =
QueryGenerator.initQueryWrapper(model, req.getParameterMap());
queryWrapper.orderByAsc("sort_no").orderByAsc("create_time");
IPage<MesXslAutoSmallMaterialPlanMaintain> pageList =
service.page(new Page<>(pageNo, pageSize), queryWrapper);
return Result.OK(pageList);
}
@AutoLog(value = "自动小料计划维护-整表保存")
@Operation(summary = "自动小料计划维护-整表保存")
@RequiresPermissions("xslmes:mes_xsl_auto_small_material_plan_maintain:saveAll")
@PostMapping("/saveAll")
public Result<String> saveAll(@RequestBody MesXslAutoSmallMaterialPlanMaintainSaveAllVO req) {
service.saveAllRows(req == null ? null : req.getRows());
return Result.OK("保存成功");
}
}

View File

@@ -1,5 +1,6 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -21,6 +22,7 @@ import org.jeecg.modules.print.entity.PrintTemplate;
import org.jeecg.modules.print.service.IPrintBizTemplateBindService;
import org.jeecg.modules.print.service.IPrintTemplateService;
import org.jeecg.modules.print.util.PrintBizDataMappingUtil;
import org.jeecg.modules.xslmes.common.XslMesBizConstants;
import org.jeecg.modules.xslmes.constant.MesXslCustomerBizStatus;
import org.jeecg.modules.xslmes.constant.MesXslPrintConstants;
import org.jeecg.modules.xslmes.entity.MesXslCustomer;
@@ -32,6 +34,12 @@ import org.jeecg.modules.xslmes.entity.MesXslUnit;
import org.jeecg.modules.xslmes.entity.MesXslVehicle;
import org.jeecg.modules.xslmes.entity.MesXslWarehouse;
import org.jeecg.modules.xslmes.entity.MesXslWarehouseArea;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestMethod;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStd;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStdLine;
import org.jeecg.modules.xslmes.entity.MesXslWeightRecord;
import org.jeecg.modules.xslmes.service.IMesXslCustomerService;
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialCardService;
@@ -42,6 +50,10 @@ import org.jeecg.modules.xslmes.service.IMesXslUnitService;
import org.jeecg.modules.xslmes.service.IMesXslVehicleService;
import org.jeecg.modules.xslmes.service.IMesXslWarehouseAreaService;
import org.jeecg.modules.xslmes.service.IMesXslWarehouseService;
import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestMethodService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestRecordService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService;
import org.jeecg.modules.xslmes.service.IMesXslWeightRecordService;
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
@@ -87,6 +99,10 @@ public class MesXslDesktopAnonController {
private final IPrintBizTemplateBindService printBizTemplateBindService;
private final IPrintTemplateService printTemplateService;
private final ObjectMapper objectMapper;
private final IMesXslMixingProductionPlanService mixingProductionPlanService;
private final IMesXslRubberQuickTestStdService rubberQuickTestStdService;
private final IMesXslRubberQuickTestRecordService rubberQuickTestRecordService;
private final IMesXslRubberQuickTestMethodService rubberQuickTestMethodService;
// ═══════════════════════════ 车辆管理 ═══════════════════════════
@@ -925,6 +941,128 @@ public class MesXslDesktopAnonController {
}
//update-end---author:cursor ---date:20250602 for【密炼物料皮重策略】桌面端单位下拉只读-----------
//update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端密炼生产计划只读-----------
@Operation(summary = "密炼生产计划维护-免密分页列表查询(供桌面端快检记录筛选)")
@GetMapping("/xslmes/mesXslMixingProductionPlan/anon/list")
public Result<IPage<MesXslMixingProductionPlan>> mixingProductionPlanAnonList(
MesXslMixingProductionPlan model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "500") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslMixingProductionPlan> qw = QueryGenerator.initQueryWrapper(model, req.getParameterMap());
qw.orderByAsc("sort_no").orderByAsc("create_time");
IPage<MesXslMixingProductionPlan> page =
mixingProductionPlanService.page(new Page<>(pageNo, pageSize), qw);
return Result.OK(page);
}
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端密炼生产计划只读-----------
//update-begin---author:jiangxh ---date:2026-06-17 for【快检实验标准】桌面端只读列表与详情-----------
@Operation(summary = "胶料快检实验标准-免密分页列表")
@GetMapping("/xslmes/mesXslRubberQuickTestStd/anon/list")
public Result<IPage<MesXslRubberQuickTestStd>> rubberQuickTestStdAnonList(
MesXslRubberQuickTestStd model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslRubberQuickTestStd> qw = QueryGenerator.initQueryWrapper(model, req.getParameterMap());
qw.orderByDesc("create_time");
IPage<MesXslRubberQuickTestStd> page = rubberQuickTestStdService.page(new Page<>(pageNo, pageSize), qw);
if (page.getRecords() != null) {
page.getRecords().forEach(this::fillStdQuickTestTypeFromMethod);
}
return Result.OK(page);
}
@Operation(summary = "胶料快检实验标准-免密通过id查询含明细")
@GetMapping("/xslmes/mesXslRubberQuickTestStd/anon/queryById")
public Result<MesXslRubberQuickTestStd> rubberQuickTestStdAnonQueryById(@RequestParam(name = "id") String id) {
MesXslRubberQuickTestStd entity = rubberQuickTestStdService.getById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
entity.setLineList(rubberQuickTestStdService.selectLinesByStdId(id));
fillStdQuickTestTypeFromMethod(entity);
return Result.OK(entity);
}
//update-end---author:jiangxh ---date:2026-06-17 for【快检实验标准】桌面端只读列表与详情-----------
//update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检实验标准查询-----------
private static final String RUBBER_QUICK_TEST_STD_ENABLE_IN_USE = "1";
@Operation(summary = "胶料快检实验标准-免密按胶料名称查询使用中标准及明细")
@GetMapping("/xslmes/mesXslRubberQuickTestStd/anon/queryByRubberMaterialName")
public Result<MesXslRubberQuickTestStd> rubberQuickTestStdAnonQueryByRubberMaterialName(
@RequestParam(name = "rubberMaterialName") String rubberMaterialName) {
if (oConvertUtils.isEmpty(rubberMaterialName)) {
return Result.error("胶料名称不能为空");
}
String name = rubberMaterialName.trim();
LambdaQueryWrapper<MesXslRubberQuickTestStd> qw = new LambdaQueryWrapper<>();
qw.eq(MesXslRubberQuickTestStd::getRubberMaterialName, name);
qw.eq(MesXslRubberQuickTestStd::getEnableStatus, RUBBER_QUICK_TEST_STD_ENABLE_IN_USE);
qw.eq(MesXslRubberQuickTestStd::getAuditStatus, XslMesBizConstants.RUBBER_QUICK_TEST_STD_AUDIT_APPROVED);
qw.orderByDesc(MesXslRubberQuickTestStd::getCreateTime);
MesXslRubberQuickTestStd std = rubberQuickTestStdService.getOne(qw, false);
if (std == null) {
return Result.error("未找到胶料「" + name + "」对应的使用中且已批准的快检实验标准");
}
std.setLineList(rubberQuickTestStdService.selectLinesByStdId(std.getId()));
fillStdQuickTestTypeFromMethod(std);
return Result.OK(std);
}
@Operation(summary = "胶料快检实验标准-免密查询明细")
@GetMapping("/xslmes/mesXslRubberQuickTestStd/anon/queryLineListByStdId")
public Result<List<MesXslRubberQuickTestStdLine>> rubberQuickTestStdAnonQueryLineListByStdId(
@RequestParam(name = "id") String id) {
return Result.OK(rubberQuickTestStdService.selectLinesByStdId(id));
}
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检实验标准查询-----------
//update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检记录保存-----------
@Operation(summary = "胶料快检记录-免密添加")
@PostMapping("/xslmes/mesXslRubberQuickTestRecord/anon/add")
public Result<String> rubberQuickTestRecordAnonAdd(@RequestBody MesXslRubberQuickTestRecord record) {
if (record == null) {
return Result.error("参数不能为空");
}
if (oConvertUtils.isEmpty(record.getRubberMaterialName())) {
return Result.error("胶料名称不能为空");
}
List<MesXslRubberQuickTestRecordLine> lineList = record.getLineList();
if (lineList == null || lineList.isEmpty()) {
return Result.error("请维护检验明细");
}
try {
if (oConvertUtils.isEmpty(record.getRecordNo())) {
record.setRecordNo(rubberQuickTestRecordService.generateDesktopRecordNo(record));
}
rubberQuickTestRecordService.fillQuickTestTypeForRecord(record);
rubberQuickTestRecordService.saveMain(record, lineList);
stompNotify.publishRubberQuickTestRecordChanged("add", record.getId());
return Result.OK(record.getRecordNo());
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
}
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】桌面端胶料快检记录保存-----------
//update-begin---author:jiangxh ---date:20260618 for【快检实验标准】桌面端回填实验方法关联实验类型-----------
private void fillStdQuickTestTypeFromMethod(MesXslRubberQuickTestStd std) {
if (std == null || oConvertUtils.isEmpty(std.getTestMethodId())) {
return;
}
MesXslRubberQuickTestMethod method = rubberQuickTestMethodService.getById(std.getTestMethodId());
if (method == null) {
return;
}
std.setQuickTestTypeId(method.getQuickTestTypeId());
std.setQuickTestTypeName(method.getQuickTestTypeName());
}
//update-end---author:jiangxh ---date:20260618 for【快检实验标准】桌面端回填实验方法关联实验类型-----------
// ─────────────────────────── 车辆私有辅助 ────────────────────────────
private void applyWeightNetAndBillType(MesXslWeightRecord record) {

View File

@@ -25,6 +25,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeMainType;
import org.jeecg.modules.xslmes.entity.MesXslProcessOperation;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslDowntimeMainTypeService;
import org.jeecg.modules.xslmes.service.IMesXslProcessOperationService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
@@ -53,6 +54,9 @@ public class MesXslDowntimeMainTypeController extends JeecgController<MesXslDown
@Autowired
private IMesXslProcessOperationService mesXslProcessOperationService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES停机主类型-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslDowntimeMainType>> queryPageList(
@@ -104,6 +108,10 @@ public class MesXslDowntimeMainTypeController extends JeecgController<MesXslDown
@RequiresPermissions("mes:mes_xsl_downtime_main_type:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateDowntimeMainTypeDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslDowntimeMainTypeService.removeById(id);
return Result.OK("删除成功!");
}
@@ -113,6 +121,10 @@ public class MesXslDowntimeMainTypeController extends JeecgController<MesXslDown
@RequiresPermissions("mes:mes_xsl_downtime_main_type:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateDowntimeMainTypeDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslDowntimeMainTypeService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -26,6 +26,7 @@ import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeMainType;
import org.jeecg.modules.xslmes.entity.MesXslDowntimeType;
import org.jeecg.modules.xslmes.entity.MesXslProcessOperation;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslDowntimeMainTypeService;
import org.jeecg.modules.xslmes.service.IMesXslDowntimeTypeService;
import org.jeecg.modules.xslmes.service.IMesXslProcessOperationService;
@@ -59,6 +60,9 @@ public class MesXslDowntimeTypeController extends JeecgController<MesXslDowntime
@Autowired
private IMesXslDowntimeMainTypeService mesXslDowntimeMainTypeService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES停机类型-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslDowntimeType>> queryPageList(
@@ -110,6 +114,10 @@ public class MesXslDowntimeTypeController extends JeecgController<MesXslDowntime
@RequiresPermissions("mes:mes_xsl_downtime_type:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateDowntimeTypeDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslDowntimeTypeService.removeById(id);
return Result.OK("删除成功!");
}
@@ -119,6 +127,10 @@ public class MesXslDowntimeTypeController extends JeecgController<MesXslDowntime
@RequiresPermissions("mes:mes_xsl_downtime_type:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateDowntimeTypeDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslDowntimeTypeService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -0,0 +1,86 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.service.IMesXslEquipAlarmRecordService;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipAlarmRecordVO;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* 设备报警记录(只读,数据来自 SQL Server MCSToMES_MixAlarm
*/
@Tag(name = "MES设备报警记录")
@RestController
@RequestMapping("/xslmes/mesXslEquipAlarmRecord")
@Slf4j
public class MesXslEquipAlarmRecordController {
@Autowired
private IMesXslEquipAlarmRecordService mesXslEquipAlarmRecordService;
@Operation(summary = "MES设备报警记录-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipAlarmRecordVO>> queryPageList(
McsToMesMixAlarm query,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
IPage<MesXslEquipAlarmRecordVO> pageList =
mesXslEquipAlarmRecordService.queryPage(query, req.getParameterMap(), pageNo, pageSize);
return Result.OK(pageList);
}
@Operation(summary = "MES设备报警记录-通过id查询")
@GetMapping(value = "/queryById")
public Result<MesXslEquipAlarmRecordVO> queryById(@RequestParam(name = "id", required = true) String id) {
MesXslEquipAlarmRecordVO entity = mesXslEquipAlarmRecordService.queryById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
return Result.OK(entity);
}
@RequiresPermissions("mes:mes_xsl_equip_alarm_record:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, McsToMesMixAlarm query) {
List<MesXslEquipAlarmRecordVO> exportList =
mesXslEquipAlarmRecordService.listForExport(query, request.getParameterMap());
Subject subject = SecurityUtils.getSubject();
LoginUser sysUser = subject != null && subject.getPrincipal() instanceof LoginUser
? (LoginUser) subject.getPrincipal()
: null;
String exporter = sysUser == null || oConvertUtils.isEmpty(sysUser.getRealname()) ? "admin" : sysUser.getRealname();
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "设备报警记录");
mv.addObject(NormalExcelConstants.CLASS, MesXslEquipAlarmRecordVO.class);
mv.addObject(
NormalExcelConstants.PARAMS,
new ExportParams("设备报警记录", "导出人:" + exporter, "设备报警记录", ExcelType.XSSF));
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
if (oConvertUtils.isNotEmpty(exportFields)) {
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
}
return mv;
}
}

View File

@@ -0,0 +1,86 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.service.IMesXslEquipDowntimeRecordService;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipDowntimeRecordVO;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* 设备停机记录(只读,数据来自 SQL Server MCSToMES_MixAlarm
*/
@Tag(name = "MES设备停机记录")
@RestController
@RequestMapping("/xslmes/mesXslEquipDowntimeRecord")
@Slf4j
public class MesXslEquipDowntimeRecordController {
@Autowired
private IMesXslEquipDowntimeRecordService mesXslEquipDowntimeRecordService;
@Operation(summary = "MES设备停机记录-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipDowntimeRecordVO>> queryPageList(
McsToMesMixAlarm query,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
IPage<MesXslEquipDowntimeRecordVO> pageList =
mesXslEquipDowntimeRecordService.queryPage(query, req.getParameterMap(), pageNo, pageSize);
return Result.OK(pageList);
}
@Operation(summary = "MES设备停机记录-通过id查询")
@GetMapping(value = "/queryById")
public Result<MesXslEquipDowntimeRecordVO> queryById(@RequestParam(name = "id", required = true) String id) {
MesXslEquipDowntimeRecordVO entity = mesXslEquipDowntimeRecordService.queryById(id);
if (entity == null) {
return Result.error("未找到对应数据");
}
return Result.OK(entity);
}
@RequiresPermissions("mes:mes_xsl_equip_downtime_record:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, McsToMesMixAlarm query) {
List<MesXslEquipDowntimeRecordVO> exportList =
mesXslEquipDowntimeRecordService.listForExport(query, request.getParameterMap());
Subject subject = SecurityUtils.getSubject();
LoginUser sysUser = subject != null && subject.getPrincipal() instanceof LoginUser
? (LoginUser) subject.getPrincipal()
: null;
String exporter = sysUser == null || oConvertUtils.isEmpty(sysUser.getRealname()) ? "admin" : sysUser.getRealname();
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "设备停机记录");
mv.addObject(NormalExcelConstants.CLASS, MesXslEquipDowntimeRecordVO.class);
mv.addObject(
NormalExcelConstants.PARAMS,
new ExportParams("设备停机记录", "导出人:" + exporter, "设备停机记录", ExcelType.XSSF));
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
if (oConvertUtils.isNotEmpty(exportFields)) {
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
}
return mv;
}
}

View File

@@ -23,6 +23,7 @@ import org.jeecg.modules.xslmes.entity.MesXslEquipInspectConfig;
import org.jeecg.modules.xslmes.entity.MesXslEquipInspectConfigLine;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger;
import org.jeecg.modules.xslmes.entity.MesXslInspectMaintainItem;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslEquipInspectConfigService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentLedgerService;
import org.jeecg.modules.xslmes.service.IMesXslInspectMaintainItemService;
@@ -53,6 +54,9 @@ public class MesXslEquipInspectConfigController
@Autowired
private IMesXslInspectMaintainItemService mesXslInspectMaintainItemService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES设备点检配置-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipInspectConfig>> queryPageList(
@@ -106,6 +110,10 @@ public class MesXslEquipInspectConfigController
@RequiresPermissions("mes:mes_xsl_equip_inspect_config:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateEquipInspectConfigDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipInspectConfigService.delMain(id);
return Result.OK("删除成功!");
}
@@ -115,6 +123,10 @@ public class MesXslEquipInspectConfigController
@RequiresPermissions("mes:mes_xsl_equip_inspect_config:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateEquipInspectConfigDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipInspectConfigService.delBatchMain(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -24,6 +24,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentCategory;
import org.jeecg.modules.xslmes.entity.MesXslProcessOperation;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentCategoryService;
import org.jeecg.modules.xslmes.service.IMesXslProcessOperationService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
@@ -52,6 +53,9 @@ public class MesXslEquipmentCategoryController extends JeecgController<MesXslEqu
@Autowired
private IMesXslProcessOperationService mesXslProcessOperationService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES设备类别-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipmentCategory>> queryPageList(
@@ -100,6 +104,10 @@ public class MesXslEquipmentCategoryController extends JeecgController<MesXslEqu
@RequiresPermissions("mes:mes_xsl_equipment_category:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateEquipmentCategoryDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentCategoryService.removeById(id);
return Result.OK("删除成功!");
}
@@ -109,6 +117,10 @@ public class MesXslEquipmentCategoryController extends JeecgController<MesXslEqu
@RequiresPermissions("mes:mes_xsl_equipment_category:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateEquipmentCategoryDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentCategoryService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -21,6 +21,7 @@ import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentLedger;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentLedgerService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
@@ -45,6 +46,9 @@ public class MesXslEquipmentLedgerController extends JeecgController<MesXslEquip
@Autowired
private IMesXslEquipmentLedgerService mesXslEquipmentLedgerService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES设备台账-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipmentLedger>> queryPageList(
@@ -96,6 +100,10 @@ public class MesXslEquipmentLedgerController extends JeecgController<MesXslEquip
@RequiresPermissions("mes:mes_xsl_equipment_ledger:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateEquipmentLedgerDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentLedgerService.removeById(id);
return Result.OK("删除成功!");
}
@@ -105,6 +113,10 @@ public class MesXslEquipmentLedgerController extends JeecgController<MesXslEquip
@RequiresPermissions("mes:mes_xsl_equipment_ledger:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateEquipmentLedgerDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentLedgerService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -22,6 +22,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentCategory;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentPart;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentCategoryService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentPartService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
@@ -49,6 +50,9 @@ public class MesXslEquipmentPartController extends JeecgController<MesXslEquipme
@Autowired
private IMesXslEquipmentCategoryService mesXslEquipmentCategoryService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES设备部位-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipmentPart>> queryPageList(
@@ -97,6 +101,10 @@ public class MesXslEquipmentPartController extends JeecgController<MesXslEquipme
@RequiresPermissions("mes:mes_xsl_equipment_part:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateEquipmentPartDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentPartService.removeById(id);
return Result.OK("删除成功!");
}
@@ -106,6 +114,10 @@ public class MesXslEquipmentPartController extends JeecgController<MesXslEquipme
@RequiresPermissions("mes:mes_xsl_equipment_part:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateEquipmentPartDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentPartService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -23,6 +23,7 @@ import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentCategory;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentPart;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentSubPart;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentCategoryService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentPartService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentSubPartService;
@@ -54,6 +55,9 @@ public class MesXslEquipmentSubPartController extends JeecgController<MesXslEqui
@Autowired
private IMesXslEquipmentPartService mesXslEquipmentPartService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES设备小部位-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipmentSubPart>> queryPageList(
@@ -102,6 +106,10 @@ public class MesXslEquipmentSubPartController extends JeecgController<MesXslEqui
@RequiresPermissions("mes:mes_xsl_equipment_sub_part:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateEquipmentSubPartDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentSubPartService.removeById(id);
return Result.OK("删除成功!");
}
@@ -111,6 +119,10 @@ public class MesXslEquipmentSubPartController extends JeecgController<MesXslEqui
@RequiresPermissions("mes:mes_xsl_equipment_sub_part:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateEquipmentSubPartDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentSubPartService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -25,6 +25,7 @@ import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentCategory;
import org.jeecg.modules.xslmes.entity.MesXslEquipmentType;
import org.jeecg.modules.xslmes.entity.MesXslProcessOperation;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentCategoryService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentTypeService;
import org.jeecg.modules.xslmes.service.IMesXslProcessOperationService;
@@ -54,6 +55,9 @@ public class MesXslEquipmentTypeController extends JeecgController<MesXslEquipme
@Autowired
private IMesXslEquipmentCategoryService mesXslEquipmentCategoryService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES设备类型-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslEquipmentType>> queryPageList(
@@ -102,6 +106,10 @@ public class MesXslEquipmentTypeController extends JeecgController<MesXslEquipme
@RequiresPermissions("mes:mes_xsl_equipment_type:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateEquipmentTypeDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentTypeService.removeById(id);
return Result.OK("删除成功!");
}
@@ -111,6 +119,10 @@ public class MesXslEquipmentTypeController extends JeecgController<MesXslEquipme
@RequiresPermissions("mes:mes_xsl_equipment_type:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateEquipmentTypeDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslEquipmentTypeService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -26,6 +27,7 @@ import org.jeecg.modules.xslmes.service.IMesXslEquipmentCategoryService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentPartService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentSubPartService;
import org.jeecg.modules.xslmes.service.IMesXslEquipmentTypeService;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslInspectMaintainItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -60,6 +62,9 @@ public class MesXslInspectMaintainItemController
@Autowired
private IMesXslEquipmentSubPartService mesXslEquipmentSubPartService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES点检及保养项目-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslInspectMaintainItem>> queryPageList(
@@ -109,6 +114,12 @@ public class MesXslInspectMaintainItemController
@RequiresPermissions("mes:mes_xsl_inspect_maintain_item:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
//update-begin---author:jiangxh ---date:20250603 for【MES】点检及保养项目删除前校验设备点检配置等引用-----------
String err = mesXslDeleteReferenceService.validateInspectMaintainItemDelete(List.of(id));
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20250603 for【MES】点检及保养项目删除前校验设备点检配置等引用-----------
mesXslInspectMaintainItemService.removeById(id);
return Result.OK("删除成功!");
}
@@ -118,6 +129,12 @@ public class MesXslInspectMaintainItemController
@RequiresPermissions("mes:mes_xsl_inspect_maintain_item:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
//update-begin---author:jiangxh ---date:20250603 for【MES】点检及保养项目删除前校验设备点检配置等引用-----------
String err = mesXslDeleteReferenceService.validateInspectMaintainItemDelete(Arrays.asList(ids.split(",")));
if (err != null) {
return Result.error(err);
}
//update-end---author:jiangxh ---date:20250603 for【MES】点检及保养项目删除前校验设备点检配置等引用-----------
mesXslInspectMaintainItemService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -0,0 +1,176 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslSmallMaterialDemandPlanSummary;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@Tag(name = "人工小料需求计划")
@RestController
@RequestMapping("/xslmes/mesXslManualSmallMaterialDemandPlan")
public class MesXslManualSmallMaterialDemandPlanController {
private static final String TABLE_NAME = "mes_xsl_manual_small_material_demand_plan";
@Autowired private JdbcTemplate jdbcTemplate;
@Operation(summary = "人工小料需求计划-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslSmallMaterialDemandPlanSummary>> queryPageList(
MesXslSmallMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params);
String countSql = "SELECT COUNT(1) FROM (" + groupedSql + ") t";
Long total = jdbcTemplate.queryForObject(countSql, Long.class, params.toArray());
long totalCount = total == null ? 0L : total;
int offset = Math.max((pageNo - 1) * pageSize, 0);
String pageSql = groupedSql + buildOrderBy(groupedByMachine) + " LIMIT ? OFFSET ?";
List<Object> pageParams = new ArrayList<>(params);
pageParams.add(pageSize);
pageParams.add(offset);
List<MesXslSmallMaterialDemandPlanSummary> rows = queryRows(pageSql, pageParams, groupedByMachine);
Page<MesXslSmallMaterialDemandPlanSummary> page = new Page<>(pageNo, pageSize, totalCount);
page.setRecords(rows);
return Result.OK(page);
}
@RequiresPermissions("xslmes:mes_xsl_manual_small_material_demand_plan:exportXls")
@RequestMapping("/exportXls")
public ModelAndView exportXls(
HttpServletRequest request,
MesXslSmallMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params) + buildOrderBy(groupedByMachine);
List<MesXslSmallMaterialDemandPlanSummary> exportList = queryRows(groupedSql, params, groupedByMachine);
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "人工小料需求计划");
mv.addObject(NormalExcelConstants.CLASS, MesXslSmallMaterialDemandPlanSummary.class);
mv.addObject(
NormalExcelConstants.PARAMS,
new ExportParams(
"人工小料需求计划",
"导出人:" + (sysUser == null ? "admin" : sysUser.getRealname()),
"人工小料需求计划",
ExcelType.XSSF));
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
if (oConvertUtils.isNotEmpty(exportFields)) {
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
}
return mv;
}
private String buildGroupedSql(
MesXslSmallMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
String statDateExpr = "DATE_FORMAT(t.stat_date, '%Y-%m-%d')";
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
if (groupedByMachine) {
sql.append(machineExpr).append(" AS machineName, ");
} else {
sql.append("'' AS machineName, ");
}
sql.append(rawMaterialExpr)
.append(" AS rawMaterialName, ")
.append("SUM(COALESCE(t.demand_weight,0)) AS demandWeight, ")
.append("MAX(")
.append(statDateExpr)
.append(") AS statDate ")
.append("FROM ")
.append(TABLE_NAME)
.append(" t ")
.append("WHERE (t.del_flag = 0 OR t.del_flag IS NULL) ");
if (query != null && StringUtils.isNotBlank(query.getStatDate())) {
sql.append("AND ").append(statDateExpr).append(" = ? ");
params.add(query.getStatDate().trim());
}
if (query != null && StringUtils.isNotBlank(query.getRawMaterialName())) {
sql.append("AND ").append(rawMaterialExpr).append(" LIKE ? ");
params.add("%" + query.getRawMaterialName().trim() + "%");
}
if (groupedByMachine && query != null && StringUtils.isNotBlank(query.getMachineName())) {
sql.append("AND ").append(machineExpr).append(" LIKE ? ");
params.add("%" + query.getMachineName().trim() + "%");
}
sql.append("GROUP BY ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
sql.append(rawMaterialExpr);
return sql.toString();
}
private List<MesXslSmallMaterialDemandPlanSummary> queryRows(
String sql, List<Object> params, boolean groupedByMachine) {
return jdbcTemplate.query(
sql,
rs -> {
List<MesXslSmallMaterialDemandPlanSummary> list = new ArrayList<>();
int seq = 1;
while (rs.next()) {
MesXslSmallMaterialDemandPlanSummary row = new MesXslSmallMaterialDemandPlanSummary();
row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : "");
row.setRawMaterialName(trim(rs.getString("rawMaterialName")));
row.setDemandWeight(rs.getBigDecimal("demandWeight"));
row.setStatDate(trim(rs.getString("statDate")));
row.setId(buildRowId(row, groupedByMachine, seq++));
list.add(row);
}
return list;
},
params.toArray());
}
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName";
}
return " ORDER BY rawMaterialName";
}
private String buildRowId(MesXslSmallMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getRawMaterialName()) + "_" + seq;
}
private String trim(String value) {
return value == null ? "" : value.trim();
}
}

View File

@@ -0,0 +1,61 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.xslmes.entity.MesXslManualSmallMaterialPlanMaintain;
import org.jeecg.modules.xslmes.service.IMesXslManualSmallMaterialPlanMaintainService;
import org.jeecg.modules.xslmes.vo.MesXslManualSmallMaterialPlanMaintainSaveAllVO;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "人工小料计划维护")
@RestController
@RequestMapping("/xslmes/mesXslManualSmallMaterialPlanMaintain")
public class MesXslManualSmallMaterialPlanMaintainController
extends JeecgController<
MesXslManualSmallMaterialPlanMaintain, IMesXslManualSmallMaterialPlanMaintainService> {
private final IMesXslManualSmallMaterialPlanMaintainService service;
public MesXslManualSmallMaterialPlanMaintainController(
IMesXslManualSmallMaterialPlanMaintainService service) {
this.service = service;
}
@Operation(summary = "人工小料计划维护-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslManualSmallMaterialPlanMaintain>> queryPageList(
MesXslManualSmallMaterialPlanMaintain model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "50") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslManualSmallMaterialPlanMaintain> queryWrapper =
QueryGenerator.initQueryWrapper(model, req.getParameterMap());
queryWrapper.orderByAsc("sort_no").orderByAsc("create_time");
IPage<MesXslManualSmallMaterialPlanMaintain> pageList =
service.page(new Page<>(pageNo, pageSize), queryWrapper);
return Result.OK(pageList);
}
@AutoLog(value = "人工小料计划维护-整表保存")
@Operation(summary = "人工小料计划维护-整表保存")
@RequiresPermissions("xslmes:mes_xsl_manual_small_material_plan_maintain:saveAll")
@PostMapping("/saveAll")
public Result<String> saveAll(@RequestBody MesXslManualSmallMaterialPlanMaintainSaveAllVO req) {
service.saveAllRows(req == null ? null : req.getRows());
return Result.OK("保存成功");
}
}

View File

@@ -21,6 +21,7 @@ import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslManufacturer;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslManufacturerService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
@@ -45,6 +46,9 @@ public class MesXslManufacturerController extends JeecgController<MesXslManufact
@Autowired
private IMesXslManufacturerService mesXslManufacturerService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES厂家信息-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslManufacturer>> queryPageList(
@@ -93,6 +97,10 @@ public class MesXslManufacturerController extends JeecgController<MesXslManufact
@RequiresPermissions("mes:mes_xsl_manufacturer:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateManufacturerDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslManufacturerService.removeById(id);
return Result.OK("删除成功!");
}
@@ -102,6 +110,10 @@ public class MesXslManufacturerController extends JeecgController<MesXslManufact
@RequiresPermissions("mes:mes_xsl_manufacturer:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateManufacturerDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslManufacturerService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -112,13 +112,14 @@ public class MesXslMixerActionController extends JeecgController<MesXslMixerActi
@Operation(summary = "校验动作名称是否重复")
@GetMapping("/checkActionName")
public Result<String> checkActionName(
@RequestParam(name = "equipmentId", required = false) String equipmentId,
@RequestParam(name = "actionName", required = true) String actionName,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(actionName) || actionName.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslMixerActionService.isActionNameDuplicated(actionName, dataId)) {
return Result.error("动作名称不能重复");
if (mesXslMixerActionService.isActionNameDuplicated(equipmentId, actionName, dataId)) {
return Result.error("同一设备下动作名称不能重复");
}
return Result.OK("该值可用!");
}
@@ -126,13 +127,14 @@ public class MesXslMixerActionController extends JeecgController<MesXslMixerActi
@Operation(summary = "校验动作代号是否重复")
@GetMapping("/checkActionCode")
public Result<String> checkActionCode(
@RequestParam(name = "equipmentId", required = false) String equipmentId,
@RequestParam(name = "actionCode", required = true) String actionCode,
@RequestParam(name = "dataId", required = false) String dataId) {
if (oConvertUtils.isEmpty(actionCode) || actionCode.trim().isEmpty()) {
return Result.OK("该值可用!");
}
if (mesXslMixerActionService.isActionCodeDuplicated(actionCode, dataId)) {
return Result.error("动作代号不能重复");
if (mesXslMixerActionService.isActionCodeDuplicated(equipmentId, actionCode, dataId)) {
return Result.error("同一设备下动作代号不能重复");
}
return Result.OK("该值可用!");
}
@@ -152,15 +154,15 @@ public class MesXslMixerActionController extends JeecgController<MesXslMixerActi
return "动作名称不能为空";
}
model.setActionName(model.getActionName().trim());
if (mesXslMixerActionService.isActionNameDuplicated(model.getActionName(), excludeId)) {
return "动作名称不能重复";
if (mesXslMixerActionService.isActionNameDuplicated(model.getEquipmentId(), model.getActionName(), excludeId)) {
return "同一设备下动作名称不能重复";
}
if (oConvertUtils.isEmpty(model.getActionCode()) || StringUtils.isBlank(model.getActionCode())) {
return "动作代号不能为空";
}
model.setActionCode(model.getActionCode().trim());
if (mesXslMixerActionService.isActionCodeDuplicated(model.getActionCode(), excludeId)) {
return "动作代号不能重复";
if (mesXslMixerActionService.isActionCodeDuplicated(model.getEquipmentId(), model.getActionCode(), excludeId)) {
return "同一设备下动作代号不能重复";
}
return null;
}

View File

@@ -0,0 +1,81 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
import org.jeecg.modules.xslmes.service.IMesXslMixingProductionPlanService;
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanOrderOptionVO;
import org.jeecg.modules.xslmes.vo.MesXslMixingProductionPlanSaveAllVO;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "密炼生产计划维护")
@RestController
@RequestMapping("/xslmes/mesXslMixingProductionPlan")
public class MesXslMixingProductionPlanController
extends JeecgController<MesXslMixingProductionPlan, IMesXslMixingProductionPlanService> {
private final IMesXslMixingProductionPlanService mixingProductionPlanService;
private final MesXslStompNotifyService stompNotify;
public MesXslMixingProductionPlanController(
IMesXslMixingProductionPlanService mixingProductionPlanService,
MesXslStompNotifyService stompNotify) {
this.mixingProductionPlanService = mixingProductionPlanService;
this.stompNotify = stompNotify;
}
@Operation(summary = "密炼生产计划维护-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslMixingProductionPlan>> queryPageList(
MesXslMixingProductionPlan model,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "20") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<MesXslMixingProductionPlan> queryWrapper =
QueryGenerator.initQueryWrapper(model, req.getParameterMap());
queryWrapper.orderByAsc("sort_no").orderByAsc("create_time");
IPage<MesXslMixingProductionPlan> pageList =
mixingProductionPlanService.page(new Page<>(pageNo, pageSize), queryWrapper);
return Result.OK(pageList);
}
@AutoLog(value = "密炼生产计划维护-整表保存")
@Operation(summary = "密炼生产计划维护-整表保存")
@RequiresPermissions("xslmes:mes_xsl_mixing_production_plan:saveAll")
@PostMapping("/saveAll")
public Result<String> saveAll(@RequestBody MesXslMixingProductionPlanSaveAllVO req) {
mixingProductionPlanService.saveAllRows(req == null ? null : req.getRows());
//update-begin---author:jiangxh ---date:20260617 for【密炼计划】整表保存后广播桌面端同步-----------
stompNotify.publishMixingProductionPlanChanged("saveAll", null);
//update-end---author:jiangxh ---date:20260617 for【密炼计划】整表保存后广播桌面端同步-----------
return Result.OK("保存成功");
}
@Operation(summary = "密炼生产计划维护-班次生产订单候选分页")
@GetMapping("/orderOptionPage")
public Result<IPage<MesXslMixingProductionPlanOrderOptionVO>> orderOptionPage(
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(name = "keyword", required = false) String keyword,
@RequestParam(name = "machineId", required = false) String machineId,
@RequestParam(name = "machineName", required = false) String machineName) {
return Result.OK(
mixingProductionPlanService.queryOrderOptions(
pageNo, pageSize, keyword, machineId, machineName));
}
}

View File

@@ -20,6 +20,7 @@ import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpec;
import org.jeecg.modules.xslmes.service.IMesXslMixingSpecService;
import org.jeecg.modules.xslmes.vo.MesXslMixingSpecPage;
import org.jeecg.modules.xslmes.vo.MesXslMixingSpecSmallWeighRangeVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -175,6 +176,17 @@ public class MesXslMixingSpecController extends JeecgController<MesXslMixingSpec
return Result.OK(mesXslMixingSpecService.queryPurposeOptions(keyword));
}
//update-begin---author:cursor ---date:20260612 for【XSLMES-20260612-A02】混炼示方小料称重范围设置-----------
@AutoLog(value = "MES混炼示方-小料称重范围设置")
@Operation(summary = "MES混炼示方-小料称重范围设置")
@RequiresPermissions("xslmes:mes_xsl_mixing_spec:edit")
@PostMapping(value = "/updateSmallWeighRange")
public Result<String> updateSmallWeighRange(@RequestBody MesXslMixingSpecSmallWeighRangeVO vo) {
mesXslMixingSpecService.updateSmallWeighRange(vo);
return Result.OK("保存成功");
}
//update-end---author:cursor ---date:20260612 for【XSLMES-20260612-A02】混炼示方小料称重范围设置-----------
@RequiresPermissions("xslmes:mes_xsl_mixing_spec:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesXslMixingSpec model) {

View File

@@ -0,0 +1,49 @@
package org.jeecg.modules.xslmes.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecHistory;
import org.jeecg.modules.xslmes.service.IMesXslMixingSpecHistoryService;
import org.jeecg.modules.xslmes.vo.MesXslMixingSpecPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 混炼示方历史记录
*/
@Tag(name = "MES混炼示方历史记录")
@RestController
@RequestMapping("/xslmes/mesXslMixingSpecHistory")
@Slf4j
public class MesXslMixingSpecHistoryController {
@Autowired
private IMesXslMixingSpecHistoryService mesXslMixingSpecHistoryService;
@Operation(summary = "混炼示方历史记录-按示方ID查询")
@GetMapping(value = "/listByMixingSpecId")
public Result<List<MesXslMixingSpecHistory>> listByMixingSpecId(
@RequestParam(name = "mixingSpecId", required = true) String mixingSpecId) {
if (StringUtils.isBlank(mixingSpecId)) {
return Result.error("mixingSpecId不能为空");
}
return Result.OK(mesXslMixingSpecHistoryService.listByMixingSpecId(mixingSpecId));
}
@Operation(summary = "混炼示方历史记录-查询快照详情")
@GetMapping(value = "/queryById")
public Result<MesXslMixingSpecPage> queryById(@RequestParam(name = "id", required = true) String id) {
MesXslMixingSpecPage page = mesXslMixingSpecHistoryService.queryPageByHistoryId(id);
if (page == null) {
return Result.error("未找到对应历史记录");
}
return Result.OK(page);
}
}

View File

@@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
@@ -97,9 +98,19 @@ public class MesXslProductionOrderController
@Operation(summary = "生产订单-拆分生成母胶计划")
@RequiresPermissions("xslmes:mes_xsl_production_order:split")
@PostMapping("/split")
public Result<MesXslMasterBatchPlan> split(@RequestParam(name = "id", required = true) String id) {
MesXslMasterBatchPlan plan = mesXslProductionOrderService.splitToMasterBatchPlan(id);
return Result.OK("拆分成功", plan);
public Result<List<MesXslMasterBatchPlan>> split(@RequestParam(name = "id", required = true) String id) {
List<MesXslMasterBatchPlan> plans = mesXslProductionOrderService.splitToMasterBatchPlan(id);
return Result.OK("拆分成功", plans);
}
@AutoLog(value = "生产订单-批量拆分生成计划")
@Operation(summary = "生产订单-批量拆分生成计划")
@RequiresPermissions("xslmes:mes_xsl_production_order:split")
@PostMapping("/splitBatch")
public Result<Integer> splitBatch(@RequestParam(name = "ids", required = true) String ids) {
List<String> idList = Arrays.asList(ids.split(","));
int count = mesXslProductionOrderService.splitToMasterBatchPlanBatch(idList);
return Result.OK("批量拆分成功", count);
}
@RequiresPermissions("xslmes:mes_xsl_production_order:exportXls")

View File

@@ -0,0 +1,178 @@
package org.jeecg.modules.xslmes.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialDemandPlanSummary;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* 原材料需求计划(汇总查询)
*/
@Tag(name = "原材料需求计划")
@RestController
@RequestMapping("/xslmes/mesXslRawMaterialDemandPlan")
public class MesXslRawMaterialDemandPlanController {
@Autowired private JdbcTemplate jdbcTemplate;
@Operation(summary = "原材料需求计划-分页列表查询")
@GetMapping("/list")
public Result<IPage<MesXslRawMaterialDemandPlanSummary>> queryPageList(
MesXslRawMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params);
String countSql = "SELECT COUNT(1) FROM (" + groupedSql + ") t";
Long total = jdbcTemplate.queryForObject(countSql, Long.class, params.toArray());
long totalCount = total == null ? 0L : total;
int offset = Math.max((pageNo - 1) * pageSize, 0);
String pageSql = groupedSql + buildOrderBy(groupedByMachine) + " LIMIT ? OFFSET ?";
List<Object> pageParams = new ArrayList<>(params);
pageParams.add(pageSize);
pageParams.add(offset);
List<MesXslRawMaterialDemandPlanSummary> rows = queryRows(pageSql, pageParams, groupedByMachine);
Page<MesXslRawMaterialDemandPlanSummary> page = new Page<>(pageNo, pageSize, totalCount);
page.setRecords(rows);
return Result.OK(page);
}
@RequiresPermissions("xslmes:mes_xsl_raw_material_demand_plan:exportXls")
@RequestMapping("/exportXls")
public ModelAndView exportXls(
HttpServletRequest request,
MesXslRawMaterialDemandPlanSummary query,
@RequestParam(name = "groupByMachine", required = false, defaultValue = "0") Integer groupByMachine) {
boolean groupedByMachine = groupByMachine != null && groupByMachine == 1;
List<Object> params = new ArrayList<>();
String groupedSql = buildGroupedSql(query, groupedByMachine, params) + buildOrderBy(groupedByMachine);
List<MesXslRawMaterialDemandPlanSummary> exportList = queryRows(groupedSql, params, groupedByMachine);
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
mv.addObject(NormalExcelConstants.FILE_NAME, "原材料需求计划");
mv.addObject(NormalExcelConstants.CLASS, MesXslRawMaterialDemandPlanSummary.class);
mv.addObject(
NormalExcelConstants.PARAMS,
new ExportParams(
"原材料需求计划",
"导出人:" + (sysUser == null ? "admin" : sysUser.getRealname()),
"原材料需求计划",
ExcelType.XSSF));
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
String exportFields = request.getParameter(NormalExcelConstants.EXPORT_FIELDS);
if (oConvertUtils.isNotEmpty(exportFields)) {
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
}
return mv;
}
private String buildGroupedSql(
MesXslRawMaterialDemandPlanSummary query, boolean groupedByMachine, List<Object> params) {
String machineExpr = "COALESCE(NULLIF(TRIM(t.machine_name),''), '')";
String erpCodeExpr = "COALESCE(NULLIF(TRIM(t.erp_code),''), '')";
String rawMaterialExpr = "COALESCE(NULLIF(TRIM(t.raw_material_name),''), '')";
StringBuilder sql = new StringBuilder();
sql.append("SELECT ");
if (groupedByMachine) {
sql.append(machineExpr).append(" AS machineName, ");
} else {
sql.append("'' AS machineName, ");
}
sql.append(erpCodeExpr)
.append(" AS erpCode, ")
.append(rawMaterialExpr)
.append(" AS rawMaterialName, ")
.append("SUM(COALESCE(t.demand_weight,0)) AS demandWeight, ")
.append("SUM(COALESCE(t.standard_weight,0)) AS standardWeight, ")
.append("SUM(COALESCE(t.actual_weight,0)) AS actualWeight ")
.append("FROM mes_xsl_raw_material_demand_plan t ")
.append("WHERE (t.del_flag = 0 OR t.del_flag IS NULL) ");
if (query != null && StringUtils.isNotBlank(query.getErpCode())) {
sql.append("AND ").append(erpCodeExpr).append(" LIKE ? ");
params.add("%" + query.getErpCode().trim() + "%");
}
if (query != null && StringUtils.isNotBlank(query.getRawMaterialName())) {
sql.append("AND ").append(rawMaterialExpr).append(" LIKE ? ");
params.add("%" + query.getRawMaterialName().trim() + "%");
}
if (groupedByMachine && query != null && StringUtils.isNotBlank(query.getMachineName())) {
sql.append("AND ").append(machineExpr).append(" LIKE ? ");
params.add("%" + query.getMachineName().trim() + "%");
}
sql.append("GROUP BY ");
if (groupedByMachine) {
sql.append(machineExpr).append(", ");
}
sql.append(erpCodeExpr).append(", ").append(rawMaterialExpr);
return sql.toString();
}
private List<MesXslRawMaterialDemandPlanSummary> queryRows(
String sql, List<Object> params, boolean groupedByMachine) {
return jdbcTemplate.query(
sql,
rs -> {
List<MesXslRawMaterialDemandPlanSummary> list = new ArrayList<>();
int seq = 1;
while (rs.next()) {
MesXslRawMaterialDemandPlanSummary row = new MesXslRawMaterialDemandPlanSummary();
row.setMachineName(groupedByMachine ? trim(rs.getString("machineName")) : "");
row.setErpCode(trim(rs.getString("erpCode")));
row.setRawMaterialName(trim(rs.getString("rawMaterialName")));
row.setDemandWeight(rs.getBigDecimal("demandWeight"));
row.setStandardWeight(rs.getBigDecimal("standardWeight"));
row.setActualWeight(rs.getBigDecimal("actualWeight"));
row.setId(buildRowId(row, groupedByMachine, seq++));
list.add(row);
}
return list;
},
params.toArray());
}
private String buildOrderBy(boolean groupedByMachine) {
if (groupedByMachine) {
return " ORDER BY machineName, rawMaterialName, erpCode";
}
return " ORDER BY rawMaterialName, erpCode";
}
private String buildRowId(MesXslRawMaterialDemandPlanSummary row, boolean groupedByMachine, int seq) {
String machine = groupedByMachine ? StringUtils.defaultString(row.getMachineName()) : "ALL";
return machine + "_" + StringUtils.defaultString(row.getErpCode()) + "_" + seq;
}
private String trim(String value) {
return value == null ? "" : value.trim();
}
}

View File

@@ -25,6 +25,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestDataPoint;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestDataPointService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestTypeService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
@@ -51,6 +52,9 @@ public class MesXslRubberQuickTestDataPointController
@Autowired
private IMesXslRubberQuickTestTypeService mesXslRubberQuickTestTypeService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES胶料快检数据点-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslRubberQuickTestDataPoint>> queryPageList(
@@ -120,6 +124,10 @@ public class MesXslRubberQuickTestDataPointController
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_data_point:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestDataPointDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestDataPointService.removeById(id);
return Result.OK("删除成功!");
}
@@ -129,6 +137,10 @@ public class MesXslRubberQuickTestDataPointController
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_data_point:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestDataPointDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestDataPointService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -50,6 +50,7 @@ import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestDataPointService;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestMethodService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestTypeService;
@@ -102,7 +103,8 @@ public class MesXslRubberQuickTestMethodController
private IMesXslRubberQuickTestDataPointService mesXslRubberQuickTestDataPointService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES胶料快检实验方法-分页列表查询")
@@ -231,29 +233,25 @@ public class MesXslRubberQuickTestMethodController
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestMethodDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestMethodService.delMain(id);
return Result.OK("删除成功!");
}
@AutoLog(value = "MES胶料快检实验方法-批量删除")
@Operation(summary = "MES胶料快检实验方法-批量删除")
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_method:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestMethodDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestMethodService.delBatchMain(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -16,10 +16,15 @@ import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.system.vo.LoginUser;
import org.apache.shiro.SecurityUtils;
import org.jeecg.modules.mes.material.entity.MesMaterial;
import org.jeecg.modules.mes.material.service.IMesMaterialService;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecord;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestRecordService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestTypeService;
@@ -49,6 +54,9 @@ public class MesXslRubberQuickTestRecordController
@Autowired
private IMesXslRubberQuickTestTypeService mesXslRubberQuickTestTypeService;
@Autowired
private ISysUserService sysUserService;
@Operation(summary = "MES胶料快检记录-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslRubberQuickTestRecord>> queryPageList(
@@ -110,6 +118,9 @@ public class MesXslRubberQuickTestRecordController
@PostMapping(value = "/batchFromMaterial")
public Result<List<String>> batchFromMaterial(@RequestBody MesXslRubberQuickTestRecordBatchFromMaterialVO vo) {
try {
//update-begin---author:jiangxh ---date:20260616 for【MES】胶料快检记录批量生成默认带出当前登录检验人-----------
fillInspectorIfEmpty(vo);
//update-end---author:jiangxh ---date:20260616 for【MES】胶料快检记录批量生成默认带出当前登录检验人-----------
List<String> ids = mesXslRubberQuickTestRecordService.batchFromMaterial(vo);
return Result.OK("成功生成 " + ids.size() + " 条快检记录", ids);
} catch (Exception e) {
@@ -153,6 +164,15 @@ public class MesXslRubberQuickTestRecordController
return Result.OK(mesXslRubberQuickTestRecordService.selectLinesByRecordId(id));
}
//update-begin---author:jiangxh ---date:2026-06-17 for【快检记录】查询原始数据明细-----------
@Operation(summary = "MES胶料快检记录-查询原始数据明细")
@GetMapping(value = "/queryRawLineListByRecordId")
public Result<List<MesXslRubberQuickTestRecordRawLine>> queryRawLineListByRecordId(
@RequestParam(name = "id", required = true) String id) {
return Result.OK(mesXslRubberQuickTestRecordService.selectRawLinesByRecordId(id));
}
//update-end---author:jiangxh ---date:2026-06-17 for【快检记录】查询原始数据明细-----------
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_record:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, MesXslRubberQuickTestRecord model) {
@@ -186,6 +206,10 @@ public class MesXslRubberQuickTestRecordController
main.setQuickTestTypeName(type.getTypeName());
}
//update-begin---author:jiangxh ---date:20260616 for【MES】胶料快检记录保存时补全检验人姓名冗余-----------
resolveInspector(main);
//update-end---author:jiangxh ---date:20260616 for【MES】胶料快检记录保存时补全检验人姓名冗余-----------
if (lineList == null || lineList.isEmpty()) {
return "请维护检验明细";
}
@@ -197,4 +221,35 @@ public class MesXslRubberQuickTestRecordController
}
return null;
}
//update-begin---author:jiangxh ---date:20260616 for【MES】胶料快检记录检验人冗余字段补全-----------
private void resolveInspector(MesXslRubberQuickTestRecord main) {
if (main == null || oConvertUtils.isEmpty(main.getInspectorUserId())) {
return;
}
if (oConvertUtils.isNotEmpty(main.getInspectorRealname())) {
return;
}
SysUser user = sysUserService.getById(main.getInspectorUserId().trim());
if (user != null) {
main.setInspectorUsername(user.getUsername());
main.setInspectorRealname(user.getRealname());
}
}
private void fillInspectorIfEmpty(MesXslRubberQuickTestRecordBatchFromMaterialVO vo) {
if (vo == null || oConvertUtils.isNotEmpty(vo.getInspectorUserId())) {
return;
}
if (SecurityUtils.getSubject() == null || SecurityUtils.getSubject().getPrincipal() == null) {
return;
}
if (!(SecurityUtils.getSubject().getPrincipal() instanceof LoginUser user)) {
return;
}
vo.setInspectorUserId(user.getId());
vo.setInspectorUsername(user.getUsername());
vo.setInspectorRealname(user.getRealname());
}
//update-end---author:jiangxh ---date:20260616 for【MES】胶料快检记录检验人冗余字段补全-----------
}

View File

@@ -34,7 +34,9 @@ import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStd;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStdLine;
import org.jeecg.modules.xslmes.service.IMesXslMixerPsCompileService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestMethodService;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService;
import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
import org.jeecg.modules.xslmes.vo.MesXslRubberQuickTestStdPage;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -69,6 +71,12 @@ public class MesXslRubberQuickTestStdController
@Autowired
private ISysDepartService sysDepartService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Autowired
private MesXslStompNotifyService stompNotify;
@Operation(summary = "MES胶料快检实验标准-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslRubberQuickTestStd>> queryPageList(
@@ -103,6 +111,9 @@ public class MesXslRubberQuickTestStdController
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
stompNotify.publishRubberQuickTestStdChanged("add", main.getId());
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
return Result.OK("添加成功!");
}
@@ -125,6 +136,9 @@ public class MesXslRubberQuickTestStdController
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
stompNotify.publishRubberQuickTestStdChanged("edit", main.getId());
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
return Result.OK("编辑成功!");
}
@@ -133,7 +147,14 @@ public class MesXslRubberQuickTestStdController
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_std:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestStdDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestStdService.delMain(id);
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
stompNotify.publishRubberQuickTestStdChanged("delete", id);
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
return Result.OK("删除成功!");
}
@@ -142,7 +163,14 @@ public class MesXslRubberQuickTestStdController
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_std:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestStdDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestStdService.delBatchMain(Arrays.asList(ids.split(",")));
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
stompNotify.publishRubberQuickTestStdChanged("batchDelete", ids);
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
return Result.OK("批量删除成功!");
}
@@ -184,6 +212,9 @@ public class MesXslRubberQuickTestStdController
.set(MesXslRubberQuickTestStd::getEnableStatus, enableStatus)
.update();
if (updated) {
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
stompNotify.publishRubberQuickTestStdChanged("status", id);
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】桌面端 STOMP 同步-----------
return Result.OK("操作成功");
}
MesXslRubberQuickTestStd cur = mesXslRubberQuickTestStdService.getById(id);
@@ -226,13 +257,6 @@ public class MesXslRubberQuickTestStdController
if (main == null) {
return "参数不能为空";
}
if (oConvertUtils.isEmpty(main.getStdName())) {
return "实验标准名称不能为空";
}
main.setStdName(main.getStdName().trim());
if (mesXslRubberQuickTestStdService.isStdNameDuplicated(main.getStdName(), excludeId, main)) {
return "实验标准名称已存在";
}
if (oConvertUtils.isEmpty(main.getTestMethodId())) {
return "请选择实验方法";
}
@@ -242,13 +266,22 @@ public class MesXslRubberQuickTestStdController
}
main.setTestMethodName(method.getMethodName());
if (oConvertUtils.isNotEmpty(main.getRubberMaterialId())) {
if (oConvertUtils.isEmpty(main.getRubberMaterialId())) {
return "请选择胶料信息";
}
MesMaterial material = mesMaterialService.getById(main.getRubberMaterialId());
if (material == null) {
return "所选胶料不存在";
}
main.setRubberMaterialName(material.getMaterialName());
//update-begin---author:jiangxh ---date:20260616 for【MES】胶料快检实验标准名称自动生成实验方法名称_胶料名称-----------
String stdName = main.getTestMethodName().trim() + "_" + main.getRubberMaterialName().trim();
main.setStdName(stdName);
if (mesXslRubberQuickTestStdService.isStdNameDuplicated(stdName, excludeId, main)) {
return "实验标准名称已存在";
}
//update-end---author:jiangxh ---date:20260616 for【MES】胶料快检实验标准名称自动生成实验方法名称_胶料名称-----------
if (oConvertUtils.isNotEmpty(main.getPsCompileId())) {
MesXslMixerPsCompile ps = mesXslMixerPsCompileService.getById(main.getPsCompileId());
@@ -258,6 +291,18 @@ public class MesXslRubberQuickTestStdController
if (!XslMesBizConstants.PS_TYPE_RAW_INSPECT_STD.equals(ps.getPsType())) {
return "发行编号须选择类型为原材料检验标准的密炼PS";
}
//update-begin---author:jiangxh ---date:20260617 for【快检实验标准】发行编号仅允许选择编制状态密炼PS-----------
boolean psCompileIdChanged = true;
if (oConvertUtils.isNotEmpty(excludeId)) {
MesXslRubberQuickTestStd existingStd = mesXslRubberQuickTestStdService.getById(excludeId);
if (existingStd != null && oConvertUtils.isNotEmpty(existingStd.getPsCompileId())) {
psCompileIdChanged = !existingStd.getPsCompileId().equals(main.getPsCompileId());
}
}
if (psCompileIdChanged && !"compile".equals(ps.getStatus())) {
return "发行编号须选择编制状态的密炼PS";
}
//update-end---author:jiangxh ---date:20260617 for【快检实验标准】发行编号仅允许选择编制状态密炼PS-----------
main.setIssueNumber(ps.getPsCode());
if (main.getIssueDate() == null && ps.getIssueDate() != null) {
main.setIssueDate(ps.getIssueDate());

View File

@@ -22,6 +22,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestType;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestTypeService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
@@ -44,6 +45,9 @@ public class MesXslRubberQuickTestTypeController
@Autowired
private IMesXslRubberQuickTestTypeService mesXslRubberQuickTestTypeService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES胶料快检实验类型-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslRubberQuickTestType>> queryPageList(
@@ -116,6 +120,10 @@ public class MesXslRubberQuickTestTypeController
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_type:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestTypeDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestTypeService.removeById(id);
return Result.OK("删除成功!");
}
@@ -125,6 +133,10 @@ public class MesXslRubberQuickTestTypeController
@RequiresPermissions("mes:mes_xsl_rubber_quick_test_type:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateRubberQuickTestTypeDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberQuickTestTypeService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -22,6 +22,7 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslRubberSmallLockReason;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslRubberSmallLockReasonService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
@@ -44,6 +45,9 @@ public class MesXslRubberSmallLockReasonController
@Autowired
private IMesXslRubberSmallLockReasonService mesXslRubberSmallLockReasonService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES胶料小料锁定原因-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslRubberSmallLockReason>> queryPageList(
@@ -122,6 +126,10 @@ public class MesXslRubberSmallLockReasonController
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateRubberSmallLockReasonDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberSmallLockReasonService.removeById(id);
return Result.OK("删除成功!");
}
@@ -131,6 +139,10 @@ public class MesXslRubberSmallLockReasonController
@RequiresPermissions("mes:mes_xsl_rubber_small_lock_reason:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateRubberSmallLockReasonDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslRubberSmallLockReasonService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -21,6 +21,7 @@ import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.entity.MesXslSparePartsCategory;
import org.jeecg.modules.xslmes.service.IMesXslDeleteReferenceService;
import org.jeecg.modules.xslmes.service.IMesXslSparePartsCategoryService;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
@@ -42,6 +43,9 @@ public class MesXslSparePartsCategoryController extends JeecgController<MesXslSp
@Autowired
private IMesXslSparePartsCategoryService mesXslSparePartsCategoryService;
@Autowired
private IMesXslDeleteReferenceService mesXslDeleteReferenceService;
@Operation(summary = "MES备品件类别-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<MesXslSparePartsCategory>> queryPageList(
@@ -90,6 +94,10 @@ public class MesXslSparePartsCategoryController extends JeecgController<MesXslSp
@RequiresPermissions("mes:mes_xsl_spare_parts_category:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
String refErr = mesXslDeleteReferenceService.validateSparePartsCategoryDelete(List.of(id));
if (refErr != null) {
return Result.error(refErr);
}
mesXslSparePartsCategoryService.removeById(id);
return Result.OK("删除成功!");
}
@@ -99,6 +107,10 @@ public class MesXslSparePartsCategoryController extends JeecgController<MesXslSp
@RequiresPermissions("mes:mes_xsl_spare_parts_category:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
String refErr = mesXslDeleteReferenceService.validateSparePartsCategoryDelete(Arrays.asList(ids.split(",")));
if (refErr != null) {
return Result.error(refErr);
}
mesXslSparePartsCategoryService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}

View File

@@ -3,6 +3,7 @@ package org.jeecg.modules.xslmes.dingtalk.service;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.system.service.ISysDictService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -79,7 +80,7 @@ public class DingTplBindFieldValueResolver {
return null;
}
if (cur instanceof Map) {
cur = ((Map<String, Object>) cur).get(p);
cur = getMapValue((Map<String, Object>) cur, p);
} else {
return null;
}
@@ -87,6 +88,32 @@ public class DingTplBindFieldValueResolver {
return cur;
}
/** JDBC 行多为下划线列名,绑定配置为驼峰字段名,双向兼容取值 */
@SuppressWarnings("unchecked")
private Object getMapValue(Map<String, Object> map, String key) {
if (map == null || StringUtils.isBlank(key)) {
return null;
}
Object val = map.get(key);
if (val != null) {
return val;
}
String underline = oConvertUtils.camelToUnderline(key);
if (!underline.equals(key)) {
val = map.get(underline);
if (val != null) {
return val;
}
}
if (key.contains("_")) {
String camel = oConvertUtils.camelName(key);
if (!camel.equals(key)) {
val = map.get(camel);
}
}
return val;
}
@SuppressWarnings("unchecked")
private String getDictTextFromRow(Object rowData, String bizField) {
if (!(rowData instanceof Map) || StringUtils.isBlank(bizField)) {
@@ -95,13 +122,13 @@ public class DingTplBindFieldValueResolver {
Map<String, Object> map = (Map<String, Object>) rowData;
String[] parts = bizField.split("\\.");
if (parts.length == 1) {
Object v = map.get(parts[0] + "_dictText");
Object v = getMapValue(map, parts[0] + "_dictText");
return v != null ? String.valueOf(v) : null;
}
String parentPath = String.join(".", java.util.Arrays.copyOf(parts, parts.length - 1));
Object parent = getNestedValue(rowData, parentPath);
if (parent instanceof Map) {
Object v = ((Map<?, ?>) parent).get(parts[parts.length - 1] + "_dictText");
Object v = getMapValue((Map<String, Object>) parent, parts[parts.length - 1] + "_dictText");
return v != null ? String.valueOf(v) : null;
}
return null;

View File

@@ -0,0 +1,339 @@
package org.jeecg.modules.xslmes.dingtalk.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.print.catalog.IPrintBizEntityFieldCatalogProvider;
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
import org.jeecg.modules.print.util.PrintBizDetailPropertyScanner;
import org.jeecg.modules.print.util.PrintBizEntityFieldIntrospector;
import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.print.vo.PrintBizTypeVO;
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingTplBind;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
* 按钉钉审批模板绑定配置构建 IM 聊天审批卡片字段,与钉钉发起审批表单展示对齐。
*/
@Slf4j
@Service
public class DingTplImCardBuilder {
private static final Pattern IDENTIFIER = Pattern.compile("^[A-Za-z0-9_]+$");
private static final String CARD_STYLE_DING = "ding";
/** 与前端 dingTplFieldValue.defaultValueMode 一致:下拉类控件默认原值 */
private static final Set<String> DD_SELECT_COMPONENTS = Set.of(
"DDSelectField", "DDMultiSelectField", "DepartmentField", "InnerContactField");
@Autowired
private IMesXslDingTplBindService bindService;
@Autowired
private IMesXslDingProcessTplService tplService;
@Autowired
private DingTplBindFieldValueResolver fieldValueResolver;
@Autowired
private IPrintBizPermEntityService printBizPermEntityService;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired(required = false)
private IPrintBizEntityFieldCatalogProvider fieldCatalogProvider;
/**
* 若存在启用的钉钉模板绑定,则按绑定字段构建完整 biz_record 载荷;否则返回 null 由调用方降级。
*/
public JSONObject buildCardPayload(MesXslApprovalInstance inst, MesXslApprovalFlow flow,
String actionLabel, boolean canApprove, String routePath,
boolean approvalCard) {
if (inst == null || oConvertUtils.isEmpty(routePath)) {
return null;
}
MesXslDingTplBind bind = bindService.resolveActiveByRoutePath(routePath);
if (bind == null || oConvertUtils.isEmpty(bind.getFieldMappingJson())) {
return null;
}
MesXslDingProcessTpl tpl = tplService.getById(bind.getTemplateId());
if (tpl == null || !"1".equals(tpl.getStatus())) {
return null;
}
Map<String, Object> rowData = loadBizRow(inst.getBizTable(), inst.getBizDataId());
if (rowData.isEmpty()) {
return null;
}
Map<String, PrintBizFieldItemVO> metaMap = buildFieldMetaMap(bind.getBizCode());
JSONArray fields = buildDingStyleFields(bind.getFieldMappingJson(), rowData, metaMap);
appendApprovalMetaFields(fields, inst, flow, approvalCard);
JSONObject item = buildItem(inst, routePath, fields, actionLabel, canApprove, approvalCard);
JSONArray items = new JSONArray();
items.add(item);
JSONObject payload = new JSONObject();
payload.put("v", 2);
payload.put("cardStyle", CARD_STYLE_DING);
payload.put("templateId", bind.getTemplateId());
payload.put("templateName", oConvertUtils.getString(bind.getTemplateName(), tpl.getTplName()));
payload.put("pageTitle", oConvertUtils.getString(inst.getBizTableName(), flow != null ? flow.getFlowName() : inst.getFlowName()));
payload.put("pagePath", routePath);
payload.put("rowKey", "id");
payload.put("items", items);
return payload;
}
private JSONArray buildDingStyleFields(String mappingJson, Map<String, Object> rowData,
Map<String, PrintBizFieldItemVO> metaMap) {
JSONArray fields = new JSONArray();
JSONArray mappings;
try {
mappings = JSON.parseArray(mappingJson);
} catch (Exception e) {
log.warn("解析钉钉模板绑定 JSON 失败: {}", e.getMessage());
return fields;
}
if (mappings == null || mappings.isEmpty()) {
return fields;
}
for (int i = 0; i < mappings.size(); i++) {
JSONObject mapping = mappings.getJSONObject(i);
if (mapping == null) {
continue;
}
String componentName = mapping.getString("componentName");
if ("TableField".equals(componentName)) {
continue;
}
String label = mapping.getString("componentLabel");
String bizField = mapping.getString("bizField");
if (oConvertUtils.isEmpty(label) || oConvertUtils.isEmpty(bizField)) {
if ("TextNote".equals(componentName) && oConvertUtils.isNotEmpty(label)) {
addField(fields, label, "");
}
continue;
}
PrintBizFieldItemVO meta = metaMap.get(bizField.trim());
String valueMode = resolveValueMode(mapping, meta);
Object val = fieldValueResolver.resolveValue(rowData, bizField.trim(), valueMode, meta);
addField(fields, label, formatValue(val));
}
return fields;
}
private void appendApprovalMetaFields(JSONArray fields, MesXslApprovalInstance inst,
MesXslApprovalFlow flow, boolean approvalCard) {
addField(fields, "审批流", oConvertUtils.getString(inst.getFlowName(),
flow != null ? flow.getFlowName() : ""));
addField(fields, approvalCard ? "当前节点" : "知会",
oConvertUtils.getString(inst.getCurrentNodeName(), approvalCard ? "审批" : "抄送"));
addField(fields, "状态", statusText(inst.getStatus()));
}
private JSONObject buildItem(MesXslApprovalInstance inst, String routePath, JSONArray fields,
String actionLabel, boolean canApprove, boolean approvalCard) {
JSONObject item = new JSONObject();
item.put("recordId", inst.getBizDataId());
item.put("fields", fields);
StringBuilder body = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
JSONObject f = fields.getJSONObject(i);
if (i > 0) {
body.append("\n");
}
body.append(f.getString("label")).append(": ").append(f.getString("value"));
}
item.put("body", body.toString());
String sep = routePath.contains("?") ? "&" : "?";
item.put("linkPath", routePath + sep + "imRecordId=" + inst.getBizDataId());
if (approvalCard) {
item.put("instanceId", inst.getId());
item.put("canApprove", canApprove);
item.put("nodeId", inst.getCurrentNodeId());
if (oConvertUtils.isNotEmpty(actionLabel)) {
item.put("actionLabel", actionLabel);
}
}
return item;
}
private Map<String, Object> loadBizRow(String table, String bizDataId) {
if (oConvertUtils.isEmpty(table) || oConvertUtils.isEmpty(bizDataId)
|| !IDENTIFIER.matcher(table).matches()) {
return Collections.emptyMap();
}
try {
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
"SELECT * FROM `" + table + "` WHERE id = ? LIMIT 1", bizDataId);
if (rows.isEmpty()) {
return Collections.emptyMap();
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】JDBC列名下划线转驼峰,与绑定字段对齐-----------
return normalizeRowKeys(rows.get(0));
//update-end---author:GHT ---date:20260610 for【IM审批通用化】JDBC列名下划线转驼峰,与绑定字段对齐-----------
} catch (Exception e) {
log.warn("加载业务单据失败 table={} id={}", table, bizDataId, e);
return Collections.emptyMap();
}
}
private Map<String, Object> normalizeRowKeys(Map<String, Object> raw) {
Map<String, Object> normalized = new LinkedHashMap<>();
if (raw == null) {
return normalized;
}
for (Map.Entry<String, Object> e : raw.entrySet()) {
String key = e.getKey();
if (oConvertUtils.isEmpty(key)) {
continue;
}
normalized.put(key, e.getValue());
if (key.contains("_")) {
String camel = oConvertUtils.camelName(key);
normalized.putIfAbsent(camel, e.getValue());
}
}
return normalized;
}
/** 与钉钉发起弹窗 defaultValueMode 对齐 */
private String resolveValueMode(JSONObject mapping, PrintBizFieldItemVO meta) {
String mode = mapping.getString("valueMode");
if (oConvertUtils.isNotEmpty(mode)) {
return mode.trim();
}
if (meta == null || oConvertUtils.isEmpty(meta.getTranslateKind())
|| "NONE".equalsIgnoreCase(meta.getTranslateKind())) {
return "raw";
}
String componentName = mapping.getString("componentName");
if (oConvertUtils.isNotEmpty(componentName) && DD_SELECT_COMPONENTS.contains(componentName)) {
return "raw";
}
return "text";
}
private Map<String, PrintBizFieldItemVO> buildFieldMetaMap(String bizCode) {
if (oConvertUtils.isEmpty(bizCode)) {
return Collections.emptyMap();
}
Map<String, PrintBizFieldItemVO> map = new LinkedHashMap<>();
for (PrintBizFieldItemVO f : listMainFieldsEnriched(bizCode)) {
if (f != null && StringUtils.isNotBlank(f.getFieldKey())) {
map.put(f.getFieldKey(), f);
}
}
Class<?> mainCls = resolveEntityClass(bizCode);
if (mainCls != null && fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(bizCode)) {
for (PrintBizDetailSlotVO slot : fieldCatalogProvider.listDetailSlots(bizCode)) {
mergeDetailMeta(map, bizCode, slot.getPropertyName(), slot.getSlotKind());
}
} else if (mainCls != null) {
for (PrintBizDetailSlotVO slot : PrintBizDetailPropertyScanner.listSlots(mainCls)) {
mergeDetailMeta(map, bizCode, slot.getPropertyName(), slot.getSlotKind());
}
}
return map;
}
private List<PrintBizFieldItemVO> listMainFieldsEnriched(String bizCode) {
Class<?> cls = resolveEntityClass(bizCode);
List<PrintBizFieldItemVO> fields;
if (fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(bizCode)) {
fields = fieldCatalogProvider.listMainFields(bizCode);
} else if (cls != null) {
fields = PrintBizEntityFieldIntrospector.listFields(cls);
} else {
return Collections.emptyList();
}
if (cls != null && fields != null && !fields.isEmpty()) {
PrintBizEntityFieldIntrospector.enrichDictMeta(fields, cls, null);
}
return fields != null ? fields : Collections.emptyList();
}
private void mergeDetailMeta(Map<String, PrintBizFieldItemVO> map, String bizCode, String prop, String kind) {
List<PrintBizFieldItemVO> detailFields;
if (fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(bizCode)) {
detailFields = fieldCatalogProvider.listPrefixedDetailFields(bizCode, prop, kind);
} else {
Class<?> mainCls = resolveEntityClass(bizCode);
detailFields = mainCls == null
? Collections.emptyList()
: PrintBizDetailPropertyScanner.listPrefixedDetailFields(mainCls, prop, kind);
}
Class<?> itemCls = resolveDetailItemClass(bizCode, prop, kind);
if (itemCls != null && detailFields != null && !detailFields.isEmpty()) {
PrintBizEntityFieldIntrospector.enrichDictMeta(detailFields, itemCls, null);
}
if (detailFields != null) {
for (PrintBizFieldItemVO f : detailFields) {
if (f != null && StringUtils.isNotBlank(f.getFieldKey())) {
map.put(f.getFieldKey(), f);
}
}
}
}
private Class<?> resolveEntityClass(String bizCode) {
PrintBizTypeVO vo = printBizPermEntityService.resolveBizTypeVo(bizCode);
if (vo == null || StringUtils.isBlank(vo.getDescription())) {
return null;
}
return PrintBizEntityFieldIntrospector.tryLoadClass(vo.getDescription().trim());
}
private Class<?> resolveDetailItemClass(String bizCode, String detailProperty, String slotKind) {
Class<?> mainCls = resolveEntityClass(bizCode);
if (mainCls == null) {
return null;
}
return PrintBizDetailPropertyScanner.resolveItemClassForSlot(mainCls, detailProperty, slotKind);
}
private void addField(JSONArray fields, String label, String value) {
JSONObject f = new JSONObject();
f.put("label", label);
f.put("value", oConvertUtils.getString(value, ""));
fields.add(f);
}
private String formatValue(Object val) {
if (val == null) {
return "";
}
if (val instanceof Date) {
return new SimpleDateFormat("yyyy-MM-dd").format((Date) val);
}
return String.valueOf(val);
}
private String statusText(String status) {
if ("1".equals(status)) {
return "已通过";
}
if ("2".equals(status)) {
return "已驳回";
}
if ("3".equals(status)) {
return "已撤销";
}
return "审批中";
}
}

View File

@@ -13,4 +13,7 @@ public interface IMesXslDingTplBindService extends IService<MesXslDingTplBind> {
/** 按业务编码查询绑定记录(未删除的第一条) */
MesXslDingTplBind getByBizCode(String bizCode);
/** 按前端路由解析启用的钉钉模板绑定(模板停用则返回 null */
MesXslDingTplBind resolveActiveByRoutePath(String routePath);
}

View File

@@ -2,11 +2,18 @@ package org.jeecg.modules.xslmes.dingtalk.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingTplBind;
import org.jeecg.modules.xslmes.dingtalk.mapper.MesXslDingTplBindMapper;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingProcessTplService;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingTplBindService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 钉钉审批模板绑定 ServiceImpl
*
@@ -17,8 +24,43 @@ import org.springframework.stereotype.Service;
public class MesXslDingTplBindServiceImpl extends ServiceImpl<MesXslDingTplBindMapper, MesXslDingTplBind>
implements IMesXslDingTplBindService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private IMesXslDingProcessTplService tplService;
@Override
public MesXslDingTplBind getByBizCode(String bizCode) {
return getOne(new QueryWrapper<MesXslDingTplBind>().eq("biz_code", bizCode).last("LIMIT 1"));
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】按路由解析启用的钉钉模板绑定-----------
@Override
public MesXslDingTplBind resolveActiveByRoutePath(String routePath) {
if (StringUtils.isBlank(routePath)) {
return null;
}
String path = routePath.trim();
String sql = "SELECT id FROM sys_permission WHERE url = ? AND del_flag = 0 LIMIT 1";
List<String> ids;
try {
ids = jdbcTemplate.queryForList(sql, String.class, path);
} catch (Exception e) {
return null;
}
if (ids.isEmpty()) {
return null;
}
MesXslDingTplBind bind = getByBizCode(ids.get(0));
if (bind == null || StringUtils.isBlank(bind.getTemplateId())) {
return null;
}
MesXslDingProcessTpl tpl = tplService.getById(bind.getTemplateId());
if (tpl == null || !"1".equals(tpl.getStatus())) {
return null;
}
return bind;
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】按路由解析启用的钉钉模板绑定-----------
}

View File

@@ -0,0 +1,69 @@
package org.jeecg.modules.xslmes.entity;
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 io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 自动小料计划维护
*/
@Data
@TableName("mes_xsl_auto_small_material_plan_maintain")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "自动小料计划维护")
public class MesXslAutoSmallMaterialPlanMaintain implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Excel(name = "排序号", width = 10)
private Integer sortNo;
@Excel(name = "配方名称", width = 20)
private String formulaName;
@Excel(name = "早班序号", width = 12)
private Integer morningSeqNo;
@Excel(name = "早班计划", width = 12)
private Integer morningPlanCount;
@Excel(name = "早班备注", width = 20)
private String morningRemark;
@Excel(name = "中班序号", width = 12)
private Integer noonSeqNo;
@Excel(name = "中班计划", width = 12)
private Integer noonPlanCount;
@Excel(name = "中班备注", width = 20)
private String noonRemark;
@Excel(name = "夜班序号", width = 12)
private Integer nightSeqNo;
@Excel(name = "夜班计划", width = 12)
private Integer nightPlanCount;
@Excel(name = "夜班备注", width = 20)
private String nightRemark;
private Integer tenantId;
private String sysOrgCode;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
}

View File

@@ -0,0 +1,69 @@
package org.jeecg.modules.xslmes.entity;
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 io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 人工小料计划维护
*/
@Data
@TableName("mes_xsl_manual_small_material_plan_maintain")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "人工小料计划维护")
public class MesXslManualSmallMaterialPlanMaintain implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Excel(name = "排序号", width = 10)
private Integer sortNo;
@Excel(name = "配方名称", width = 20)
private String formulaName;
@Excel(name = "早班序号", width = 12)
private Integer morningSeqNo;
@Excel(name = "早班计划", width = 12)
private Integer morningPlanCount;
@Excel(name = "早班备注", width = 20)
private String morningRemark;
@Excel(name = "中班序号", width = 12)
private Integer noonSeqNo;
@Excel(name = "中班计划", width = 12)
private Integer noonPlanCount;
@Excel(name = "中班备注", width = 20)
private String noonRemark;
@Excel(name = "夜班序号", width = 12)
private Integer nightSeqNo;
@Excel(name = "夜班计划", width = 12)
private Integer nightPlanCount;
@Excel(name = "夜班备注", width = 20)
private String nightRemark;
private Integer tenantId;
private String sysOrgCode;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
}

View File

@@ -38,6 +38,16 @@ public class MesXslMixerAction implements Serializable {
@Schema(description = "设备名称冗余")
private String equipmentName;
//update-begin---author:GHT ---date:20260617 for【MES上辅机】密炼动作秒级采集-补全机台字段-----------
@Excel(name = "机台编号", width = 15)
@Schema(description = "机台编号(采集自中间表 EquipID")
private String equipId;
@Excel(name = "机台类型", width = 15)
@Schema(description = "机台类型(采集自中间表 EquipType")
private String equipType;
//update-end---author:GHT ---date:20260617 for【MES上辅机】密炼动作秒级采集-补全机台字段-----------
@Excel(name = "动作名称", width = 20)
@Schema(description = "动作名称")
private String actionName;

View File

@@ -0,0 +1,99 @@
package org.jeecg.modules.xslmes.entity;
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 io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 密炼生产计划维护
*/
@Data
@TableName("mes_xsl_mixing_production_plan")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "密炼生产计划维护")
public class MesXslMixingProductionPlan implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Excel(name = "排序号", width = 10)
@Schema(description = "排序号")
private Integer sortNo;
@Excel(name = "机台", width = 20, dictTable = "mes_xsl_equipment_ledger", dicText = "equipment_name", dicCode = "id")
@Dict(dictTable = "mes_xsl_equipment_ledger", dicText = "equipment_name", dicCode = "id")
@Schema(description = "机台ID")
private String machineId;
@Excel(name = "机台", width = 20)
@Schema(description = "机台名称")
private String machineName;
@Excel(name = "班次标识", width = 10)
@Schema(description = "班次标识1早班 2中班 3晚班")
private Integer shiftFlag;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "计划日期(保存时写入当前日期)")
private Date planDate;
@Schema(description = "计划号yyyyMMddA三位流水")
private String planNo;
@Schema(description = "计划ID母胶/终胶计划)")
private String planId;
@Schema(description = "计划类型 M母胶/F终胶")
private String planType;
@Schema(description = "生产订单ID")
private String sourceOrderId;
@Schema(description = "胶料ID取母胶/终胶计划materialCode")
private String materialId;
@Schema(description = "胶料名称(取母胶/终胶计划mesMaterialName")
private String materialName;
@Excel(name = "生产订单", width = 20)
private String orderNo;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "订单日期")
private Date orderDate;
@Excel(name = "配方名称", width = 20)
private String formulaName;
@Excel(name = "计划重量", width = 15)
private BigDecimal planWeight;
@Excel(name = "计划车数", width = 12)
private Integer plannedCarCount;
@Excel(name = "已排产车数", width = 12)
private Integer scheduledCarCount;
@Excel(name = "完成车数", width = 12)
private Integer finishedCarCount;
@Excel(name = "计划", width = 12)
private Integer planCount;
@Excel(name = "备注", width = 20)
private String remark;
private Integer tenantId;
private String sysOrgCode;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
}

View File

@@ -89,6 +89,20 @@ public class MesXslMixingSpec implements Serializable {
@Schema(description = "自动小料打印设定")
private String autoSmallPrintSetting;
//update-begin---author:cursor ---date:20260612 for【XSLMES-20260612-A02】混炼示方小料称重上下限容差-----------
@Schema(description = "人工小料计量下限容差(KG)")
private BigDecimal manualSmallWeighLowerTol;
@Schema(description = "人工小料计量上限容差(KG)")
private BigDecimal manualSmallWeighUpperTol;
@Schema(description = "自动小料计量下限容差(KG)")
private BigDecimal autoSmallWeighLowerTol;
@Schema(description = "自动小料计量上限容差(KG)")
private BigDecimal autoSmallWeighUpperTol;
//update-end---author:cursor ---date:20260612 for【XSLMES-20260612-A02】混炼示方小料称重上下限容差-----------
@Schema(description = "设定车数")
private Integer setTrainCount;

View File

@@ -0,0 +1,71 @@
package org.jeecg.modules.xslmes.entity;
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 io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 混炼示方历史记录
*/
@Data
@TableName("mes_xsl_mixing_spec_history")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "混炼示方历史记录")
public class MesXslMixingSpecHistory implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
@Schema(description = "混炼示方主表ID")
private String mixingSpecId;
@Excel(name = "规格名", width = 20)
@Schema(description = "规格名")
private String specName;
@Excel(name = "发行编号", width = 16)
@Schema(description = "发行编号")
private String issueNumber;
@Excel(name = "版本号", width = 10)
@Schema(description = "版本号")
private String versionNo;
@Excel(name = "操作类型", width = 10, dicCode = "xslmes_formula_spec_edit_log_action")
@Dict(dicCode = "xslmes_formula_spec_edit_log_action")
@Schema(description = "操作类型 create=新增 update=修改")
private String actionType;
@Schema(description = "完整快照JSON")
private String snapshotJson;
@Excel(name = "操作时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "操作时间")
private Date operateTime;
@Schema(description = "操作人账号")
private String operateBy;
@Excel(name = "操作人", width = 14)
@Schema(description = "操作人姓名")
private String operateByName;
@Schema(description = "租户ID")
private Integer tenantId;
}

View File

@@ -0,0 +1,44 @@
package org.jeecg.modules.xslmes.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
/**
* 原材料需求计划汇总(按原材料或按机台+原材料)
*/
@Data
@Schema(description = "原材料需求计划汇总")
public class MesXslRawMaterialDemandPlanSummary implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private String id;
@Excel(name = "机台", width = 20)
@Schema(description = "机台")
private String machineName;
@Excel(name = "ERP编号", width = 18)
@Schema(description = "ERP编号")
private String erpCode;
@Excel(name = "原材料名称", width = 24)
@Schema(description = "原材料名称")
private String rawMaterialName;
@Excel(name = "需求重量", width = 15)
@Schema(description = "需求重量")
private BigDecimal demandWeight;
@Excel(name = "标准重量", width = 15)
@Schema(description = "标准重量")
private BigDecimal standardWeight;
@Excel(name = "实际重量", width = 15)
@Schema(description = "实际重量")
private BigDecimal actualWeight;
}

View File

@@ -78,6 +78,7 @@ public class MesXslRubberQuickTestRecord implements Serializable {
private Date inspectTime;
@Schema(description = "检验人用户ID")
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "id")
private String inspectorUserId;
private String inspectorUsername;
@@ -129,4 +130,8 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@TableField(exist = false)
private List<MesXslRubberQuickTestRecordLine> lineList;
@TableField(exist = false)
@Schema(description = "原始数据明细(试验结果全部检测值)")
private List<MesXslRubberQuickTestRecordRawLine> rawLineList;
}

View File

@@ -0,0 +1,72 @@
package org.jeecg.modules.xslmes.entity;
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 io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* MES 胶料快检记录原始数据明细(试验结果全部检测值)
*/
@Data
@TableName("mes_xsl_rubber_quick_test_record_raw_line")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES胶料快检记录原始数据明细")
public class MesXslRubberQuickTestRecordRawLine implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
private String id;
@Schema(description = "主表ID mes_xsl_rubber_quick_test_record.id")
private String recordId;
@Excel(name = "编号", width = 14)
@Schema(description = "试验结果编号如1_1")
private String rowNo;
@Schema(description = "数据点ID")
private String dataPointId;
@Excel(name = "数据点", width = 18)
private String inspectItem;
@Excel(name = "下限值", width = 12, type = 10)
private BigDecimal lowerLimit;
@Excel(name = "上限值", width = 12, type = 10)
private BigDecimal upperLimit;
@Excel(name = "检测值", width = 12, type = 10)
private BigDecimal inspectValue;
@Excel(name = "行检验结果", width = 10, dicCode = "xslmes_rubber_quick_test_record_result")
@Dict(dicCode = "xslmes_rubber_quick_test_record_result")
@Schema(description = "行检验结果1合格0不合格")
private String rowInspectResult;
private Integer sortNo;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -106,4 +106,12 @@ public class MesXslRubberQuickTestStd implements Serializable {
@TableField(exist = false)
@Schema(description = "标准明细")
private List<MesXslRubberQuickTestStdLine> lineList;
@TableField(exist = false)
@Schema(description = "实验类型ID来自实验方法桌面端展示")
private String quickTestTypeId;
@TableField(exist = false)
@Schema(description = "实验类型名称(来自实验方法,桌面端展示)")
private String quickTestTypeName;
}

View File

@@ -0,0 +1,35 @@
package org.jeecg.modules.xslmes.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
/**
* 小料需求计划汇总(自动/人工)
*/
@Data
@Schema(description = "小料需求计划汇总")
public class MesXslSmallMaterialDemandPlanSummary implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private String id;
@Excel(name = "机台", width = 20)
@Schema(description = "机台")
private String machineName;
@Excel(name = "原材料名称", width = 24)
@Schema(description = "原材料名称")
private String rawMaterialName;
@Excel(name = "需求重量(KG)", width = 18)
@Schema(description = "需求重量(KG)")
private BigDecimal demandWeight;
@Schema(description = "统计日期(yyyy-MM-dd)")
private String statDate;
}

View File

@@ -0,0 +1,7 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslAutoSmallMaterialPlanMaintain;
public interface MesXslAutoSmallMaterialPlanMaintainMapper
extends BaseMapper<MesXslAutoSmallMaterialPlanMaintain> {}

View File

@@ -0,0 +1,7 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslManualSmallMaterialPlanMaintain;
public interface MesXslManualSmallMaterialPlanMaintainMapper
extends BaseMapper<MesXslManualSmallMaterialPlanMaintain> {}

View File

@@ -0,0 +1,6 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslMixingProductionPlan;
public interface MesXslMixingProductionPlanMapper extends BaseMapper<MesXslMixingProductionPlan> {}

View File

@@ -0,0 +1,9 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecHistory;
/**
* 混炼示方历史记录
*/
public interface MesXslMixingSpecHistoryMapper extends BaseMapper<MesXslMixingSpecHistory> {}

View File

@@ -0,0 +1,6 @@
package org.jeecg.modules.xslmes.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestRecordRawLine;
public interface MesXslRubberQuickTestRecordRawLineMapper extends BaseMapper<MesXslRubberQuickTestRecordRawLine> {}

View File

@@ -0,0 +1,44 @@
package org.jeecg.modules.xslmes.mcs.config;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsDbConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 启动时加载中间库配置,覆盖 application.yml 中的 sqlserver_mcs 连接
*/
@Slf4j
@Component
@Order(100)
public class McsDataSourceInitializer implements ApplicationRunner {
@Autowired
private IMesXslMcsDbConfigService mcsDbConfigService;
@Autowired
private McsDataSourceManager mcsDataSourceManager;
//update-begin---author:GHT ---date:20260616 for【MES上辅机】启动时加载中间库可视化配置-----------
@Override
public void run(ApplicationArguments args) {
try {
MesXslMcsDbConfig config = mcsDbConfigService.loadStartupConfig();
if (config != null) {
mcsDataSourceManager.applyConfig(config);
log.info("[MCS中间库] 已加载数据库中的中间库配置 tenantId={}, status={}, host={}:{}",
config.getTenantId(), config.getStatus(), config.getServerHost(), config.getServerPort());
} else {
log.info("[MCS中间库] 数据库中无中间库配置,继续使用 application.yml 中的 sqlserver_mcs");
}
} catch (Exception e) {
log.error("[MCS中间库] 启动加载配置失败,将使用 yml 默认连接", e);
}
}
//update-end---author:GHT ---date:20260616 for【MES上辅机】启动时加载中间库可视化配置-----------
}

View File

@@ -0,0 +1,64 @@
package org.jeecg.modules.xslmes.mcs.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 注册 MCS 中间库 HTTP 拦截器(读取/写入开关在接口层生效)
*/
@Configuration
public class McsWebMvcConfig implements WebMvcConfigurer {
@Autowired
private McsDataSourceManager mcsDataSourceManager;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(mcsReadWriteInterceptor())
.addPathPatterns("/xslmes/mcs/**")
.excludePathPatterns("/xslmes/mcs/dbConfig/**");
}
//update-begin---author:GHT ---date:20260616 for【MES上辅机】HTTP层拦截中间库读写请求-----------
private HandlerInterceptor mcsReadWriteInterceptor() {
return new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!mcsDataSourceManager.hasSavedConfig()) {
return true;
}
String uri = request.getRequestURI();
if (uri.contains("/mcs/dbConfig")) {
return true;
}
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
if (!mcsDataSourceManager.isReadEnabled()) {
throw new JeecgBootException("中间库读取已关闭,请在「中间库连接配置」中开启读取开关并保存");
}
if (isWriteRequest(request, uri) && !mcsDataSourceManager.isWriteEnabled()) {
throw new JeecgBootException("中间库写入已关闭,请在「中间库连接配置」中开启写入开关并保存");
}
return true;
}
private boolean isWriteRequest(HttpServletRequest request, String uri) {
String method = request.getMethod();
boolean mutating = "POST".equalsIgnoreCase(method)
|| "PUT".equalsIgnoreCase(method)
|| "DELETE".equalsIgnoreCase(method)
|| "PATCH".equalsIgnoreCase(method);
return mutating && uri.toLowerCase().contains("mestomcs");
}
};
}
//update-end---author:GHT ---date:20260616 for【MES上辅机】HTTP层拦截中间库读写请求-----------
}

View File

@@ -0,0 +1,85 @@
package org.jeecg.modules.xslmes.mcs.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.mybatis.MybatisPlusSaasConfig;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsDbConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* MES上辅机 SQL Server 中间库连接配置
*/
@Tag(name = "MES上辅机中间库配置")
@RestController
@RequestMapping("/xslmes/mcs/dbConfig")
public class MesXslMcsDbConfigController {
@Autowired
private IMesXslMcsDbConfigService mcsDbConfigService;
@Autowired
private McsDataSourceManager mcsDataSourceManager;
private Integer resolveTenantId(Integer tenantId) {
if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
if (tenantId == null) {
tenantId = oConvertUtils.getInt(TenantContext.getTenant(), -1);
}
} else if (tenantId == null) {
tenantId = oConvertUtils.getInt(TenantContext.getTenant(), 0);
}
return tenantId;
}
@Operation(summary = "获取中间库配置")
@GetMapping("/get")
public Result<MesXslMcsDbConfig> getConfig(@RequestParam(name = "tenantId", required = false) Integer tenantId) {
return Result.OK(mcsDbConfigService.getCurrentConfig(resolveTenantId(tenantId)));
}
@Operation(summary = "保存中间库配置")
@PostMapping("/save")
public Result<String> saveConfig(@RequestBody MesXslMcsDbConfig config) {
if (config.getTenantId() == null) {
config.setTenantId(resolveTenantId(null));
}
return mcsDbConfigService.saveOrUpdateConfig(config);
}
@Operation(summary = "测试中间库连接")
@GetMapping("/testConnect")
public Result<String> testConnect(@RequestParam(name = "tenantId", required = false) Integer tenantId) {
return mcsDbConfigService.testConnection(resolveTenantId(tenantId));
}
@Operation(summary = "删除中间库配置")
@DeleteMapping("/delete")
public Result<String> deleteConfig(@RequestParam(name = "id") String id) {
return mcsDbConfigService.deleteConfig(id);
}
@Operation(summary = "获取中间库连接状态")
@GetMapping("/status")
public Result<Map<String, Object>> status() {
Map<String, Object> data = new LinkedHashMap<>();
MesXslMcsDbConfig cfg = mcsDataSourceManager.getCachedConfig();
data.put("dbConfigActive", mcsDataSourceManager.isDbConfigActive());
data.put("readEnabled", mcsDataSourceManager.isReadEnabled());
data.put("writeEnabled", mcsDataSourceManager.isWriteEnabled());
if (cfg != null) {
data.put("serverHost", cfg.getServerHost());
data.put("serverPort", cfg.getServerPort());
data.put("dbName", cfg.getDbName());
}
return Result.OK(data);
}
}

View File

@@ -0,0 +1,150 @@
package org.jeecg.modules.xslmes.mcs.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
import org.jeecg.modules.xslmes.mcs.mapper.McsMetaMapper;
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsSyncConfigService;
import org.jeecg.modules.xslmes.mcs.sync.McsSyncScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* MES上辅机 中间表采集配置(表/字段绑定 + 采集操作 + 元数据)
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
@Tag(name = "MES上辅机采集配置")
@RestController
@RequestMapping("/xslmes/mcs/syncConfig")
public class MesXslMcsSyncConfigController {
@Autowired
private IMesXslMcsSyncConfigService syncConfigService;
@Autowired
private McsSyncScheduler syncScheduler;
@Autowired
private McsMetaMapper metaMapper;
@Autowired
private McsDataSourceManager mcsDataSourceManager;
@Operation(summary = "采集配置-分页列表")
@GetMapping("/list")
public Result<IPage<MesXslMcsSyncConfig>> list(MesXslMcsSyncConfig query,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
LambdaQueryWrapper<MesXslMcsSyncConfig> qw = new LambdaQueryWrapper<MesXslMcsSyncConfig>()
.eq(MesXslMcsSyncConfig::getDelFlag, 0)
.like(StringUtils.isNotBlank(query.getConfigName()), MesXslMcsSyncConfig::getConfigName, query.getConfigName())
.like(StringUtils.isNotBlank(query.getSourceTable()), MesXslMcsSyncConfig::getSourceTable, query.getSourceTable())
.orderByDesc(MesXslMcsSyncConfig::getUpdateTime);
IPage<MesXslMcsSyncConfig> page = syncConfigService.page(new Page<>(pageNo, pageSize), qw);
page.getRecords().forEach(c -> c.setRunning(syncScheduler.isRunning(c.getId())));
return Result.OK(page);
}
@Operation(summary = "采集配置-详情(含字段映射)")
@GetMapping("/queryById")
public Result<MesXslMcsSyncConfig> queryById(@RequestParam("id") String id) {
MesXslMcsSyncConfig cfg = syncConfigService.getDetail(id);
if (cfg == null) {
return Result.error("配置不存在");
}
cfg.setRunning(syncScheduler.isRunning(id));
return Result.OK(cfg);
}
@Operation(summary = "采集配置-按业务类型获取(密炼动作页用)")
@GetMapping("/getByBizType")
public Result<MesXslMcsSyncConfig> getByBizType(@RequestParam(name = "bizType", defaultValue = "MIX_ACT") String bizType) {
MesXslMcsSyncConfig cfg = syncConfigService.getByBizType(bizType);
if (cfg != null) {
cfg.setRunning(syncScheduler.isRunning(cfg.getId()));
}
return Result.OK(cfg);
}
@Operation(summary = "采集配置-新增")
@RequiresPermissions("xslmes:mcsSyncConfig:add")
@PostMapping("/add")
public Result<String> add(@RequestBody MesXslMcsSyncConfig config) {
config.setId(null);
return syncConfigService.saveConfig(config);
}
@Operation(summary = "采集配置-编辑")
@RequiresPermissions("xslmes:mcsSyncConfig:edit")
@PostMapping("/edit")
public Result<String> edit(@RequestBody MesXslMcsSyncConfig config) {
if (StringUtils.isBlank(config.getId())) {
return Result.error("缺少配置ID");
}
return syncConfigService.saveConfig(config);
}
@Operation(summary = "采集配置-删除")
@RequiresPermissions("xslmes:mcsSyncConfig:delete")
@DeleteMapping("/delete")
public Result<String> delete(@RequestParam("id") String id) {
return syncConfigService.deleteConfig(id);
}
@Operation(summary = "采集操作-是否采集+采集间隔")
@RequiresPermissions("xslmes:mcsSyncConfig:setting")
@PostMapping("/saveCollect")
public Result<String> saveCollect(@RequestBody MesXslMcsSyncConfig body) {
return syncConfigService.saveCollect(body);
}
// ===================== 元数据 =====================
@Operation(summary = "元数据-中间库表清单")
@GetMapping("/meta/sourceTables")
public Result<List<Map<String, Object>>> sourceTables() {
if (!mcsDataSourceManager.isDbConfigActive()) {
return Result.error("中间库未连接,请先在「中间库连接配置」中启用");
}
return Result.OK(metaMapper.listSourceTables());
}
@Operation(summary = "元数据-中间库表字段")
@GetMapping("/meta/sourceColumns")
public Result<List<Map<String, Object>>> sourceColumns(@RequestParam("table") String table) {
if (!mcsDataSourceManager.isDbConfigActive()) {
return Result.error("中间库未连接,请先在「中间库连接配置」中启用");
}
if (!table.matches("^[A-Za-z0-9_]+$")) {
return Result.error("非法表名");
}
return Result.OK(metaMapper.listSourceColumns(table));
}
@Operation(summary = "元数据-MES业务表清单(mes_xsl_前缀)")
@GetMapping("/meta/targetTables")
public Result<List<Map<String, Object>>> targetTables() {
return Result.OK(metaMapper.listTargetTables());
}
@Operation(summary = "元数据-MES表字段")
@GetMapping("/meta/targetColumns")
public Result<List<Map<String, Object>>> targetColumns(@RequestParam("table") String table) {
if (!table.matches("^[A-Za-z0-9_]+$")) {
return Result.error("非法表名");
}
return Result.OK(metaMapper.listTargetColumns(table));
}
}

View File

@@ -0,0 +1,48 @@
package org.jeecg.modules.xslmes.mcs.datasource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.jeecg.common.exception.JeecgBootException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* MCS 中间库读写开关守卫
* <ul>
* <li>读取开关拦截所有中间表查询McsToMes + MesToMcs 列表/详情等)</li>
* <li>写入开关:仅拦截 MES→MCS 方向的增删改</li>
* </ul>
*/
@Slf4j
@Aspect
@Component
@Order(1)
public class McsDataSourceGuardAspect {
@Autowired
private McsDataSourceManager mcsDataSourceManager;
//update-begin---author:GHT ---date:20260616 for【MES上辅机】读取开关拦截全部中间表查询-----------
@Before("(execution(* com.baomidou.mybatisplus.extension.service.IService+.*(..)) "
+ "&& (target(org.jeecg.modules.xslmes.mcs.service.impl.McsToMes*) "
+ "|| target(org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*)))")
public void guardRead(JoinPoint joinPoint) {
if (!mcsDataSourceManager.isReadEnabled()) {
throw new JeecgBootException("中间库读取已关闭,请在「中间库连接配置」中开启读取开关并保存");
}
}
@Before("execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.save*(..)) || "
+ "execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.update*(..)) || "
+ "execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.remove*(..)) || "
+ "execution(* org.jeecg.modules.xslmes.mcs.service.impl.MesToMcs*.delete*(..))")
public void guardWrite(JoinPoint joinPoint) {
if (!mcsDataSourceManager.isWriteEnabled()) {
throw new JeecgBootException("中间库写入已关闭,请在「中间库连接配置」中开启写入开关并保存");
}
}
//update-end---author:GHT ---date:20260616 for【MES上辅机】读取开关拦截全部中间表查询-----------
}

View File

@@ -0,0 +1,250 @@
package org.jeecg.modules.xslmes.mcs.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.druid.DruidConfig;
import com.baomidou.dynamic.datasource.creator.druid.DruidDataSourceCreator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.util.dynamic.db.DataSourceCachePool;
import org.jeecg.modules.system.util.SecurityUtil;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.atomic.AtomicReference;
/**
* MCS SQL Server 动态数据源管理器
* <p>配置保存后热刷新 sqlserver_mcs 数据源,无需改 application.yml</p>
*/
@Slf4j
@Component
public class McsDataSourceManager {
public static final String DS_KEY = "sqlserver_mcs";
private final AtomicReference<MesXslMcsDbConfig> cachedConfig = new AtomicReference<>();
@Autowired
private DataSource dataSource;
@Autowired
private DruidDataSourceCreator dataSourceCreator;
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.url:}")
private String ymlUrl;
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.username:}")
private String ymlUsername;
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.password:}")
private String ymlPassword;
@Value("${spring.datasource.dynamic.datasource.sqlserver_mcs.driver-class-name:com.microsoft.sqlserver.jdbc.SQLServerDriver}")
private String ymlDriverClassName;
public MesXslMcsDbConfig getCachedConfig() {
return cachedConfig.get();
}
public boolean hasSavedConfig() {
return cachedConfig.get() != null;
}
public boolean isDbConfigActive() {
MesXslMcsDbConfig cfg = cachedConfig.get();
if (cfg == null || !Integer.valueOf(1).equals(cfg.getStatus())) {
return false;
}
DynamicRoutingDataSource routing = (DynamicRoutingDataSource) dataSource;
return routing.getDataSources().containsKey(DS_KEY);
}
public boolean isReadEnabled() {
MesXslMcsDbConfig cfg = cachedConfig.get();
if (cfg == null) {
return true;
}
return Integer.valueOf(1).equals(cfg.getReadEnabled());
}
public boolean isWriteEnabled() {
MesXslMcsDbConfig cfg = cachedConfig.get();
if (cfg == null) {
return true;
}
return Integer.valueOf(1).equals(cfg.getWriteEnabled());
}
//update-begin---author:GHT ---date:20260616 for【MES上辅机】SQL Server中间库可视化配置-----------
/**
* 将配置应用到动态数据源(启用时注册/更新,禁用时移除)
*/
public void applyConfig(MesXslMcsDbConfig config) {
DynamicRoutingDataSource routing = (DynamicRoutingDataSource) dataSource;
closeAndRemoveDataSource(routing);
DataSourceCachePool.removeCache(DS_KEY);
if (config == null) {
cachedConfig.set(null);
restoreYmlDataSource(routing);
log.info("[MCS中间库] 已清除页面配置");
return;
}
if (Integer.valueOf(1).equals(config.getStatus())) {
try {
String plainPassword = decryptPassword(config.getDbPassword());
DataSourceProperty property = buildDataSourceProperty(config, plainPassword);
DataSource mcsDs = dataSourceCreator.createDataSource(property);
routing.addDataSource(DS_KEY, mcsDs);
cachedConfig.set(config);
log.info("[MCS中间库] 动态数据源 {} 已热刷新(无需重启): {}:{}/{}", DS_KEY,
config.getServerHost(), config.getServerPort(), config.getDbName());
} catch (Exception e) {
cachedConfig.set(config);
log.error("[MCS中间库] 刷新动态数据源失败", e);
throw new RuntimeException("刷新中间库数据源失败:" + e.getMessage(), e);
}
return;
}
cachedConfig.set(config);
restoreYmlDataSource(routing);
log.info("[MCS中间库] 连接未启用,读写开关仍按页面配置生效");
}
private void closeAndRemoveDataSource(DynamicRoutingDataSource routing) {
if (!routing.getDataSources().containsKey(DS_KEY)) {
return;
}
DataSource old = routing.getDataSource(DS_KEY);
routing.removeDataSource(DS_KEY);
closeDataSourceQuietly(old);
}
private void closeDataSourceQuietly(DataSource ds) {
if (ds instanceof DruidDataSource druid && druid.isEnable()) {
try {
druid.close();
} catch (Exception e) {
log.warn("[MCS中间库] 关闭旧连接池失败: {}", e.getMessage());
}
}
}
/**
* 测试 JDBC 连通性(不写入动态数据源)
*/
public String testConnection(MesXslMcsDbConfig config, String plainPassword) {
if (config == null) {
return "配置为空";
}
String jdbcUrl = buildJdbcUrl(config);
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
try (Connection conn = DriverManager.getConnection(jdbcUrl, config.getDbUsername(), plainPassword)) {
if (conn.isValid(5)) {
return null;
}
return "连接无效";
}
} catch (Exception e) {
log.warn("[MCS中间库] 连接测试失败: {}", e.getMessage());
return e.getMessage();
}
}
public String buildJdbcUrl(MesXslMcsDbConfig config) {
int loginTimeout = config.getLoginTimeout() != null ? config.getLoginTimeout() : 120;
int connectTimeout = config.getConnectTimeout() != null ? config.getConnectTimeout() : 120000;
String host = resolveHost(config);
int port = config.getServerPort() != null ? config.getServerPort() : 1433;
String dbName = StringUtils.isNotBlank(config.getDbName()) ? config.getDbName() : "MES_ShareDB";
return "jdbc:sqlserver://" + host + ":" + port
+ ";DatabaseName=" + dbName
+ ";encrypt=false;trustServerCertificate=true;SelectMethod=cursor"
+ ";loginTimeout=" + loginTimeout
+ ";connectTimeout=" + connectTimeout + ";";
}
private DataSourceProperty buildDataSourceProperty(MesXslMcsDbConfig config, String plainPassword) {
DataSourceProperty property = new DataSourceProperty();
property.setPoolName(DS_KEY);
property.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
property.setUrl(buildJdbcUrl(config));
property.setUsername(config.getDbUsername());
property.setPassword(plainPassword);
applySqlServerDruidConfig(property);
return property;
}
/**
* SQL Server 不支持 Oracle/MySQL 的 DUAL 表,覆盖全局 validationQuery 避免连接池校验失败
*/
private void applySqlServerDruidConfig(DataSourceProperty property) {
DruidConfig druid = property.getDruid();
if (druid == null) {
druid = new DruidConfig();
property.setDruid(druid);
}
druid.setValidationQuery("SELECT 1");
}
private void restoreYmlDataSource(DynamicRoutingDataSource routing) {
if (StringUtils.isBlank(ymlUrl)) {
log.warn("[MCS中间库] yml 中未配置 sqlserver_mcs无法恢复默认数据源");
return;
}
try {
DataSourceProperty property = new DataSourceProperty();
property.setPoolName(DS_KEY);
property.setDriverClassName(ymlDriverClassName);
property.setUrl(ymlUrl);
property.setUsername(ymlUsername);
property.setPassword(ymlPassword);
applySqlServerDruidConfig(property);
routing.addDataSource(DS_KEY, dataSourceCreator.createDataSource(property));
} catch (Exception e) {
log.error("[MCS中间库] 恢复 yml 默认数据源失败", e);
}
}
private String resolveHost(MesXslMcsDbConfig config) {
String host = config.getServerHost() == null ? "" : config.getServerHost().trim();
if (host.contains(":")) {
String[] parts = host.split(":", 2);
host = parts[0];
if (config.getServerPort() == null || config.getServerPort() == 1433) {
try {
config.setServerPort(Integer.parseInt(parts[1]));
} catch (NumberFormatException ignored) {
// 端口解析失败时保留原端口
}
}
}
return host;
}
public String decryptPassword(String encrypted) {
if (StringUtils.isBlank(encrypted)) {
return "";
}
try {
return SecurityUtil.jiemi(encrypted);
} catch (Exception e) {
return encrypted;
}
}
public String encryptPassword(String plain) {
if (StringUtils.isBlank(plain)) {
return plain;
}
return SecurityUtil.jiami(plain);
}
//update-end---author:GHT ---date:20260616 for【MES上辅机】SQL Server中间库可视化配置-----------
}

View File

@@ -0,0 +1,80 @@
package org.jeecg.modules.xslmes.mcs.entity;
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.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* MES上辅机 SQL Server 中间库配置
*/
@Data
@TableName("mes_xsl_mcs_db_config")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES上辅机SQL Server中间库配置")
public class MesXslMcsDbConfig implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
@Schema(description = "租户ID")
private Integer tenantId;
@Schema(description = "服务器地址")
private String serverHost;
@Schema(description = "端口")
private Integer serverPort;
@Schema(description = "数据库名")
private String dbName;
@Schema(description = "用户名")
private String dbUsername;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Schema(description = "密码")
private String dbPassword;
@Schema(description = "读取开关(0-关,1-开)")
private Integer readEnabled;
@Schema(description = "写入开关(0-关,1-开)")
private Integer writeEnabled;
@Schema(description = "启用状态(0-关,1-开)")
private Integer status;
@Schema(description = "登录超时(秒)")
private Integer loginTimeout;
@Schema(description = "连接超时(毫秒)")
private Integer connectTimeout;
@Schema(description = "备注")
private String remark;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@@ -0,0 +1,126 @@
package org.jeecg.modules.xslmes.mcs.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* MES上辅机 中间表采集配置(通用)
* <p>按 bizType 区分不同业务(密炼动作/报警/配方等),供秒级定时采集统一复用</p>
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】密炼动作秒级采集
*/
@Data
@TableName("mes_xsl_mcs_sync_config")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES上辅机中间表采集配置")
public class MesXslMcsSyncConfig implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
@Schema(description = "业务类型(采集任务唯一标识,如 MIX_ACT 密炼动作;通用配置可为空)")
private String bizType;
@Schema(description = "配置名称")
private String configName;
@Schema(description = "业务名称")
private String bizName;
@Schema(description = "源中间表名")
private String sourceTable;
@Schema(description = "源中间表注释")
private String sourceTableComment;
@Schema(description = "MES目标表名")
private String targetTable;
@Schema(description = "MES目标表注释")
private String targetTableComment;
@Schema(description = "采集时间间隔(秒)默认1秒")
private Integer intervalSeconds;
@Schema(description = "采集状态(0停止,1运行)")
private String status;
@Schema(description = "采集模式(FULL全量匹配,TIME时间匹配,INCR增量匹配-标记位回写)")
private String syncMode;
@Schema(description = "时间列/标记列(源表列名)。TIME模式=时间列INCR模式=同步标记列(为空表示未采集,采集后回写'1')")
private String incrColumn;
@Schema(description = "时间范围(TODAY当天,LAST7最近七天)")
private String timeWindow;
@Schema(description = "每轮最大采集行数(INCR模式TOP N)")
private Integer batchLimit;
@Schema(description = "增量采集高水位(INCR模式自动维护)")
private String lastWatermark;
@Schema(description = "增量标记采集条件(IS_NULL为空,EQ_EMPTY等于匹配值,NE_EMPTY不等于匹配值)INCR模式用")
private String flagCondition;
//update-begin---author:GHT ---date:20260617 for【MES上辅机】增量采集条件等于/不等于支持自定义匹配值-----------
@Schema(description = "增量标记采集条件比较值(EQ_EMPTY/NE_EMPTY 用,留空表示空字符串)INCR模式用")
private String flagMatchValue;
//update-end---author:GHT ---date:20260617 for【MES上辅机】增量采集条件等于/不等于支持自定义匹配值-----------
@Schema(description = "增量标记采集完成后回写值(默认1)INCR模式用")
private String flagWriteValue;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "最近一次采集时间")
private Date lastSyncTime;
@Schema(description = "最近一次采集结果")
private String lastSyncResult;
@Schema(description = "备注")
private String remark;
@Schema(description = "租户ID")
private Integer tenantId;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
@TableField(exist = false)
@Schema(description = "字段映射明细(主子保存/详情用)")
private List<MesXslMcsSyncField> fieldList;
@TableField(exist = false)
@Schema(description = "采集任务是否运行中(运行态由调度器实时给出)")
private Boolean running;
}

View File

@@ -0,0 +1,75 @@
package org.jeecg.modules.xslmes.mcs.entity;
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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* MES上辅机 采集字段映射(中间库源字段 → MES目标字段
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
@Data
@TableName("mes_xsl_mcs_sync_field")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description = "MES上辅机采集字段映射")
public class MesXslMcsSyncField implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
@Schema(description = "采集配置ID")
private String configId;
@Schema(description = "中间库源字段名")
private String sourceField;
@Schema(description = "源字段注释")
private String sourceFieldComment;
@Schema(description = "源字段类型")
private String sourceFieldType;
@Schema(description = "MES目标字段名(接收字段)")
private String targetField;
@Schema(description = "MES目标字段注释")
private String targetFieldComment;
@Schema(description = "是否匹配键(0否,1是)")
private String matchKey;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "租户ID")
private Integer tenantId;
private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
private Integer delFlag;
}

View File

@@ -0,0 +1,58 @@
package org.jeecg.modules.xslmes.mcs.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 中间库(SQL Server) / MES(MySQL) 表与字段元数据查询。
* <p>源表元数据走 sqlserver_mcs 数据源,目标表元数据走默认 MES 库。</p>
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
public interface McsMetaMapper {
/**
* 中间库表清单(含表注释 MS_Description
*/
@DS("sqlserver_mcs")
@Select("SELECT t.name AS tableName, CAST(ep.value AS NVARCHAR(200)) AS tableComment "
+ "FROM sys.tables t "
+ "LEFT JOIN sys.extended_properties ep ON ep.major_id = t.object_id AND ep.minor_id = 0 AND ep.name = 'MS_Description' "
+ "ORDER BY t.name")
List<Map<String, Object>> listSourceTables();
/**
* 中间库表字段清单(含字段注释、类型)
*/
@DS("sqlserver_mcs")
@Select("SELECT c.name AS columnName, ty.name AS dataType, CAST(ep.value AS NVARCHAR(200)) AS columnComment "
+ "FROM sys.columns c "
+ "JOIN sys.types ty ON c.user_type_id = ty.user_type_id "
+ "LEFT JOIN sys.extended_properties ep ON ep.major_id = c.object_id AND ep.minor_id = c.column_id AND ep.name = 'MS_Description' "
+ "WHERE c.object_id = OBJECT_ID(#{table}) "
+ "ORDER BY c.column_id")
List<Map<String, Object>> listSourceColumns(@Param("table") String table);
/**
* MES 业务表清单(仅 mes_xsl_ 前缀)
*/
@Select("SELECT table_name AS tableName, table_comment AS tableComment "
+ "FROM information_schema.tables "
+ "WHERE table_schema = (SELECT DATABASE()) AND table_name LIKE 'mes\\_xsl\\_%' "
+ "ORDER BY table_name")
List<Map<String, Object>> listTargetTables();
/**
* MES 表字段清单(含字段注释、类型)
*/
@Select("SELECT column_name AS columnName, data_type AS dataType, column_comment AS columnComment "
+ "FROM information_schema.columns "
+ "WHERE table_schema = (SELECT DATABASE()) AND table_name = #{table} "
+ "ORDER BY ordinal_position")
List<Map<String, Object>> listTargetColumns(@Param("table") String table);
}

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.mcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
/**
* MES上辅机 SQL Server 中间库配置 Mapper
*/
public interface MesXslMcsDbConfigMapper extends BaseMapper<MesXslMcsDbConfig> {
}

View File

@@ -0,0 +1,13 @@
package org.jeecg.modules.xslmes.mcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
/**
* MES上辅机 中间表采集配置 Mapper
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】密炼动作秒级采集
*/
public interface MesXslMcsSyncConfigMapper extends BaseMapper<MesXslMcsSyncConfig> {
}

View File

@@ -0,0 +1,13 @@
package org.jeecg.modules.xslmes.mcs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncField;
/**
* MES上辅机 采集字段映射 Mapper
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
public interface MesXslMcsSyncFieldMapper extends BaseMapper<MesXslMcsSyncField> {
}

View File

@@ -0,0 +1,19 @@
package org.jeecg.modules.xslmes.mcs.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
import java.util.Map;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipAlarmRecordVO;
/**
* 设备报警记录只读SQL Server MCSToMES_MixAlarm
*/
public interface IMesXslEquipAlarmRecordService {
IPage<MesXslEquipAlarmRecordVO> queryPage(McsToMesMixAlarm query, Map<String, String[]> paramMap, int pageNo, int pageSize);
MesXslEquipAlarmRecordVO queryById(String id);
List<MesXslEquipAlarmRecordVO> listForExport(McsToMesMixAlarm query, Map<String, String[]> paramMap);
}

View File

@@ -0,0 +1,19 @@
package org.jeecg.modules.xslmes.mcs.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
import java.util.Map;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipDowntimeRecordVO;
/**
* 设备停机记录只读SQL Server MCSToMES_MixAlarm
*/
public interface IMesXslEquipDowntimeRecordService {
IPage<MesXslEquipDowntimeRecordVO> queryPage(McsToMesMixAlarm query, Map<String, String[]> paramMap, int pageNo, int pageSize);
MesXslEquipDowntimeRecordVO queryById(String id);
List<MesXslEquipDowntimeRecordVO> listForExport(McsToMesMixAlarm query, Map<String, String[]> paramMap);
}

View File

@@ -0,0 +1,36 @@
package org.jeecg.modules.xslmes.mcs.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
/**
* MES上辅机 SQL Server 中间库配置 Service
*/
public interface IMesXslMcsDbConfigService extends IService<MesXslMcsDbConfig> {
/**
* 获取当前租户配置(无则返回 null
*/
MesXslMcsDbConfig getCurrentConfig(Integer tenantId);
/**
* 保存或更新配置并刷新动态数据源
*/
Result<String> saveOrUpdateConfig(MesXslMcsDbConfig config);
/**
* 测试数据库连接
*/
Result<String> testConnection(Integer tenantId);
/**
* 删除配置并移除动态数据源
*/
Result<String> deleteConfig(String id);
/**
* 启动时加载中间库配置(不限定租户,取最近更新的一条)
*/
MesXslMcsDbConfig loadStartupConfig();
}

View File

@@ -0,0 +1,40 @@
package org.jeecg.modules.xslmes.mcs.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
/**
* MES上辅机 中间表采集配置 Service配置驱动表/字段绑定 + 采集操作)
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
public interface IMesXslMcsSyncConfigService extends IService<MesXslMcsSyncConfig> {
/**
* 获取配置详情(含字段映射明细 fieldList
*/
MesXslMcsSyncConfig getDetail(String id);
/**
* 按业务类型获取最近配置密炼动作页用bizType=MIX_ACT
*/
MesXslMcsSyncConfig getByBizType(String bizType);
/**
* 保存配置(头 + 字段映射明细,主子整存)
*/
Result<String> saveConfig(MesXslMcsSyncConfig config);
/**
* 删除配置及其字段映射,并停止采集
*/
Result<String> deleteConfig(String id);
/**
* 采集操作:维护是否采集、采集间隔、采集模式(全量/时间/增量)及其参数。
* status='1' 启动并按间隔重排,'0' 停止。
*/
Result<String> saveCollect(MesXslMcsSyncConfig body);
}

View File

@@ -0,0 +1,59 @@
package org.jeecg.modules.xslmes.mcs.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.service.IMcsToMesMixAlarmService;
import org.jeecg.modules.xslmes.mcs.service.IMesXslEquipAlarmRecordService;
import org.jeecg.modules.xslmes.mcs.util.McsQueryHelper;
import org.jeecg.modules.xslmes.mcs.util.MesXslMcsMixAlarmConvertUtil;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipAlarmRecordVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 设备报警记录
*/
@Service
public class MesXslEquipAlarmRecordServiceImpl implements IMesXslEquipAlarmRecordService {
@Autowired
private IMcsToMesMixAlarmService mcsToMesMixAlarmService;
@Autowired
private MesXslMcsMixAlarmReadMarker mesXslMcsMixAlarmReadMarker;
@Override
public IPage<MesXslEquipAlarmRecordVO> queryPage(McsToMesMixAlarm query, Map<String, String[]> paramMap, int pageNo, int pageSize) {
QueryWrapper<McsToMesMixAlarm> queryWrapper = McsQueryHelper.buildWrapper(query, paramMap);
Page<McsToMesMixAlarm> page = new Page<>(pageNo, pageSize);
IPage<McsToMesMixAlarm> rawPage = mcsToMesMixAlarmService.page(page, queryWrapper);
//update-begin---author:jiangxh ---date:20250604 for【MES】设备报警记录列表查询后回写 MCS 读取时间与标识-----------
mesXslMcsMixAlarmReadMarker.markAsRead(rawPage.getRecords());
//update-end---author:jiangxh ---date:20250604 for【MES】设备报警记录列表查询后回写 MCS 读取时间与标识-----------
Page<MesXslEquipAlarmRecordVO> voPage = new Page<>(pageNo, pageSize, rawPage.getTotal());
voPage.setRecords(rawPage.getRecords().stream().map(MesXslMcsMixAlarmConvertUtil::toAlarmVo).collect(Collectors.toList()));
return voPage;
}
@Override
public MesXslEquipAlarmRecordVO queryById(String id) {
McsToMesMixAlarm row = mcsToMesMixAlarmService.getById(id);
if (row == null) {
return null;
}
return MesXslMcsMixAlarmConvertUtil.toAlarmVo(row);
}
@Override
public List<MesXslEquipAlarmRecordVO> listForExport(McsToMesMixAlarm query, Map<String, String[]> paramMap) {
QueryWrapper<McsToMesMixAlarm> queryWrapper = McsQueryHelper.buildWrapper(query, paramMap);
return mcsToMesMixAlarmService.list(queryWrapper).stream()
.map(MesXslMcsMixAlarmConvertUtil::toAlarmVo)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,59 @@
package org.jeecg.modules.xslmes.mcs.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.service.IMcsToMesMixAlarmService;
import org.jeecg.modules.xslmes.mcs.service.IMesXslEquipDowntimeRecordService;
import org.jeecg.modules.xslmes.mcs.util.McsQueryHelper;
import org.jeecg.modules.xslmes.mcs.util.MesXslMcsMixAlarmConvertUtil;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipDowntimeRecordVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 设备停机记录
*/
@Service
public class MesXslEquipDowntimeRecordServiceImpl implements IMesXslEquipDowntimeRecordService {
@Autowired
private IMcsToMesMixAlarmService mcsToMesMixAlarmService;
@Autowired
private MesXslMcsMixAlarmReadMarker mesXslMcsMixAlarmReadMarker;
@Override
public IPage<MesXslEquipDowntimeRecordVO> queryPage(McsToMesMixAlarm query, Map<String, String[]> paramMap, int pageNo, int pageSize) {
QueryWrapper<McsToMesMixAlarm> queryWrapper = McsQueryHelper.buildWrapper(query, paramMap);
Page<McsToMesMixAlarm> page = new Page<>(pageNo, pageSize);
IPage<McsToMesMixAlarm> rawPage = mcsToMesMixAlarmService.page(page, queryWrapper);
//update-begin---author:jiangxh ---date:20250604 for【MES】设备停机记录列表查询后回写 MCS 读取时间与标识-----------
mesXslMcsMixAlarmReadMarker.markAsRead(rawPage.getRecords());
//update-end---author:jiangxh ---date:20250604 for【MES】设备停机记录列表查询后回写 MCS 读取时间与标识-----------
Page<MesXslEquipDowntimeRecordVO> voPage = new Page<>(pageNo, pageSize, rawPage.getTotal());
voPage.setRecords(rawPage.getRecords().stream().map(MesXslMcsMixAlarmConvertUtil::toDowntimeVo).collect(Collectors.toList()));
return voPage;
}
@Override
public MesXslEquipDowntimeRecordVO queryById(String id) {
McsToMesMixAlarm row = mcsToMesMixAlarmService.getById(id);
if (row == null) {
return null;
}
return MesXslMcsMixAlarmConvertUtil.toDowntimeVo(row);
}
@Override
public List<MesXslEquipDowntimeRecordVO> listForExport(McsToMesMixAlarm query, Map<String, String[]> paramMap) {
QueryWrapper<McsToMesMixAlarm> queryWrapper = McsQueryHelper.buildWrapper(query, paramMap);
return mcsToMesMixAlarmService.list(queryWrapper).stream()
.map(MesXslMcsMixAlarmConvertUtil::toDowntimeVo)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,196 @@
package org.jeecg.modules.xslmes.mcs.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsDbConfig;
import org.jeecg.modules.xslmes.mcs.mapper.MesXslMcsDbConfigMapper;
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsDbConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.Date;
/**
* MES上辅机 SQL Server 中间库配置 Service 实现
*/
@Slf4j
@Service
public class MesXslMcsDbConfigServiceImpl extends ServiceImpl<MesXslMcsDbConfigMapper, MesXslMcsDbConfig>
implements IMesXslMcsDbConfigService {
@Autowired
private McsDataSourceManager mcsDataSourceManager;
@Override
public MesXslMcsDbConfig getCurrentConfig(Integer tenantId) {
int tid = tenantId != null ? tenantId : 0;
LambdaQueryWrapper<MesXslMcsDbConfig> qw = new LambdaQueryWrapper<>();
qw.eq(MesXslMcsDbConfig::getTenantId, tid);
qw.orderByDesc(MesXslMcsDbConfig::getUpdateTime);
qw.last("LIMIT 1");
MesXslMcsDbConfig config = getOne(qw, false);
if (config != null) {
config.setDbPassword(null);
}
return config;
}
//update-begin---author:GHT ---date:20260616 for【MES上辅机】启动时加载中间库配置不限定租户-----------
@Override
public MesXslMcsDbConfig loadStartupConfig() {
LambdaQueryWrapper<MesXslMcsDbConfig> qw = new LambdaQueryWrapper<>();
qw.orderByDesc(MesXslMcsDbConfig::getUpdateTime);
qw.last("LIMIT 1");
return getOne(qw, false);
}
//update-end---author:GHT ---date:20260616 for【MES上辅机】启动时加载中间库配置不限定租户-----------
//update-begin---author:GHT ---date:20260616 for【MES上辅机】SQL Server中间库可视化配置-----------
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> saveOrUpdateConfig(MesXslMcsDbConfig config) {
if (config == null) {
return Result.error("配置不能为空");
}
if (StringUtils.isBlank(config.getServerHost())) {
return Result.error("服务器地址不能为空");
}
if (StringUtils.isBlank(config.getDbUsername())) {
return Result.error("用户名不能为空");
}
if (config.getTenantId() == null) {
config.setTenantId(0);
}
if (config.getServerPort() == null) {
config.setServerPort(1433);
}
if (StringUtils.isBlank(config.getDbName())) {
config.setDbName("MES_ShareDB");
}
if (config.getReadEnabled() == null) {
config.setReadEnabled(1);
}
if (config.getWriteEnabled() == null) {
config.setWriteEnabled(1);
}
if (config.getStatus() == null) {
config.setStatus(0);
}
if (config.getLoginTimeout() == null) {
config.setLoginTimeout(120);
}
if (config.getConnectTimeout() == null) {
config.setConnectTimeout(120000);
}
String plainPassword = resolvePlainPassword(config);
if (StringUtils.isBlank(plainPassword)) {
return Result.error("密码不能为空");
}
String err = mcsDataSourceManager.testConnection(config, plainPassword);
if (err != null && Integer.valueOf(1).equals(config.getStatus())) {
return Result.error("连接测试失败:" + err);
}
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String username = user != null ? user.getUsername() : "system";
Date now = new Date();
if (StringUtils.isNotBlank(config.getId())) {
MesXslMcsDbConfig old = getById(config.getId());
if (old == null) {
return Result.error("配置不存在");
}
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
config.setUpdateBy(username);
config.setUpdateTime(now);
updateById(config);
} else {
config.setDbPassword(mcsDataSourceManager.encryptPassword(plainPassword));
config.setCreateBy(username);
config.setCreateTime(now);
config.setUpdateBy(username);
config.setUpdateTime(now);
save(config);
}
MesXslMcsDbConfig applied = getById(config.getId());
scheduleApplyAfterCommit(applied);
return Result.OK("保存成功,中间库连接已热刷新,无需重启后端");
}
/**
* 事务提交后再刷新数据源,避免库未落盘就切换连接
*/
private void scheduleApplyAfterCommit(MesXslMcsDbConfig applied) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
mcsDataSourceManager.applyConfig(applied);
}
});
} else {
mcsDataSourceManager.applyConfig(applied);
}
}
@Override
public Result<String> testConnection(Integer tenantId) {
MesXslMcsDbConfig config = getOneByTenant(tenantId);
if (config == null) {
return Result.error("尚未配置中间库连接");
}
String plainPassword = mcsDataSourceManager.decryptPassword(config.getDbPassword());
String err = mcsDataSourceManager.testConnection(config, plainPassword);
if (err == null) {
return Result.OK("连接成功");
}
return Result.error("连接失败:" + err);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> deleteConfig(String id) {
MesXslMcsDbConfig config = getById(id);
if (config == null) {
return Result.error("配置不存在");
}
removeById(id);
mcsDataSourceManager.applyConfig(null);
return Result.OK("已删除配置,中间库连接已断开(无需重启后端)");
}
private MesXslMcsDbConfig getOneByTenant(Integer tenantId) {
int tid = tenantId != null ? tenantId : 0;
LambdaQueryWrapper<MesXslMcsDbConfig> qw = new LambdaQueryWrapper<>();
qw.eq(MesXslMcsDbConfig::getTenantId, tid);
qw.orderByDesc(MesXslMcsDbConfig::getUpdateTime);
qw.last("LIMIT 1");
return getOne(qw, false);
}
private String resolvePlainPassword(MesXslMcsDbConfig config) {
if (StringUtils.isNotBlank(config.getDbPassword())) {
return config.getDbPassword();
}
if (StringUtils.isNotBlank(config.getId())) {
MesXslMcsDbConfig old = getById(config.getId());
if (old != null) {
return mcsDataSourceManager.decryptPassword(old.getDbPassword());
}
}
return null;
}
//update-end---author:GHT ---date:20260616 for【MES上辅机】SQL Server中间库可视化配置-----------
}

View File

@@ -0,0 +1,36 @@
package org.jeecg.modules.xslmes.mcs.service.impl;
import java.util.Date;
import java.util.List;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.service.IMcsToMesMixAlarmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 列表查询后将 MCSToMES_MixAlarm 标记为已读ReadTime、MES_Flag=1
*/
@Component
public class MesXslMcsMixAlarmReadMarker {
@Autowired
private IMcsToMesMixAlarmService mcsToMesMixAlarmService;
public void markAsRead(List<McsToMesMixAlarm> records) {
if (records == null || records.isEmpty()) {
return;
}
Date now = new Date();
for (McsToMesMixAlarm row : records) {
if (oConvertUtils.isEmpty(row.getGuid())) {
continue;
}
McsToMesMixAlarm upd = new McsToMesMixAlarm();
upd.setGuid(row.getGuid());
upd.setReadTime(now);
upd.setMesFlag(1);
mcsToMesMixAlarmService.updateById(upd);
}
}
}

View File

@@ -0,0 +1,223 @@
package org.jeecg.modules.xslmes.mcs.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncField;
import org.jeecg.modules.xslmes.mcs.mapper.MesXslMcsSyncConfigMapper;
import org.jeecg.modules.xslmes.mcs.mapper.MesXslMcsSyncFieldMapper;
import org.jeecg.modules.xslmes.mcs.service.IMesXslMcsSyncConfigService;
import org.jeecg.modules.xslmes.mcs.sync.McsSyncScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* MES上辅机 中间表采集配置 Service 实现
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
@Slf4j
@Service
public class MesXslMcsSyncConfigServiceImpl extends ServiceImpl<MesXslMcsSyncConfigMapper, MesXslMcsSyncConfig>
implements IMesXslMcsSyncConfigService {
@Autowired
private MesXslMcsSyncFieldMapper syncFieldMapper;
@Autowired
private McsSyncScheduler syncScheduler;
@Override
public MesXslMcsSyncConfig getDetail(String id) {
MesXslMcsSyncConfig cfg = getById(id);
if (cfg == null) {
return null;
}
cfg.setFieldList(listFields(id));
return cfg;
}
@Override
public MesXslMcsSyncConfig getByBizType(String bizType) {
return getOne(new LambdaQueryWrapper<MesXslMcsSyncConfig>()
.eq(MesXslMcsSyncConfig::getBizType, bizType)
.eq(MesXslMcsSyncConfig::getDelFlag, 0)
.orderByDesc(MesXslMcsSyncConfig::getUpdateTime)
.last("LIMIT 1"), false);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> saveConfig(MesXslMcsSyncConfig config) {
if (config == null) {
return Result.error("配置不能为空");
}
if (StringUtils.isBlank(config.getSourceTable())) {
return Result.error("请选择中间库源表");
}
if (StringUtils.isBlank(config.getTargetTable())) {
return Result.error("请选择MES目标表");
}
List<MesXslMcsSyncField> fields = config.getFieldList() != null ? config.getFieldList() : new ArrayList<>();
// 至少一个有效映射
boolean hasValid = fields.stream().anyMatch(f -> StringUtils.isNotBlank(f.getSourceField())
&& StringUtils.isNotBlank(f.getTargetField()));
if (!hasValid) {
return Result.error("请至少配置一个字段映射(源字段+接收字段)");
}
String username = currentUsername();
Date now = new Date();
if (config.getIntervalSeconds() == null || config.getIntervalSeconds() < 1) {
config.setIntervalSeconds(1);
}
if (config.getTenantId() == null) {
config.setTenantId(0);
}
config.setDelFlag(0);
config.setUpdateBy(username);
config.setUpdateTime(now);
boolean isUpdate = StringUtils.isNotBlank(config.getId());
if (isUpdate) {
MesXslMcsSyncConfig old = getById(config.getId());
if (old == null) {
return Result.error("配置不存在");
}
// 状态由采集操作维护,保存配置不改变运行状态
config.setStatus(old.getStatus());
updateById(config);
} else {
if (StringUtils.isBlank(config.getStatus())) {
config.setStatus("0");
}
config.setCreateBy(username);
config.setCreateTime(now);
save(config);
}
// 整存字段映射:先物理删除旧映射再插入
syncFieldMapper.delete(new LambdaQueryWrapper<MesXslMcsSyncField>()
.eq(MesXslMcsSyncField::getConfigId, config.getId()));
int sort = 0;
for (MesXslMcsSyncField f : fields) {
if (StringUtils.isBlank(f.getSourceField())) {
continue;
}
f.setId(null);
f.setConfigId(config.getId());
f.setSortNo(sort++);
f.setTenantId(config.getTenantId());
f.setDelFlag(0);
f.setCreateBy(username);
f.setCreateTime(now);
f.setUpdateBy(username);
f.setUpdateTime(now);
syncFieldMapper.insert(f);
}
// 运行中则按新配置重排(间隔/映射即时生效)
if ("1".equals(config.getStatus())) {
syncScheduler.scheduleTask(getById(config.getId()));
}
return Result.OK(isUpdate ? "保存成功" : "新增成功");
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> deleteConfig(String id) {
MesXslMcsSyncConfig cfg = getById(id);
if (cfg == null) {
return Result.error("配置不存在");
}
syncScheduler.cancelTask(id);
syncFieldMapper.delete(new LambdaQueryWrapper<MesXslMcsSyncField>()
.eq(MesXslMcsSyncField::getConfigId, id));
removeById(id);
return Result.OK("删除成功");
}
@Override
public Result<String> saveCollect(MesXslMcsSyncConfig body) {
if (body == null || StringUtils.isBlank(body.getId())) {
return Result.error("缺少配置ID");
}
MesXslMcsSyncConfig cfg = getById(body.getId());
if (cfg == null) {
return Result.error("配置不存在");
}
if (body.getIntervalSeconds() != null) {
if (body.getIntervalSeconds() < 1) {
return Result.error("采集间隔不能小于1秒");
}
cfg.setIntervalSeconds(body.getIntervalSeconds());
}
// 采集模式及参数
String mode = StringUtils.isBlank(body.getSyncMode()) ? "FULL" : body.getSyncMode().trim().toUpperCase();
cfg.setSyncMode(mode);
cfg.setIncrColumn(StringUtils.trimToNull(body.getIncrColumn()));
cfg.setTimeWindow(StringUtils.isBlank(body.getTimeWindow()) ? "TODAY" : body.getTimeWindow());
if (body.getBatchLimit() != null && body.getBatchLimit() > 0) {
cfg.setBatchLimit(body.getBatchLimit());
}
// INCR(标记回写):采集条件 + 回写值(可视化配置,回写值默认"1"
cfg.setFlagCondition(StringUtils.isBlank(body.getFlagCondition()) ? "IS_NULL" : body.getFlagCondition().trim().toUpperCase());
//update-begin---author:GHT ---date:20260617 for【MES上辅机】增量采集条件等于/不等于支持自定义匹配值-----------
cfg.setFlagMatchValue(body.getFlagMatchValue());
//update-end---author:GHT ---date:20260617 for【MES上辅机】增量采集条件等于/不等于支持自定义匹配值-----------
cfg.setFlagWriteValue(StringUtils.isBlank(body.getFlagWriteValue()) ? "1" : body.getFlagWriteValue());
if (("TIME".equals(mode) || "INCR".equals(mode)) && StringUtils.isBlank(cfg.getIncrColumn())) {
return Result.error("时间匹配/增量匹配需选择" + ("TIME".equals(mode) ? "时间列" : "标记列"));
}
boolean on = "1".equals(body.getStatus());
cfg.setStatus(on ? "1" : "0");
cfg.setUpdateBy(currentUsername());
cfg.setUpdateTime(new Date());
updateById(cfg);
if (on) {
syncScheduler.scheduleTask(cfg);
return Result.OK("已启动采集(" + modeText(mode) + "),间隔 " + cfg.getIntervalSeconds() + "");
}
syncScheduler.cancelTask(cfg.getId());
return Result.OK("已停止采集");
}
private String modeText(String mode) {
switch (mode) {
case "TIME":
return "时间匹配";
case "INCR":
return "增量匹配";
default:
return "全量匹配";
}
}
private List<MesXslMcsSyncField> listFields(String configId) {
return syncFieldMapper.selectList(new LambdaQueryWrapper<MesXslMcsSyncField>()
.eq(MesXslMcsSyncField::getConfigId, configId)
.eq(MesXslMcsSyncField::getDelFlag, 0)
.orderByAsc(MesXslMcsSyncField::getSortNo));
}
private String currentUsername() {
try {
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
return user != null ? user.getUsername() : "system";
} catch (Exception e) {
return "system";
}
}
}

View File

@@ -0,0 +1,524 @@
package org.jeecg.modules.xslmes.mcs.sync;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncField;
import org.jeecg.modules.xslmes.mcs.mapper.McsMetaMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 通用中间表采集引擎(配置驱动,纯字段拷贝)。
* <p>支持三种采集模式,应对中间库不同规模的表:</p>
* <ul>
* <li><b>FULL 全量匹配</b>:全表读源+全表读目标→按匹配键 Upsert仅写新增/变化行。适合小状态表、以更新为主。</li>
* <li><b>TIME 时间匹配</b>:按时间列只取窗口内数据(当天/最近七天)→按匹配键 Upsert目标侧按窗口匹配键定向读取。避免全表扫描。</li>
* <li><b>INCR 增量匹配(标记位回写)</b>源表选一「同步标记列」仅采集该列为空NULL/''的行TOP N 限流),
* 按匹配键 Upsert 到 MES 后,回写源表该列为 {@code '1'},下轮不再重复采集。适合带 GUID 主键、无可靠递增列的流水表。</li>
* </ul>
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
@Slf4j
@Component
public class GenericMcsSyncEngine {
public static final String MODE_FULL = "FULL";
public static final String MODE_TIME = "TIME";
public static final String MODE_INCR = "INCR";
/** 合法标识符(表名/列名),防止 SQL 注入 */
private static final Pattern IDENT = Pattern.compile("^[A-Za-z0-9_]+$");
/** 批量写入分批大小 */
private static final int BATCH_SIZE = 500;
/** IN 查询分块大小 */
private static final int IN_CHUNK = 1000;
/** INCR 默认每轮行数 */
private static final int DEFAULT_BATCH_LIMIT = 2000;
/** INCR 标记位回写后的已同步标识值 */
private static final String FLAG_SYNCED = "1";
@Autowired
private DataSource dataSource;
@Autowired
private McsMetaMapper metaMapper;
@Autowired
private org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager mcsDataSourceManager;
//update-begin---author:GHT ---date:20260617 for【MES上辅机】采集模式 全量/时间/增量-----------
public String sync(MesXslMcsSyncConfig cfg, List<MesXslMcsSyncField> fields) {
String sourceTable = trim(cfg.getSourceTable());
String targetTable = trim(cfg.getTargetTable());
if (StringUtils.isBlank(sourceTable) || StringUtils.isBlank(targetTable)) {
return "未配置源表或目标表,跳过";
}
validateIdent(sourceTable);
validateIdent(targetTable);
List<MesXslMcsSyncField> maps = fields == null ? List.of() : fields.stream()
.filter(f -> StringUtils.isNotBlank(f.getSourceField()) && StringUtils.isNotBlank(f.getTargetField()))
.collect(Collectors.toList());
if (maps.isEmpty()) {
return "无有效字段映射,跳过";
}
for (MesXslMcsSyncField f : maps) {
validateIdent(f.getSourceField());
validateIdent(f.getTargetField());
}
String mode = StringUtils.isBlank(cfg.getSyncMode()) ? MODE_FULL : cfg.getSyncMode().trim().toUpperCase();
JdbcTemplate sourceJt = new JdbcTemplate(getSourceDataSource());
JdbcTemplate targetJt = new JdbcTemplate(dataSource);
// 目标表标准字段探测 + 自动填充列
Set<String> targetCols = metaMapper.listTargetColumns(targetTable).stream()
.map(m -> String.valueOf(m.get("columnName")).toLowerCase())
.collect(Collectors.toSet());
boolean hasDel = targetCols.contains("del_flag");
List<MesXslMcsSyncField> keyMaps = maps.stream().filter(f -> "1".equals(f.getMatchKey())).collect(Collectors.toList());
Set<String> keyTargetsLower = keyMaps.stream().map(k -> k.getTargetField().toLowerCase()).collect(Collectors.toSet());
List<MesXslMcsSyncField> nonKeyMaps = maps.stream()
.filter(f -> !keyTargetsLower.contains(f.getTargetField().toLowerCase()))
.collect(Collectors.toList());
int tenantId = cfg.getTenantId() != null ? cfg.getTenantId() : 0;
Timestamp now = new Timestamp(System.currentTimeMillis());
AutoCols auto = buildAutoCols(targetCols, maps, tenantId, now);
// 1. 按模式读源数据
LinkedHashSet<String> srcCols = maps.stream().map(MesXslMcsSyncField::getSourceField)
.collect(Collectors.toCollection(LinkedHashSet::new));
List<Map<String, Object>> rows;
//update-begin---author:GHT ---date:20260617 for【MES上辅机】增量匹配改为标记位回写-----------
// INCR(标记回写)模式:仅采集「标记列」为空的行,采完回写"1",下轮不再重复采集
String flagCol = null;
boolean flagMode = MODE_INCR.equals(mode);
if (flagMode) {
flagCol = requireIncrColumn(cfg);
if (keyMaps.isEmpty()) {
return "增量(标记)采集需在字段映射中勾选至少一个匹配键作为回写主键(如 GUID";
}
if (!mcsDataSourceManager.isWriteEnabled()) {
return "增量(标记)采集需开启中间库写入开关以回写同步标记,请在「中间库连接配置」开启写入";
}
srcCols.add(flagCol);
int limit = cfg.getBatchLimit() != null && cfg.getBatchLimit() > 0 ? cfg.getBatchLimit() : DEFAULT_BATCH_LIMIT;
String predicate = flagPredicate(flagCol, cfg.getFlagCondition(), cfg.getFlagMatchValue());
String sql = "SELECT TOP " + limit + " " + colList(srcCols) + " FROM [" + sourceTable + "]"
+ " WHERE (" + predicate + ")";
rows = sourceJt.queryForList(sql);
if (rows.isEmpty()) {
return "增量采集:无待采集数据";
}
} else if (MODE_TIME.equals(mode)) {
String incrCol = requireIncrColumn(cfg);
Timestamp[] window = timeWindow(cfg.getTimeWindow(), now);
StringBuilder sql = new StringBuilder("SELECT ").append(colList(srcCols))
.append(" FROM [").append(sourceTable).append("] WHERE [").append(incrCol).append("] >= ?");
List<Object> args = new ArrayList<>();
args.add(window[0]);
if (window[1] != null) {
sql.append(" AND [").append(incrCol).append("] < ?");
args.add(window[1]);
}
rows = sourceJt.queryForList(sql.toString(), args.toArray());
} else {
// FULL
rows = sourceJt.queryForList("SELECT " + colList(srcCols) + " FROM [" + sourceTable + "]");
}
if (rows.isEmpty()) {
return ("TIME".equals(mode) ? "时间匹配" : "全量匹配") + ":窗口/源表无数据,未更新";
}
// 无匹配键 → 整批追加
if (keyMaps.isEmpty()) {
int ins = appendInsert(targetJt, targetTable, maps, auto, rows);
return String.format("采集完成(无匹配键,追加):新增%d源%d条", ins, rows.size());
}
// 2. 加载现有目标数据FULL 全量TIME/INCR 仅按本批匹配键定向读取)
LinkedHashSet<String> existCols = new LinkedHashSet<>();
keyMaps.forEach(k -> existCols.add(k.getTargetField()));
maps.forEach(m -> existCols.add(m.getTargetField()));
Map<String, Map<String, Object>> existingByKey = (MODE_TIME.equals(mode) || flagMode)
? loadExistingByKeys(targetJt, targetTable, existCols, keyMaps, hasDel, rows)
: loadExistingAll(targetJt, targetTable, existCols, keyMaps, hasDel);
// 3. 比对 → 批量 Upsert
List<String> updateSetCols = nonKeyMaps.stream().map(MesXslMcsSyncField::getTargetField).collect(Collectors.toList());
boolean updTime = targetCols.contains("update_time") && !mappedContains(maps, "update_time");
boolean updBy = targetCols.contains("update_by") && !mappedContains(maps, "update_by");
String updateSql = buildUpdateSql(targetTable, updateSetCols, keyMaps, updTime, updBy, hasDel);
String insertSql = buildInsertSql(targetTable, maps, auto);
List<Object[]> insertArgs = new ArrayList<>();
List<Object[]> updateArgs = new ArrayList<>();
Set<String> handled = new HashSet<>();
int unchanged = 0;
for (Map<String, Object> row : rows) {
Map<String, Object> rci = ci(row);
String key = buildKeyFromSource(keyMaps, rci);
if (!handled.add(key)) {
continue;
}
Map<String, Object> existing = existingByKey.get(key);
if (existing == null) {
insertArgs.add(buildInsertArgs(maps, rci, auto));
} else if (updateSetCols.isEmpty()) {
unchanged++;
} else if (isChanged(nonKeyMaps, rci, existing)) {
updateArgs.add(buildUpdateArgs(nonKeyMaps, keyMaps, rci, updTime, updBy, now));
} else {
unchanged++;
}
}
int ins = batch(targetJt, insertSql, insertArgs);
int upd = updateSetCols.isEmpty() ? 0 : batch(targetJt, updateSql, updateArgs);
// INCR(标记回写):对本批所有源行回写标记值,下轮不再采集
if (flagMode) {
String writeValue = StringUtils.isBlank(cfg.getFlagWriteValue()) ? FLAG_SYNCED : cfg.getFlagWriteValue();
int marked = writeBackFlag(sourceJt, sourceTable, flagCol, cfg.getFlagCondition(), cfg.getFlagMatchValue(), writeValue, keyMaps, rows);
return String.format("增量采集:新增%d更新%d未变%d回写标记%d源%d条",
ins, upd, unchanged, marked, rows.size());
}
return String.format("%s新增%d更新%d未变%d源%d条",
"TIME".equals(mode) ? "时间匹配" : "全量匹配", ins, upd, unchanged, rows.size());
//update-end---author:GHT ---date:20260617 for【MES上辅机】增量匹配改为标记位回写-----------
}
//update-begin---author:GHT ---date:20260617 for【MES上辅机】增量匹配改为标记位回写-----------
/**
* INCR 标记采集条件根据配置构造源表标记列的判定谓词SELECT 取数 + 回写守卫共用)。
* <ul>
* <li>{@code IS_NULL} 为空:{@code [col] IS NULL}</li>
* <li>{@code EQ_EMPTY} 等于:{@code [col] = '<匹配值>'}(匹配值留空时退化为等于空串)</li>
* <li>{@code NE_EMPTY} 不等于:{@code [col] <> '<匹配值>'}(匹配值留空时退化为不等于空串)</li>
* </ul>
* @param matchValue EQ_EMPTY/NE_EMPTY 的比较值,由用户填写,留空表示空字符串
*/
private String flagPredicate(String flagCol, String condition, String matchValue) {
String c = StringUtils.isBlank(condition) ? "IS_NULL" : condition.trim().toUpperCase();
switch (c) {
case "EQ_EMPTY":
return "[" + flagCol + "] = '" + sqlLiteral(matchValue) + "'";
case "NE_EMPTY":
return "[" + flagCol + "] <> '" + sqlLiteral(matchValue) + "'";
case "IS_NULL":
default:
return "[" + flagCol + "] IS NULL";
}
}
/** 将用户填写的匹配值转义为 SQL 字符串字面量内容(单引号翻倍),防止注入。 */
private String sqlLiteral(String value) {
return value == null ? "" : value.replace("'", "''");
}
/**
* INCR 标记回写:把本批读到的源行该标记列回写为配置的回写值。
* <p>仅按匹配键精确定位本批读到的行(而非整列条件批量更新),
* 避免误标在本轮 SELECT 之后才进入中间库、尚未采集的新数据;
* 并以采集条件谓词做守卫,避开本轮已被其他进程改动的行。</p>
*/
private int writeBackFlag(JdbcTemplate sourceJt, String sourceTable, String flagCol, String condition,
String matchValue, String writeValue, List<MesXslMcsSyncField> keyMaps, List<Map<String, Object>> rows) {
validateIdent(flagCol);
StringBuilder sql = new StringBuilder("UPDATE [").append(sourceTable).append("] SET [")
.append(flagCol).append("] = ? WHERE ");
sql.append(keyMaps.stream().map(k -> "[" + k.getSourceField() + "] = ?").collect(Collectors.joining(" AND ")));
sql.append(" AND (").append(flagPredicate(flagCol, condition, matchValue)).append(")");
List<Object[]> argsList = new ArrayList<>();
Set<String> handled = new HashSet<>();
for (Map<String, Object> row : rows) {
Map<String, Object> rci = ci(row);
String key = buildKeyFromSource(keyMaps, rci);
if (!handled.add(key)) {
continue;
}
List<Object> args = new ArrayList<>(keyMaps.size() + 1);
args.add(writeValue);
for (MesXslMcsSyncField k : keyMaps) {
args.add(rci.get(k.getSourceField()));
}
argsList.add(args.toArray());
}
return batch(sourceJt, sql.toString(), argsList);
}
//update-end---author:GHT ---date:20260617 for【MES上辅机】增量匹配改为标记位回写-----------
// ---------------- 现有数据加载 ----------------
private Map<String, Map<String, Object>> loadExistingAll(JdbcTemplate jt, String table, LinkedHashSet<String> existCols,
List<MesXslMcsSyncField> keyMaps, boolean hasDel) {
String sql = "SELECT " + colListBt(existCols) + " FROM `" + table + "`" + (hasDel ? " WHERE `del_flag` = 0" : "");
Map<String, Map<String, Object>> map = new HashMap<>();
for (Map<String, Object> er : jt.queryForList(sql)) {
Map<String, Object> eci = ci(er);
map.put(buildKeyFromTarget(keyMaps, eci), eci);
}
return map;
}
private Map<String, Map<String, Object>> loadExistingByKeys(JdbcTemplate jt, String table, LinkedHashSet<String> existCols,
List<MesXslMcsSyncField> keyMaps, boolean hasDel,
List<Map<String, Object>> rows) {
Map<String, Map<String, Object>> map = new HashMap<>();
MesXslMcsSyncField firstKey = keyMaps.get(0);
// 收集窗口内首匹配键去重值
LinkedHashSet<Object> values = new LinkedHashSet<>();
for (Map<String, Object> row : rows) {
Object v = ci(row).get(firstKey.getSourceField());
if (v != null) {
values.add(v);
}
}
if (values.isEmpty()) {
return map;
}
List<Object> valueList = new ArrayList<>(values);
for (int i = 0; i < valueList.size(); i += IN_CHUNK) {
List<Object> part = valueList.subList(i, Math.min(i + IN_CHUNK, valueList.size()));
String ph = part.stream().map(x -> "?").collect(Collectors.joining(","));
String sql = "SELECT " + colListBt(existCols) + " FROM `" + table + "` WHERE `"
+ firstKey.getTargetField() + "` IN (" + ph + ")" + (hasDel ? " AND `del_flag` = 0" : "");
for (Map<String, Object> er : jt.queryForList(sql, part.toArray())) {
Map<String, Object> eci = ci(er);
map.put(buildKeyFromTarget(keyMaps, eci), eci);
}
}
return map;
}
// ---------------- 追加写入 ----------------
private int appendInsert(JdbcTemplate jt, String table, List<MesXslMcsSyncField> maps, AutoCols auto,
List<Map<String, Object>> rows) {
List<Object[]> insertArgs = new ArrayList<>();
for (Map<String, Object> row : rows) {
insertArgs.add(buildInsertArgs(maps, ci(row), auto));
}
return batch(jt, buildInsertSql(table, maps, auto), insertArgs);
}
// ---------------- SQL 构建 ----------------
private String buildInsertSql(String table, List<MesXslMcsSyncField> maps, AutoCols auto) {
List<String> cols = new ArrayList<>();
maps.forEach(m -> cols.add(m.getTargetField()));
if (auto.id) {
cols.add("id");
}
cols.addAll(auto.cols);
String colSql = cols.stream().map(c -> "`" + c + "`").collect(Collectors.joining(","));
String ph = cols.stream().map(c -> "?").collect(Collectors.joining(","));
return "INSERT INTO `" + table + "` (" + colSql + ") VALUES (" + ph + ")";
}
private Object[] buildInsertArgs(List<MesXslMcsSyncField> maps, Map<String, Object> ci, AutoCols auto) {
List<Object> args = new ArrayList<>(maps.size() + auto.vals.size() + 1);
for (MesXslMcsSyncField m : maps) {
args.add(ci.get(m.getSourceField()));
}
if (auto.id) {
args.add(IdWorker.getIdStr());
}
args.addAll(auto.vals);
return args.toArray();
}
private String buildUpdateSql(String table, List<String> setCols, List<MesXslMcsSyncField> keyMaps,
boolean updTime, boolean updBy, boolean hasDel) {
if (setCols.isEmpty()) {
return null;
}
StringBuilder sql = new StringBuilder("UPDATE `").append(table).append("` SET ");
sql.append(setCols.stream().map(c -> "`" + c + "` = ?").collect(Collectors.joining(",")));
if (updTime) {
sql.append(", `update_time` = ?");
}
if (updBy) {
sql.append(", `update_by` = ?");
}
sql.append(" WHERE ");
sql.append(keyMaps.stream().map(k -> "`" + k.getTargetField() + "` = ?").collect(Collectors.joining(" AND ")));
if (hasDel) {
sql.append(" AND `del_flag` = 0");
}
return sql.toString();
}
private Object[] buildUpdateArgs(List<MesXslMcsSyncField> nonKeyMaps, List<MesXslMcsSyncField> keyMaps,
Map<String, Object> ci, boolean updTime, boolean updBy, Timestamp now) {
List<Object> args = new ArrayList<>();
for (MesXslMcsSyncField m : nonKeyMaps) {
args.add(ci.get(m.getSourceField()));
}
if (updTime) {
args.add(now);
}
if (updBy) {
args.add("mcs-sync");
}
for (MesXslMcsSyncField k : keyMaps) {
args.add(ci.get(k.getSourceField()));
}
return args.toArray();
}
// ---------------- 工具 ----------------
/** 自动填充列汇总id 单独标记,因每行不同) */
private static class AutoCols {
boolean id;
final List<String> cols = new ArrayList<>();
final List<Object> vals = new ArrayList<>();
}
private AutoCols buildAutoCols(Set<String> targetCols, List<MesXslMcsSyncField> maps, int tenantId, Timestamp now) {
AutoCols a = new AutoCols();
a.id = targetCols.contains("id") && !mappedContains(maps, "id");
addAuto(a, targetCols, maps, "create_time", now);
addAuto(a, targetCols, maps, "update_time", now);
addAuto(a, targetCols, maps, "create_by", "mcs-sync");
addAuto(a, targetCols, maps, "update_by", "mcs-sync");
addAuto(a, targetCols, maps, "tenant_id", tenantId);
addAuto(a, targetCols, maps, "del_flag", 0);
return a;
}
private void addAuto(AutoCols a, Set<String> targetCols, List<MesXslMcsSyncField> maps, String col, Object val) {
if (targetCols.contains(col) && !mappedContains(maps, col)) {
a.cols.add(col);
a.vals.add(val);
}
}
/** 返回 [start, end]end 可为 null */
private Timestamp[] timeWindow(String window, Timestamp now) {
String w = StringUtils.isBlank(window) ? "TODAY" : window.trim().toUpperCase();
if ("LAST7".equals(w)) {
return new Timestamp[]{Timestamp.valueOf(LocalDateTime.now().minusDays(7)), null};
}
// 默认当天
Timestamp start = Timestamp.valueOf(LocalDate.now().atStartOfDay());
Timestamp end = Timestamp.valueOf(LocalDate.now().plusDays(1).atStartOfDay());
return new Timestamp[]{start, end};
}
private String requireIncrColumn(MesXslMcsSyncConfig cfg) {
String col = trim(cfg.getIncrColumn());
if (StringUtils.isBlank(col)) {
throw new IllegalArgumentException("当前采集模式需指定标记列/时间列,请在采集操作中配置");
}
validateIdent(col);
return col;
}
private int batch(JdbcTemplate jt, String sql, List<Object[]> argsList) {
if (sql == null || argsList.isEmpty()) {
return 0;
}
int total = 0;
for (int i = 0; i < argsList.size(); i += BATCH_SIZE) {
List<Object[]> part = argsList.subList(i, Math.min(i + BATCH_SIZE, argsList.size()));
jt.batchUpdate(sql, part);
total += part.size();
}
return total;
}
private boolean isChanged(List<MesXslMcsSyncField> nonKeyMaps, Map<String, Object> ci, Map<String, Object> existing) {
for (MesXslMcsSyncField m : nonKeyMaps) {
if (!normVal(ci.get(m.getSourceField())).equals(normVal(existing.get(m.getTargetField())))) {
return true;
}
}
return false;
}
private String buildKeyFromSource(List<MesXslMcsSyncField> keyMaps, Map<String, Object> ci) {
return keyMaps.stream().map(k -> normKey(ci.get(k.getSourceField()))).collect(Collectors.joining("||"));
}
private String buildKeyFromTarget(List<MesXslMcsSyncField> keyMaps, Map<String, Object> eci) {
return keyMaps.stream().map(k -> normKey(eci.get(k.getTargetField()))).collect(Collectors.joining("||"));
}
private Map<String, Object> ci(Map<String, Object> row) {
Map<String, Object> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
m.putAll(row);
return m;
}
private String colList(LinkedHashSet<String> cols) {
return cols.stream().map(c -> "[" + c + "]").collect(Collectors.joining(","));
}
private String colListBt(LinkedHashSet<String> cols) {
return cols.stream().map(c -> "`" + c + "`").collect(Collectors.joining(","));
}
private String normKey(Object v) {
return v == null ? "" : String.valueOf(v).trim();
}
private String normVal(Object v) {
return v == null ? " " : String.valueOf(v);
}
private boolean mappedContains(List<MesXslMcsSyncField> maps, String targetCol) {
return maps.stream().anyMatch(m -> m.getTargetField() != null && m.getTargetField().equalsIgnoreCase(targetCol));
}
private DataSource getSourceDataSource() {
DynamicRoutingDataSource routing = (DynamicRoutingDataSource) dataSource;
DataSource src = routing.getDataSources().get(McsDataSourceManager.DS_KEY);
if (src == null) {
throw new IllegalStateException("中间库数据源 " + McsDataSourceManager.DS_KEY + " 未注册");
}
return src;
}
private void validateIdent(String name) {
if (name == null || !IDENT.matcher(name).matches()) {
throw new IllegalArgumentException("非法的表名或字段名: " + name);
}
}
private String trim(String s) {
return s == null ? null : s.trim();
}
//update-end---author:GHT ---date:20260617 for【MES上辅机】采集模式 全量/时间/增量-----------
}

View File

@@ -0,0 +1,162 @@
package org.jeecg.modules.xslmes.mcs.sync;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.xslmes.mcs.datasource.McsDataSourceManager;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncConfig;
import org.jeecg.modules.xslmes.mcs.entity.MesXslMcsSyncField;
import org.jeecg.modules.xslmes.mcs.mapper.MesXslMcsSyncConfigMapper;
import org.jeecg.modules.xslmes.mcs.mapper.MesXslMcsSyncFieldMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
* 中间表采集调度器(通用,配置驱动)。
* <p>基于 {@link ThreadPoolTaskScheduler} 为每个运行中的采集配置维护一个可重排的定时任务,
* 支持秒级间隔、运行时改间隔、启动/停止。每次触发调用 {@link GenericMcsSyncEngine} 执行采集。</p>
*
* @author GHT
* @date 2026-06-17 for【MES上辅机】采集配置-表与字段绑定
*/
@Slf4j
@Component
public class McsSyncScheduler {
private static final String LOG_TAG = "[MCS采集]";
@Autowired
private MesXslMcsSyncConfigMapper syncConfigMapper;
@Autowired
private MesXslMcsSyncFieldMapper syncFieldMapper;
@Autowired
private McsDataSourceManager mcsDataSourceManager;
@Autowired
private GenericMcsSyncEngine syncEngine;
/** 运行中的定时任务configId -> future */
private final Map<String, ScheduledFuture<?>> runningTasks = new ConcurrentHashMap<>();
private ThreadPoolTaskScheduler taskScheduler;
@PostConstruct
public void init() {
taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(4);
taskScheduler.setThreadNamePrefix("mcs-sync-");
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
taskScheduler.setAwaitTerminationSeconds(10);
taskScheduler.initialize();
log.info("{} 采集调度器初始化完成", LOG_TAG);
}
@PreDestroy
public void destroy() {
runningTasks.values().forEach(f -> f.cancel(false));
runningTasks.clear();
if (taskScheduler != null) {
taskScheduler.shutdown();
}
}
/**
* 应用启动后,加载所有 status=1 的采集配置并启动定时任务
*/
@EventListener(ApplicationReadyEvent.class)
public void loadOnStartup() {
try {
List<MesXslMcsSyncConfig> configs = syncConfigMapper.selectList(
new LambdaQueryWrapper<MesXslMcsSyncConfig>()
.eq(MesXslMcsSyncConfig::getDelFlag, 0)
.eq(MesXslMcsSyncConfig::getStatus, "1"));
configs.forEach(this::scheduleTask);
log.info("{} 启动加载采集任务完成,已启动={}", LOG_TAG, configs.size());
} catch (Exception e) {
log.error("{} 启动加载采集任务失败: {}", LOG_TAG, e.getMessage(), e);
}
}
public boolean isRunning(String configId) {
ScheduledFuture<?> f = runningTasks.get(configId);
return f != null && !f.isCancelled();
}
/**
* (重新)按配置的间隔调度采集任务。已存在则先取消再重排,实现运行时改间隔。
*/
public synchronized void scheduleTask(MesXslMcsSyncConfig config) {
if (config == null || config.getId() == null) {
return;
}
cancelTask(config.getId());
long seconds = config.getIntervalSeconds() != null && config.getIntervalSeconds() > 0
? config.getIntervalSeconds() : 1L;
String configId = config.getId();
ScheduledFuture<?> future = taskScheduler.scheduleWithFixedDelay(
() -> runOnce(configId), Duration.ofSeconds(seconds));
runningTasks.put(configId, future);
log.info("{} 采集任务已启动 configId={} 间隔={}s", LOG_TAG, configId, seconds);
}
/**
* 取消采集任务(仅停内存定时,不改库)
*/
public synchronized void cancelTask(String configId) {
ScheduledFuture<?> old = runningTasks.remove(configId);
if (old != null) {
old.cancel(false);
log.info("{} 采集任务已停止 configId={}", LOG_TAG, configId);
}
}
/**
* 单次采集执行:连接/读取开关守卫 + 调用通用引擎 + 落库结果
*/
private void runOnce(String configId) {
MesXslMcsSyncConfig cfg = syncConfigMapper.selectById(configId);
if (cfg == null || cfg.getDelFlag() != null && cfg.getDelFlag() == 1 || !"1".equals(cfg.getStatus())) {
return;
}
// 中间库未启用或读取开关关闭时安静跳过
if (!mcsDataSourceManager.isDbConfigActive() || !mcsDataSourceManager.isReadEnabled()) {
log.debug("{} 中间库未就绪或读取关闭,跳过 configId={}", LOG_TAG, configId);
return;
}
try {
List<MesXslMcsSyncField> fields = syncFieldMapper.selectList(
new LambdaQueryWrapper<MesXslMcsSyncField>()
.eq(MesXslMcsSyncField::getConfigId, configId)
.eq(MesXslMcsSyncField::getDelFlag, 0)
.orderByAsc(MesXslMcsSyncField::getSortNo));
String result = syncEngine.sync(cfg, fields);
// INCR 改为标记位回写后不再维护高水位,仅落库采集结果
updateSyncResult(configId, result, null);
} catch (Exception e) {
log.error("{} 采集异常 configId={}: {}", LOG_TAG, configId, e.getMessage(), e);
updateSyncResult(configId, "采集失败:" + e.getMessage(), null);
}
}
private void updateSyncResult(String id, String result, String watermark) {
MesXslMcsSyncConfig update = new MesXslMcsSyncConfig();
update.setId(id);
update.setLastSyncTime(new Date());
update.setLastSyncResult(result != null && result.length() > 480 ? result.substring(0, 480) : result);
update.setLastWatermark(watermark);
syncConfigMapper.updateById(update);
}
}

View File

@@ -0,0 +1,90 @@
package org.jeecg.modules.xslmes.mcs.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.mcs.entity.McsToMesMixAlarm;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipAlarmRecordVO;
import org.jeecg.modules.xslmes.mcs.vo.MesXslEquipDowntimeRecordVO;
/**
* MCSToMES_MixAlarm 与 MES 设备报警/停机展示 VO 转换
*/
public final class MesXslMcsMixAlarmConvertUtil {
public static final String RECORD_TYPE_AUTO = "自动";
private static final String[] DATE_PATTERNS = {
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy/MM/dd HH:mm"
};
private MesXslMcsMixAlarmConvertUtil() {}
public static MesXslEquipAlarmRecordVO toAlarmVo(McsToMesMixAlarm row) {
MesXslEquipAlarmRecordVO vo = new MesXslEquipAlarmRecordVO();
vo.setId(row.getGuid());
vo.setEquipId(row.getEquipId());
vo.setEquipName(row.getEquipName());
vo.setEquipType(row.getEquipType());
vo.setShiftClass(null);
vo.setAlarmContent(row.getAlarmInf());
vo.setAlarmTime(row.getWriteTime());
vo.setRecordType(RECORD_TYPE_AUTO);
return vo;
}
public static MesXslEquipDowntimeRecordVO toDowntimeVo(McsToMesMixAlarm row) {
MesXslEquipDowntimeRecordVO vo = new MesXslEquipDowntimeRecordVO();
vo.setId(row.getGuid());
vo.setEquipId(row.getEquipId());
vo.setEquipName(row.getEquipName());
vo.setEquipType(row.getEquipType());
vo.setShiftClass(null);
vo.setDowntimeContent(row.getAlarmInf());
vo.setDowntimeStartTime(row.getWriteTime());
vo.setDowntimeEndTime(row.getEndTime());
vo.setDowntimeDuration(formatDowntimeDuration(row.getWriteTime(), row.getEndTime()));
vo.setRecordType(RECORD_TYPE_AUTO);
return vo;
}
public static String formatDowntimeDuration(Date start, String endStr) {
if (start == null || oConvertUtils.isEmpty(endStr)) {
return null;
}
Date end = parseDateTime(endStr);
if (end == null) {
return null;
}
long diffMs = end.getTime() - start.getTime();
if (diffMs < 0) {
return null;
}
long totalMinutes = diffMs / 60000;
long hours = totalMinutes / 60;
long minutes = totalMinutes % 60;
if (hours > 0) {
return minutes > 0 ? hours + "小时" + minutes + "分钟" : hours + "小时";
}
return minutes + "分钟";
}
public static Date parseDateTime(String text) {
if (oConvertUtils.isEmpty(text)) {
return null;
}
String trimmed = text.trim();
for (String pattern : DATE_PATTERNS) {
try {
return new SimpleDateFormat(pattern).parse(trimmed);
} catch (ParseException ignored) {
// try next pattern
}
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
package org.jeecg.modules.xslmes.mcs.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 设备报警记录(展示 VO数据来自 SQL Server MCSToMES_MixAlarm
*/
@Data
@Schema(description = "设备报警记录")
public class MesXslEquipAlarmRecordVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private String id;
@Excel(name = "机台编号", width = 15)
@Schema(description = "机台编号")
private String equipId;
@Excel(name = "机台名称", width = 15)
@Schema(description = "机台名称")
private String equipName;
@Excel(name = "机台类型", width = 15)
@Schema(description = "机台类型")
private String equipType;
@Excel(name = "班次", width = 12)
@Schema(description = "班次")
private String shiftClass;
@Excel(name = "报警内容", width = 40)
@Schema(description = "报警内容")
private String alarmContent;
@Excel(name = "报警时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "报警时间")
private Date alarmTime;
@Excel(name = "类型", width = 12)
@Schema(description = "类型(自动/人工)")
private String recordType;
}

View File

@@ -0,0 +1,59 @@
package org.jeecg.modules.xslmes.mcs.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 设备停机记录(展示 VO数据来自 SQL Server MCSToMES_MixAlarm
*/
@Data
@Schema(description = "设备停机记录")
public class MesXslEquipDowntimeRecordVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键")
private String id;
@Excel(name = "机台编号", width = 15)
@Schema(description = "机台编号")
private String equipId;
@Excel(name = "机台名称", width = 15)
@Schema(description = "机台名称")
private String equipName;
@Excel(name = "机台类型", width = 15)
@Schema(description = "机台类型")
private String equipType;
@Excel(name = "班次", width = 12)
@Schema(description = "班次")
private String shiftClass;
@Excel(name = "停机内容", width = 40)
@Schema(description = "停机内容")
private String downtimeContent;
@Excel(name = "停机开始时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "停机开始时间")
private Date downtimeStartTime;
@Excel(name = "停机结束时间", width = 20)
@Schema(description = "停机结束时间")
private String downtimeEndTime;
@Excel(name = "停机时长", width = 15)
@Schema(description = "停机时长")
private String downtimeDuration;
@Excel(name = "类型", width = 12)
@Schema(description = "类型(自动/人工)")
private String recordType;
}

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import org.jeecg.modules.xslmes.entity.MesXslAutoSmallMaterialPlanMaintain;
public interface IMesXslAutoSmallMaterialPlanMaintainService
extends IService<MesXslAutoSmallMaterialPlanMaintain> {
void saveAllRows(List<MesXslAutoSmallMaterialPlanMaintain> rows);
}

View File

@@ -0,0 +1,41 @@
package org.jeecg.modules.xslmes.service;
import java.util.Collection;
/**
* MES 设备管理 / 质量管理主数据删除前引用校验。
*/
public interface IMesXslDeleteReferenceService {
String validateEquipmentCategoryDelete(Collection<String> ids);
String validateEquipmentTypeDelete(Collection<String> ids);
String validateEquipmentPartDelete(Collection<String> ids);
String validateEquipmentSubPartDelete(Collection<String> ids);
String validateInspectMaintainItemDelete(Collection<String> ids);
String validateEquipmentLedgerDelete(Collection<String> ids);
String validateManufacturerDelete(Collection<String> ids);
String validateSparePartsCategoryDelete(Collection<String> ids);
String validateDowntimeMainTypeDelete(Collection<String> ids);
String validateDowntimeTypeDelete(Collection<String> ids);
String validateEquipInspectConfigDelete(Collection<String> ids);
String validateRubberQuickTestTypeDelete(Collection<String> ids);
String validateRubberQuickTestDataPointDelete(Collection<String> ids);
String validateRubberQuickTestMethodDelete(Collection<String> ids);
String validateRubberQuickTestStdDelete(Collection<String> ids);
String validateRubberSmallLockReasonDelete(Collection<String> ids);
}

View File

@@ -0,0 +1,10 @@
package org.jeecg.modules.xslmes.service;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import org.jeecg.modules.xslmes.entity.MesXslManualSmallMaterialPlanMaintain;
public interface IMesXslManualSmallMaterialPlanMaintainService
extends IService<MesXslManualSmallMaterialPlanMaintain> {
void saveAllRows(List<MesXslManualSmallMaterialPlanMaintain> rows);
}

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