Compare commits

72 Commits

Author SHA1 Message Date
zc
5407bae132 优化 2026-06-15 18:10:42 +08:00
zc
e370b51c39 天海优化配置 2026-05-09 09:25:47 +08:00
zc
5cc05036da 天海优化数据库名 2026-05-08 16:48:26 +08:00
zc
e72867edbe 整箱导出优化 2026-05-07 16:22:54 +08:00
zc
470a2dbe6e 主从库双数据源 2026-05-07 15:58:13 +08:00
zc
3113ba2542 优化 2026-04-30 17:01:53 +08:00
zc
d976555206 优化 2026-04-27 17:14:42 +08:00
zc
0da708c29f 整箱优化 2026-04-27 16:20:22 +08:00
zc
bcf22400b5 提交 2026-04-21 14:15:58 +08:00
zc
2b6c4ff7f3 优化标记号 2026-04-17 17:01:04 +08:00
zc
e1a9fba2ca 优化工单导出 2026-04-16 13:45:13 +08:00
zc
f88d4d8fe7 优化 2026-04-16 00:42:25 +08:00
zc
eda31fcffd 优化 2026-04-13 11:52:55 +08:00
zc
bfcb05893a 优化 2026-04-13 11:03:05 +08:00
zc
5e2972e8c5 优化 2026-04-12 23:22:03 +08:00
zc
a323ddb10b 优化宇视图片保存地址 2026-04-12 19:56:26 +08:00
zc
e6a12aaeb2 优化 2026-04-12 18:28:02 +08:00
zc
371daf2eee 优化宇视打包jar包依赖问题 2026-04-10 01:21:00 +08:00
zc
c03bcfb043 称重优化 2026-04-09 20:09:17 +08:00
zc
677349559c 称重优化 2026-04-09 16:02:05 +08:00
zc
90eb4a1af8 称重优化 2026-04-09 11:05:57 +08:00
zc
95242f772e 称重优化 2026-04-07 17:40:42 +08:00
zc
8196e34cb4 称重优化连接 2026-04-07 16:38:35 +08:00
zc
da9823ee18 灯光调整 2026-04-07 14:58:30 +08:00
zc
f55ace8ec0 图片定时任务 2026-04-07 11:02:50 +08:00
zc
ca91379e38 优化灯光 2026-04-07 09:52:49 +08:00
zc
4f34e267d7 物料流程整合 2026-04-03 17:31:20 +08:00
zc
5f86b1c909 优化 2026-04-03 16:52:21 +08:00
zc
e8c34248e9 优化 2026-04-03 16:21:28 +08:00
zc
6da72d6702 优化 2026-03-27 11:36:06 +08:00
zc
402d04294c 优化 2026-03-25 17:36:06 +08:00
zc
c4f29d3ee5 新页面整箱领取 2026-03-24 14:55:12 +08:00
zc
8bacb6ec5e 宇视抓拍图片 2026-03-23 18:05:15 +08:00
zc
027da720fd 宇视抓拍图片 2026-03-23 15:40:22 +08:00
zc
3b8d01b673 优化 2026-03-20 18:06:45 +08:00
zc
18dbf7a042 优化 2026-03-20 16:36:42 +08:00
zc
1937049b0e 优化 2026-03-20 16:25:09 +08:00
zc
2d3d4e3f56 优化 2026-03-18 17:41:36 +08:00
zc
06c600f67d 优化 2026-03-18 11:24:49 +08:00
zc
50a1c03776 优化不联网 2026-03-17 15:17:42 +08:00
zc
61a18e781d 优化 2026-03-17 10:48:29 +08:00
zc
8d588502aa 优化 2026-03-16 09:38:58 +08:00
zc
bf1af52660 Merge remote-tracking branch 'refs/remotes/origin/master_lz'
# Conflicts:
#	wms-webapi/src/main/java/top/wms/admin/controller/tcp/service/CommandService.java
#	wms-webapi/src/main/java/top/wms/admin/controller/weighManage/ah/AHDZCConnect.java
2026-03-13 10:33:03 +08:00
cd1ba55b26 优化 2026-03-13 10:30:55 +08:00
zc
d60966dd3f 优化 2026-03-13 09:47:51 +08:00
zc
687be5840e Merge branch 'refs/heads/master_lz' 2026-03-12 20:20:46 +08:00
zc
d9f808ecc1 tcp服务启动 2026-03-12 19:45:07 +08:00
c1d84aaf81 优化 2026-03-12 16:54:55 +08:00
zc
dec15eb913 tcp服务启动 2026-03-11 17:53:58 +08:00
18e014d9cb Merge branch 'master' into master_lz 2026-03-11 17:44:57 +08:00
2cb03b146a tcp服务 2026-03-11 17:44:06 +08:00
zc
7d48c78c9b 海康工业相机优化关闭socket视频重连异常 2026-03-11 17:39:05 +08:00
zc
c80dce6419 海康工业相机对接彩色视频流 2026-03-11 15:05:14 +08:00
zc
4802e11f7c 海康工业相机对接优化视频流 2026-03-11 11:09:56 +08:00
zc
fde2f18ff5 海康工业相机对接黑白画面 2026-03-11 10:55:59 +08:00
zc
41e0b0b5b4 添加海康工业相机demo代码 2026-03-10 16:54:45 +08:00
zc
fb05360c5e 代码优化 2026-03-09 20:33:47 +08:00
df9479f870 抓取失败返回优化 2026-03-09 16:25:41 +08:00
e4d6de61ae Merge branch 'refs/heads/master' into master_lz 2026-03-06 17:59:47 +08:00
73c1bc9d01 称重抓取 2026-03-06 17:58:55 +08:00
zc
ae9a607da1 代码优化 2026-03-06 15:55:06 +08:00
32f88b1443 物料查询返回错误提示 2026-03-06 15:41:12 +08:00
zc
c59df81b38 电子秤设备&优化 2026-03-05 18:13:39 +08:00
zc
66449b0482 电子秤设备 2026-03-05 16:02:36 +08:00
5726239eda 优化用户重置密码加密+物料照片批量导入 2026-03-05 14:39:20 +08:00
656267e6b7 Merge branch 'refs/heads/master' into master_lz
# Conflicts:
#	wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java
#	wms-module-system/src/main/java/top/wms/admin/material/service/impl/MaterialInfoServiceImpl.java
2026-03-05 14:27:48 +08:00
zc
381c1638e3 优化追溯 2026-03-04 18:05:00 +08:00
d95f527938 Merge branch 'master' into master_lz 2026-03-04 18:02:30 +08:00
97df87eb06 优化 2026-03-04 18:01:13 +08:00
zc
b98a0f06ba 优化称重 2026-03-03 17:58:57 +08:00
zc
15af775af4 Merge remote-tracking branch 'refs/remotes/origin/master_lz'
# Conflicts:
#	wms-module-system/src/main/java/top/wms/admin/auth/handler/CardLoginHandler.java
2026-03-03 15:26:12 +08:00
zc
85e9e5d110 优化登录 2026-03-03 15:24:48 +08:00
202 changed files with 25039 additions and 1672 deletions

View File

@@ -1,212 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter</artifactId>
<version>2.9.0</version>
</parent>
<groupId>top.wms</groupId>
<artifactId>wms-admin</artifactId>
<version>3.6.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>园区管理系统</description>
<url>https://github.com/wms-org/wms-admin</url>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<modules>
<module>wms-webapi</module>
<module>wms-module-system</module>
<module>wms-plugin</module>
<module>wms-common</module>
<module>wms-extension</module>
</modules>
<properties>
<revision>3.6.0-SNAPSHOT</revision>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-webapi</artifactId>
<version>3.6.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-module-system</artifactId>
<version>3.6.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-common</artifactId>
<version>3.6.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-plugin-schedule</artifactId>
<version>3.6.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-plugin-generator</artifactId>
<version>3.6.0-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>huawei-mirror</id>
<name>HuaweiCloud Mirror</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</repository>
<repository>
<id>ali-mirror</id>
<name>AliYun Mirror</name>
<url>https://maven.aliyun.com/repository/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>huawei-mirror</id>
<name>HuaweiCloud Mirror</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</pluginRepository>
<pluginRepository>
<id>ali-mirror</id>
<name>AliYun Mirror</name>
<url>https://maven.aliyun.com/repository/public/</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>apply</goal>
</goals>
</execution>
</executions>
<configuration>
<java>
<removeUnusedImports />
<eclipse>
<file>.style/p3c-codestyle.xml</file>
</eclipse>
<licenseHeader>
<file>.style/license-header</file>
</licenseHeader>
</java>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten-clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>sonar</id>
<activation />
<build>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sonar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<sonar.projectKey>Charles7c_wms-admin</sonar.projectKey>
<sonar.moduleKey>${project.groupId}:${project.artifactId}</sonar.moduleKey>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>charles7c</sonar.organization>
</properties>
</profile>
</profiles>
</project>

51
.gitignore vendored Normal file
View File

@@ -0,0 +1,51 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
logs/
sdklog/
*.log
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Maven ###
.flattened-pom.xml
### JVM ###
hs_err_pid*
replay_pid*
### OS ###
.DS_Store
Thumbs.db
### Spotless ###
spotless-index

View File

@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.wms</groupId>
<artifactId>wms-admin</artifactId>
<version>3.6.0-SNAPSHOT</version>
</parent>
<artifactId>wms-common</artifactId>
<version>3.6.0-SNAPSHOT</version>
<description>公共模块(存放公共工具类,公共配置等)</description>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-redis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.780</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-satoken</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-auth-justauth</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-jetcache</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-websocket</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-messaging-mail</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-graphic</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-captcha-behavior</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-limiter</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-mask</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-security-password</artifactId>
</dependency>
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -155,5 +155,12 @@
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.100.Final</version> <!-- 使用较新稳定版本 -->
</dependency>
</dependencies>
</project>
</project>

View File

@@ -0,0 +1,18 @@
package top.wms.admin.common.config.mybatis;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import top.wms.admin.common.constant.DataSourceContextHolder;
/**
* 动态数据源路由
*
* @author Admin
* @since 2024/12/22
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}

View File

@@ -0,0 +1,40 @@
package top.wms.admin.common.constant;
/**
* 数据源上下文 Holder
*
* @author Admin
* @since 2024/12/22
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
private DataSourceContextHolder() {
}
/**
* 设置数据源
*
* @param dataSource 数据源标识
*/
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
/**
* 获取数据源
*
* @return 数据源标识
*/
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
/**
* 清除数据源
*/
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}

View File

@@ -74,6 +74,11 @@ public class UserContext implements Serializable {
*/
private String clientId;
/**
* 数据源标识
*/
private String dataSource;
public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);
@@ -113,4 +118,4 @@ public class UserContext implements Serializable {
}
return this.pwdResetTime.plusDays(this.passwordExpirationDays).isBefore(LocalDateTime.now());
}
}
}

View File

@@ -0,0 +1,63 @@
package top.wms.admin.common.enums;
/**
* DPA6024V-2T-1.0 数字控制器命令类型枚举
* 定义控制器支持的指令字
*/
public enum CommandTypeEnum {
/**
* 打开对应通道 (指令字: 1)
*/
ON('1'),
/**
* 关闭对应通道 (指令字: 2)
*/
OFF('2'),
/**
* 设置对应通道亮度参数 (指令字: 3)
*/
SET_BRIGHTNESS('3'),
/**
* 读出对应通道亮度参数 (指令字: 4)
*/
READ('4');
private final char commandCode;
CommandTypeEnum(char commandCode) {
this.commandCode = commandCode;
}
/**
* 获取指令字字符
*
* @return 指令字 (如 '1', '2', '3', '4')
*/
public char getCommandCode() {
return commandCode;
}
/**
* 根据指令字获取对应的命令类型
*
* @param commandCode 指令字字符
* @return 对应的CommandType找不到返回null
*/
public static CommandTypeEnum fromCommandCode(char commandCode) {
for (CommandTypeEnum type : values()) {
if (type.commandCode == commandCode) {
return type;
}
}
return null;
}
@Override
public String toString() {
return String.valueOf(commandCode);
}
}

View File

@@ -1,24 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 交易状态枚举
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeOrderTypeEnum implements BaseEnum<Integer> {
/**
* 在线消费
*/
CONSUME(0, "消费");
private final Integer value;
private final String description;
}

View File

@@ -1,49 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 支付方式枚举
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumePayModeEnum implements BaseEnum<Integer> {
/**
* 人脸
*/
FACE(0, "人脸"),
/**
* 云卡
*/
CLOUD_CARD(1, "云卡"),
/**
* 刷卡
*/
SWIPE_CARD(2, "刷卡"),
/**
* 支付宝
*/
ALIPAY(3, "支付宝"),
/**
* 微信
*/
WECHAT(4, "微信"),
/**
* 取餐码
*/
MEAL_CODE(5, "取餐码");
private final Integer value;
private final String description;
}

View File

@@ -1,39 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 消费-充值方式
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeRechargeModeEnum implements BaseEnum<Integer> {
/**
* 现金
*/
CASH(0, "现金"),
/**
* 支付宝
*/
ALIPAY(1, "支付宝"),
/**
* 微信
*/
WECHAT(2, "微信"),
/**
* 网银
*/
ONLINE_BANKING(3, "网银");
private final Integer value;
private final String description;
}

View File

@@ -1,49 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 消费-操作类型
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeRechargeTypeEnum implements BaseEnum<Integer> {
/**
* 补贴
*/
SUBSIDY(0, "补贴"),
/**
* 充值
*/
RECHARGE(1, "充值"),
/**
* 退款
*/
REFUND(2, "退款"),
/**
* 清零(全部)
*/
CLEAR(3, "清零(全部)"),
/**
* 清零(充值)
*/
CLEAR_CZ(4, "清零(充值)"),
/**
* 清零(补贴)
*/
CLEAR_BT(5, "清零(补贴)");
private final Integer value;
private final String description;
}

View File

@@ -1,29 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 消费-充值来源
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeRechargeWayEnum implements BaseEnum<Integer> {
/**
* 在线消费
*/
WEB(0, "平台"),
/**
* 离线消费
*/
PHONE(1, "手机");
private final Integer value;
private final String description;
}

View File

@@ -1,44 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 交易状态枚举
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeResultEnum implements BaseEnum<Integer> {
/**
* 在线消费
*/
ONLINE_CONSUME(0, "在线消费"),
/**
* 离线消费
*/
OFFLINE_CONSUME(1, "离线消费"),
/**
* 超时
*/
TIMEOUT(2, "超时"),
/**
* 消费异常
*/
CONSUMPTION_EXCEPTION(3, "消费异常"),
/**
* 异常消费
*/
EXCEPTION_CONSUMPTION(4, "异常消费");
private final Integer value;
private final String description;
}

View File

@@ -1,38 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 消费-充值来源
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeTmrtypeEnum implements BaseEnum<Integer> {
/**
* 早餐
*/
BREAKFAST(0, "早餐"),
/**
* 中餐
*/
LUNCH(1, "中餐"),
/**
* 午餐
*/
DINNER(2, "午餐"),
/**
* 夜餐
*/
NIGHT(3, "夜餐");
private final Integer value;
private final String description;
}

View File

@@ -1,49 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 消费类型枚举
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeTypeConverter implements BaseEnum<Integer> {
/**
* 单价
*/
UNIT_PRICE(0, "单价"),
/**
* 定额
*/
FIXED_PRICE(1, "定额"),
/**
* 时段模式
*/
TIME_PERIOD(2, "时段模式"),
/**
* 计次
*/
COUNTING(3, "计次"),
/**
* 点餐机模式
*/
ORDERING_MACHINE(5, "点餐机模式"),
/**
* 身份模式
*/
IDENTITY(9, "身份模式");
private final Integer value;
private final String description;
}

View File

@@ -1,34 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 钱包消费模式枚举
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum ConsumeWalletModeEnum implements BaseEnum<Integer> {
/**
* 先消费补贴再个人
*/
SUBSIDY_THEN_PERSONAL(0, "先消费补贴再个人"),
/**
* 仅现金
*/
ONLY_CASH(1, "仅现金"),
/**
* 仅补贴
*/
ONLY_SUBSIDY(2, "仅补贴");
private final Integer value;
private final String description;
}

View File

@@ -0,0 +1,30 @@
package top.wms.admin.common.enums;
/**
* 数据源枚举
*
* @author Admin
* @since 2024/12/22
*/
public enum DataSourceEnum {
/**
* 主数据源
*/
WMS_TH("wms_th"),
/**
* 从数据源
*/
WMS_QL("wms_ql");
private final String value;
DataSourceEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View File

@@ -1,29 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
* 启用禁用
*
* @author Charles7c
* @since 2022/12/29 22:38
*/
@Getter
@RequiredArgsConstructor
public enum EnableEnum implements BaseEnum<Integer> {
/**
* 禁用
*/
DISABLE(0, "禁用"),
/**
* 在线消费
*/
ENABLE(1, "启用");
private final Integer value;
private final String description;
}

View File

@@ -0,0 +1,49 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
/**
*
*
* @author Charles7c
* @since 2023/2/26 21:35
*/
@Getter
@RequiredArgsConstructor
public enum LightLevelEnum implements BaseEnum<Integer> {
/**
* 一级
*/
LEVEL1(1, "一级"),
/**
* 二级
*/
LEVEL2(2, "二级"),
/**
* 三级
*/
LEVEL3(3, "三级");
private final Integer value;
private final String description;
/**
* 根据描述获取值
*
* @param description 描述
* @return 值,如果找不到则返回 null
*/
public static Integer getValueByDescription(String description) {
for (LightLevelEnum enumValue : values()) {
if (enumValue.getDescription().equals(description)) {
return enumValue.getValue();
}
}
return null;
}
}

View File

@@ -0,0 +1,55 @@
package top.wms.admin.common.enums;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import java.util.HashMap;
import java.util.Map;
public class LightLevelEnumConverter implements Converter<Integer> {
private static final Map<Integer, String> LIGHT_LEVEL_MAP = new HashMap<>();
static {
LIGHT_LEVEL_MAP.put(1, "一级");
LIGHT_LEVEL_MAP.put(2, "二级");
LIGHT_LEVEL_MAP.put(3, "三级");
}
@Override
public Class<?> supportJavaTypeKey() {
return Integer.class; // 支持的 Java 类型
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING; // 写入 Excel 时用字符串
}
@Override
public WriteCellData<String> convertToExcelData(Integer value,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (value == null) {
return new WriteCellData<>("");
}
String label = LIGHT_LEVEL_MAP.getOrDefault(value, "");
return new WriteCellData<>(label);
}
@Override
public Integer convertToJavaData(ReadCellData<?> cellData,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
String stringValue = cellData.getStringValue();
for (Map.Entry<Integer, String> entry : LIGHT_LEVEL_MAP.entrySet()) {
if (entry.getValue().equals(stringValue)) {
return entry.getKey();
}
}
return null;
}
}

View File

@@ -1,33 +0,0 @@
package top.wms.admin.common.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import top.continew.starter.core.enums.BaseEnum;
@Getter
@RequiredArgsConstructor
public enum OperTypeEnum implements BaseEnum<Integer> {
/**
* 新增
*/
ADD(0, "新增"),
/**
* 修改
*/
UPDATE(1, "修改"),
/**
* 下发
*/
DOWN(2, "下发"),
/**
* 删除
*/
DEL(3, "删除");
private final Integer value;
private final String description;
}

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.wms</groupId>
<artifactId>wms-admin</artifactId>
<version>3.6.0-SNAPSHOT</version>
</parent>
<artifactId>wms-extension</artifactId>
<version>3.6.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>扩展模块(存放其他扩展模块)</description>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<modules>
<module>wms-extension-schedule-server</module>
</modules>
</project>

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.wms</groupId>
<artifactId>wms-extension</artifactId>
<version>3.6.0-SNAPSHOT</version>
</parent>
<artifactId>wms-extension-schedule-server</artifactId>
<version>3.6.0-SNAPSHOT</version>
<description>任务调度服务端</description>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<properties>
<snail-job.version>1.2.0</snail-job.version>
</properties>
<dependencies>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-server-starter</artifactId>
<version>${snail-job.version}</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.wms</groupId>
<artifactId>wms-admin</artifactId>
<version>3.6.0-SNAPSHOT</version>
</parent>
<artifactId>wms-module-system</artifactId>
<version>3.6.0-SNAPSHOT</version>
<description>系统管理模块(存放系统管理相关业务功能,例如:部门管理、角色管理、用户管理等)</description>
<licenses>
<license>
<name>GNU LESSER GENERAL PUBLIC LICENSE</name>
<url>http://www.gnu.org/licenses/lgpl.html</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -18,5 +18,19 @@
<groupId>top.wms</groupId>
<artifactId>wms-common</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
<!-- 串口通信依赖 -->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.10.5</version>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -8,12 +8,12 @@ import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import top.wms.admin.auth.model.req.LoginReq;
import top.wms.admin.common.constant.DataSourceContextHolder;
import top.wms.admin.common.context.RoleContext;
import top.wms.admin.common.context.UserContext;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.common.context.UserExtraContext;
import top.wms.admin.common.enums.DisEnableStatusEnum;
import top.wms.admin.system.model.entity.DeptDO;
import top.wms.admin.system.model.entity.UserDO;
import top.wms.admin.system.model.resp.ClientResp;
import top.wms.admin.system.service.DeptService;
@@ -92,6 +92,14 @@ public abstract class AbstractLoginHandler<T extends LoginReq> implements LoginH
userContext.setClientType(client.getClientType());
model.setExtra(CLIENT_ID, client.getClientId());
userContext.setClientId(client.getClientId());
// 设置数据源(如果用户未配置,则使用默认主库)
String dataSource = user.getDataSource();
if (dataSource == null || dataSource.isEmpty()) {
dataSource = "wms_th"; // 默认主库
}
userContext.setDataSource(dataSource);
DataSourceContextHolder.setDataSource(dataSource);
// 登录并缓存用户信息
StpUtil.login(userContext.getId(), model.setExtraData(BeanUtil.beanToMap(new UserExtraContext(SpringWebUtils
.getRequest()))));
@@ -106,7 +114,7 @@ public abstract class AbstractLoginHandler<T extends LoginReq> implements LoginH
*/
protected void checkUserStatus(UserDO user) {
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
// DeptDO dept = deptService.getById(user.getDeptId());
// CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, dept.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员");
// DeptDO dept = deptService.getById(user.getDeptId());
// CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, dept.getStatus(), "此账号所属部门已被禁用,如有疑问,请联系管理员");
}
}
}

View File

@@ -1,24 +1,23 @@
package top.wms.admin.auth.handler;
import jakarta.servlet.http.HttpServletRequest;
import top.continew.starter.cache.redisson.util.RedisUtils;
import org.springframework.stereotype.Component;
import top.continew.starter.core.validation.ValidationUtils;
import top.wms.admin.auth.AbstractLoginHandler;
import top.wms.admin.auth.enums.AuthTypeEnum;
import top.wms.admin.auth.model.req.CardLoginReq;
import top.wms.admin.auth.model.req.PhoneLoginReq;
import top.wms.admin.auth.model.resp.LoginResp;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.system.model.entity.UserDO;
import top.wms.admin.system.model.resp.ClientResp;
public class CardLoginHandler extends AbstractLoginHandler<CardLoginReq> {
@Component
public class CardLoginHandler extends AbstractLoginHandler<CardLoginReq> {
@Override
public LoginResp login(CardLoginReq req, ClientResp client, HttpServletRequest request) {
// 验证手机号
UserDO user = userService.getByCard(req.getCardNumber());
ValidationUtils.throwIfNull(user, "手机号未绑定本系统账号");
ValidationUtils.throwIfNull(user, "号未绑定本系统账号");
// 检查用户状态
super.checkUserStatus(user);
// 执行认证
@@ -26,18 +25,13 @@ public class CardLoginHandler extends AbstractLoginHandler<CardLoginReq> {
return LoginResp.builder().token(token).build();
}
// @Override
// public void preLogin(PhoneLoginReq req, ClientResp client, HttpServletRequest request) {
// String phone = req.getPhone();
// String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + phone;
// String captcha = RedisUtils.get(captchaKey);
// ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
// ValidationUtils.throwIfNotEqualIgnoreCase(req.getCaptcha(), captcha, CAPTCHA_ERROR);
// RedisUtils.delete(captchaKey);
// }
@Override
public void preLogin(CardLoginReq req, ClientResp client, HttpServletRequest request) {
super.preLogin(req, client, request);
}
@Override
public AuthTypeEnum getAuthType() {
return AuthTypeEnum.PHONE;
return AuthTypeEnum.CARD;
}
}

View File

@@ -1,39 +1,15 @@
package top.wms.admin.auth.handler;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.json.JSONUtil;
import com.xkcoding.justauth.AuthRequestFactory;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import org.springframework.stereotype.Component;
import top.wms.admin.auth.AbstractLoginHandler;
import top.wms.admin.auth.enums.AuthTypeEnum;
import top.wms.admin.auth.model.req.SocialLoginReq;
import top.wms.admin.auth.model.resp.LoginResp;
import top.wms.admin.common.constant.RegexConstants;
import top.wms.admin.common.constant.SysConstants;
import top.wms.admin.common.enums.DisEnableStatusEnum;
import top.wms.admin.common.enums.GenderEnum;
import top.wms.admin.system.model.entity.RoleDO;
import top.wms.admin.system.model.entity.UserDO;
import top.wms.admin.system.model.entity.UserSocialDO;
import top.wms.admin.system.model.resp.ClientResp;
import top.wms.admin.system.service.UserRoleService;
import top.wms.admin.system.service.UserSocialService;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.validation.ValidationUtils;
import java.time.LocalDateTime;
import java.util.Collections;
/**
* 第三方账号登录处理器
@@ -46,57 +22,10 @@ import java.util.Collections;
@RequiredArgsConstructor
public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
private final AuthRequestFactory authRequestFactory;
private final UserSocialService userSocialService;
private final UserRoleService userRoleService;
private final ProjectProperties projectProperties;
@Override
public LoginResp login(SocialLoginReq req, ClientResp client, HttpServletRequest request) {
// 获取第三方登录信息
AuthRequest authRequest = this.getAuthRequest(req.getSource());
AuthCallback callback = new AuthCallback();
callback.setCode(req.getCode());
callback.setState(req.getState());
AuthResponse<AuthUser> response = authRequest.login(callback);
ValidationUtils.throwIf(!response.ok(), response.getMsg());
AuthUser authUser = response.getData();
// 如未绑定则自动注册新用户,保存或更新关联信息
String source = authUser.getSource();
String openId = authUser.getUuid();
UserSocialDO userSocial = userSocialService.getBySourceAndOpenId(source, openId);
UserDO user;
if (null == userSocial) {
String username = authUser.getUsername();
UserDO existsUser = userService.getByUsername(username);
String randomStr = RandomUtil.randomString(RandomUtil.BASE_CHAR, 5);
if (null != existsUser || !ReUtil.isMatch(RegexConstants.USERNAME, username)) {
username = randomStr + IdUtil.fastSimpleUUID();
}
user = new UserDO();
user.setUsername(username);
user.setGender(GenderEnum.valueOf(authUser.getGender().name()));
user.setAvatar(authUser.getAvatar());
user.setStatus(DisEnableStatusEnum.ENABLE);
userService.save(user);
Long userId = user.getId();
RoleDO role = roleService.getByCode(SysConstants.SUPER_ROLE_CODE);
userRoleService.assignRolesToUser(Collections.singletonList(role.getId()), userId);
userSocial = new UserSocialDO();
userSocial.setUserId(userId);
userSocial.setSource(source);
userSocial.setOpenId(openId);
} else {
user = BeanUtil.copyProperties(userService.getById(userSocial.getUserId()), UserDO.class);
}
// 检查用户状态
super.checkUserStatus(user);
userSocial.setMetaJson(JSONUtil.toJsonStr(authUser));
userSocial.setLastLoginTime(LocalDateTime.now());
userSocialService.saveOrUpdate(userSocial);
// 执行认证
String token = super.authenticate(user, client);
return LoginResp.builder().token(token).build();
// 第三方登录已禁用
throw new BadRequestException("第三方登录功能已禁用");
}
@Override
@@ -112,18 +41,4 @@ public class SocialLoginHandler extends AbstractLoginHandler<SocialLoginReq> {
return AuthTypeEnum.SOCIAL;
}
/**
* 获取 AuthRequest
*
* @param source 平台名称
* @return AuthRequest
*/
private AuthRequest getAuthRequest(String source) {
try {
return authRequestFactory.get(source);
} catch (Exception e) {
throw new BadRequestException("暂不支持 [%s] 平台账号登录".formatted(source));
}
}
}
}

View File

@@ -0,0 +1,16 @@
package top.wms.admin.fullWorkOrder.mapper;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.fullWorkOrder.model.entity.FullWorkOrderInfoDO;
/**
* 整箱领取记录 Mapper
*
* @author zc
* @since 2026/03/24 09:36
*/
@Repository
public interface FullWorkOrderInfoMapper extends BaseMapper<FullWorkOrderInfoDO> {
}

View File

@@ -0,0 +1,28 @@
package top.wms.admin.fullWorkOrder.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.fullWorkOrder.model.entity.FullWorkOrderDO;
import top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp;
import java.util.List;
/**
* 整箱领取记录 Mapper
*
* @author zc
* @since 2026/03/24 09:36
*/
@Repository
public interface FullWorkOrderMapper extends BaseMapper<FullWorkOrderDO> {
IPage<FullWorkOrderResp> selectFullWorkOrderPage(@Param("page") Page<Object> objectPage,
@Param(Constants.WRAPPER) QueryWrapper<FullWorkOrderDO> queryWrapper);
List<FullWorkOrderResp> selectFullWorkOrderExport(@Param(Constants.WRAPPER) QueryWrapper<FullWorkOrderDO> queryWrapper);
}

View File

@@ -0,0 +1,58 @@
package top.wms.admin.fullWorkOrder.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.wms.admin.common.model.entity.BaseDO;
import java.io.Serial;
/**
* 整箱领取记录实体
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@TableName("sys_full_work_order")
public class FullWorkOrderDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标题
*/
private String title;
/**
* 任务工单号
*/
private String orderNo;
/**
* 物料编码
*/
private String materialCode;
/**
* 图片地址
*/
private String imgUrl;
/**
* 批次号
*/
private String batch;
/**
* 标记号
*/
private String mark;
/**
* 数量
*/
private Integer count;
}

View File

@@ -0,0 +1,30 @@
package top.wms.admin.fullWorkOrder.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 整箱领取记录实体
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@TableName("sys_full_work_order_info")
public class FullWorkOrderInfoDO {
@Serial
private static final long serialVersionUID = 1L;
private Long fullWorkOrderId;
private BigDecimal weight;
/**
* 图片地址
*/
private String imgUrl;
}

View File

@@ -0,0 +1,64 @@
package top.wms.admin.fullWorkOrder.model.query;
import cn.hutool.core.date.DatePattern;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 整箱领取记录查询条件
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@Schema(description = "整箱领取记录查询条件")
public class FullWorkOrderQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
private String orderNo;
/**
* 物料编码
*/
@Schema(description = "物料编码")
private String materialCode;
/**
* 物料名称
*/
@Schema(description = "物料名称")
private String materialName;
/**
* 批次号
*/
@Schema(description = "批次号")
private String batch;
/**
* 创建人
*/
@Schema(description = "创建人")
private Long createUser;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
private List<Date> createTime;
}

View File

@@ -0,0 +1,48 @@
package top.wms.admin.fullWorkOrder.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 创建或修改整箱领取记录参数
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@Schema(description = "创建或修改整箱领取记录参数")
public class FullWorkOrderInfoReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 重量
*/
@Schema(description = "重量")
@NotNull(message = "重量不能为空")
private BigDecimal weight;
/**
* 整箱领取记录ID
*/
@Schema(description = "整箱领取记录ID")
@NotNull(message = "整箱领取记录ID不能为空")
private Long fullWorkOrderId;
/**
* 图片地址
*/
@Schema(description = "图片地址")
@NotBlank(message = "图片未采集到")
@Length(max = 255, message = "图片地址长度不能超过 {max} 个字符")
private String imgUrl;
}

View File

@@ -0,0 +1,76 @@
package top.wms.admin.fullWorkOrder.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 创建或修改整箱领取记录参数
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@Schema(description = "创建或修改整箱领取记录参数")
public class FullWorkOrderReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标题
*/
@Schema(description = "标题")
private String title;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
private String orderNo;
/**
* 物料编码
*/
@Schema(description = "物料编码")
@NotBlank(message = "物料编码不能为空")
@Length(max = 255, message = "物料编码长度不能超过 {max} 个字符")
private String materialCode;
/**
* 图片地址
*/
@Schema(description = "图片地址")
@NotBlank(message = "图片未采集到")
@Length(max = 255, message = "图片地址长度不能超过 {max} 个字符")
private String imgUrl;
/**
* 批次号
*/
@Schema(description = "批次号")
@NotBlank(message = "批次号不能为空")
private String batch;
/**
* 标记号
*/
@Schema(description = "标记号")
@NotBlank(message = "标记号不能为空")
private String mark;
/**
* 数量
*/
@Schema(description = "数量")
@NotNull(message = "数量不能为空")
private Integer count;
}

View File

@@ -0,0 +1,56 @@
package top.wms.admin.fullWorkOrder.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.wms.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 整箱领取记录详情信息
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "整箱领取记录详情信息")
public class FullWorkOrderDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标题
*/
@Schema(description = "标题")
@ExcelProperty(value = "标题")
private String title;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
@ExcelProperty(value = "任务工单号")
private String orderNo;
/**
* 物料编码
*/
@Schema(description = "物料编码")
@ExcelProperty(value = "物料编码")
private String materialCode;
/**
* 图片地址
*/
@Schema(description = "图片地址")
@ExcelProperty(value = "图片地址")
private String imgUrl;
}

View File

@@ -0,0 +1,82 @@
package top.wms.admin.fullWorkOrder.model.resp;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.wms.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 整箱领取记录信息
*
* @author zc
* @since 2026/03/24 09:36
*/
@Data
@Schema(description = "整箱领取记录信息")
public class FullWorkOrderResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标题
*/
@Schema(description = "标题")
@ExcelProperty(value = "标题", order = 1)
private String title;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
@ExcelProperty(value = "任务工单号", order = 2)
private String orderNo;
/**
* 物料编码
*/
@Schema(description = "物料编码")
@ExcelProperty(value = "物料编码", order = 4)
private String materialCode;
/**
* 物料名称
*/
@Schema(description = "物料名称")
@ExcelProperty(value = "物料名称", order = 3)
private String materialName;
/**
* 图片地址
*/
@Schema(description = "图片地址")
@ExcelProperty(value = "图片地址", order = 5)
private String imgUrl;
/**
* 批次号
*/
@Schema(description = "批次号")
@ExcelProperty(value = "批次号", order = 6)
private String batch;
/**
* 标记号
*/
@Schema(description = "标记号")
@ExcelProperty(value = "标记号", order = 7)
private String mark;
/**
* 标记号
*/
@Schema(description = "标记号")
@ExcelProperty(value = "数量", order = 8)
private Integer count;
}

View File

@@ -0,0 +1,23 @@
package top.wms.admin.fullWorkOrder.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.wms.admin.fullWorkOrder.model.entity.FullWorkOrderInfoDO;
import top.wms.admin.fullWorkOrder.model.query.FullWorkOrderQuery;
import top.wms.admin.fullWorkOrder.model.req.FullWorkOrderInfoReq;
import top.wms.admin.fullWorkOrder.model.req.FullWorkOrderReq;
import top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp;
import java.util.List;
/**
* 整箱领取记录业务接口
*
* @author zc
* @since 2026/03/24 09:36
*/
public interface FullWorkOrderService extends BaseService<FullWorkOrderResp, FullWorkOrderResp, FullWorkOrderQuery, FullWorkOrderReq> {
void saveFullWorkOrderDetail(List<FullWorkOrderInfoReq> infos);
List<FullWorkOrderInfoDO> getFullWorkOrderInfos(Long fullWorkOrderId);
}

View File

@@ -0,0 +1,99 @@
package top.wms.admin.fullWorkOrder.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.file.excel.util.ExcelUtils;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.fullWorkOrder.mapper.FullWorkOrderInfoMapper;
import top.wms.admin.fullWorkOrder.mapper.FullWorkOrderMapper;
import top.wms.admin.fullWorkOrder.model.entity.FullWorkOrderDO;
import top.wms.admin.fullWorkOrder.model.entity.FullWorkOrderInfoDO;
import top.wms.admin.fullWorkOrder.model.query.FullWorkOrderQuery;
import top.wms.admin.fullWorkOrder.model.req.FullWorkOrderInfoReq;
import top.wms.admin.fullWorkOrder.model.req.FullWorkOrderReq;
import top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp;
import top.wms.admin.fullWorkOrder.service.FullWorkOrderService;
import java.util.Date;
import java.util.List;
/**
* 整箱领取记录业务实现
*
* @author zc
* @since 2026/03/24 09:36
*/
@Service
@RequiredArgsConstructor
public class FullWorkOrderServiceImpl extends BaseServiceImpl<FullWorkOrderMapper, FullWorkOrderDO, FullWorkOrderResp, FullWorkOrderResp, FullWorkOrderQuery, FullWorkOrderReq> implements FullWorkOrderService {
private final FullWorkOrderInfoMapper fullWorkOrderInfoMapper;
@Override
public PageResp<FullWorkOrderResp> page(FullWorkOrderQuery query, PageQuery pageQuery) {
QueryWrapper<FullWorkOrderDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialCode()), "f.material_code", query.getMaterialCode());
queryWrapper.eq(StrUtil.isNotBlank(query.getOrderNo()), "f.order_no", query.getOrderNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "f.batch", query.getBatch());
queryWrapper.eq(StrUtil.isNotBlank(query.getMaterialName()), "m.material_name", query.getMaterialName());
queryWrapper.between(CollUtil.isNotEmpty(query.getCreateTime()), "f.create_time", CollUtil.getFirst(query
.getCreateTime()), CollUtil.getLast(query.getCreateTime()));
this.sort(queryWrapper, pageQuery);
IPage<FullWorkOrderResp> page = baseMapper.selectFullWorkOrderPage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}
@Override
public void beforeAdd(FullWorkOrderReq req) {
String timestamp = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String randomNum = String.format("%06d", (int)(Math.random() * 1000000));
req.setOrderNo(timestamp + randomNum);
String title = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_PATTERN) + "-" + UserContextHolder
.getUsername();
req.setTitle(title);
}
@Override
public void export(FullWorkOrderQuery query, SortQuery sortQuery, HttpServletResponse response) {
QueryWrapper<FullWorkOrderDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialCode()), "f.material_code", query.getMaterialCode());
queryWrapper.eq(StrUtil.isNotBlank(query.getOrderNo()), "f.order_no", query.getOrderNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "f.batch", query.getBatch());
queryWrapper.eq(StrUtil.isNotBlank(query.getMaterialName()), "m.material_name", query.getMaterialName());
queryWrapper.between(CollUtil.isNotEmpty(query.getCreateTime()), "f.create_time", CollUtil.getFirst(query
.getCreateTime()), CollUtil.getLast(query.getCreateTime()));
List<FullWorkOrderResp> list = baseMapper.selectFullWorkOrderExport(queryWrapper);
ExcelUtils.export(list, "整箱领取导出记录", FullWorkOrderResp.class, response);
}
@Override
public void saveFullWorkOrderDetail(List<FullWorkOrderInfoReq> infos) {
List<FullWorkOrderInfoDO> fullWorkOrderInfoDOS = BeanUtil.copyToList(infos, FullWorkOrderInfoDO.class);
fullWorkOrderInfoMapper.insertBatch(fullWorkOrderInfoDOS);
}
@Override
public List<FullWorkOrderInfoDO> getFullWorkOrderInfos(Long fullWorkOrderId) {
List<FullWorkOrderInfoDO> fullWorkOrderInfoDOS = fullWorkOrderInfoMapper
.selectList(new QueryWrapper<FullWorkOrderInfoDO>().eq("full_work_order_id", fullWorkOrderId));
return fullWorkOrderInfoDOS;
}
}

View File

@@ -0,0 +1,326 @@
package top.wms.admin.light;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.wms.admin.common.enums.CommandTypeEnum;
import java.nio.charset.StandardCharsets;
/**
* DPA6024V-2T-1.0 数字控制器 Java 控制类
* 支持通过RS232串口控制LED光源的亮度0-255级
*/
@Slf4j
@Component
public class LightService {
private SerialPortHandler serialHandler;
private final Object connectLock = new Object();
/**
* 连接控制器
* 前端页面进入"称重管理"页面时调用
*/
public boolean connect() {
synchronized (connectLock) {
try {
// 先关闭旧的连接(如果存在)
if (serialHandler != null) {
log.info("关闭旧的串口连接");
serialHandler.close();
serialHandler = null;
}
// 使用COM1串口
String portName = "COM1";
// 默认波特率9600
int baudRate = 9600;
serialHandler = new SerialPortHandler(portName, baudRate);
boolean connected = serialHandler.open();
if (connected) {
log.info("数字控制器连接成功");
} else {
log.error("数字控制器连接失败");
}
return connected;
} catch (Exception e) {
log.error("连接失败: {}", e.getMessage());
// 确保异常情况下serialHandler为null
serialHandler = null;
return false;
}
}
}
/**
* 断开连接
* 前端页面离开"称重管理"页面时调用
*/
public void disconnect() {
synchronized (connectLock) {
if (serialHandler != null) {
log.info("正在断开数字控制器连接");
try {
serialHandler.close();
} catch (Exception e) {
log.error("关闭串口失败: {}", e.getMessage());
} finally {
serialHandler = null;
log.info("数字控制器连接已断开");
}
}
}
}
/**
* 检查是否已连接
*
* @return 已连接返回true否则返回false
*/
public boolean isConnected() {
synchronized (connectLock) {
return serialHandler != null && serialHandler.isOpen();
}
}
/**
* 检查连接状态并尝试重连
*
* @return 重连成功返回true否则返回false
*/
public boolean checkAndReconnect() {
synchronized (connectLock) {
if (isConnected()) {
return true;
}
log.warn("串口连接异常,尝试重新连接");
return connect();
}
}
/**
* 重新连接控制器
*
* @return 重连成功返回true否则返回false
*/
public boolean reconnect() {
synchronized (connectLock) {
if (serialHandler != null) {
try {
serialHandler.close();
} catch (Exception e) {
log.error("关闭串口失败: {}", e.getMessage());
} finally {
serialHandler = null;
}
}
return connect();
}
}
/**
* 打开指定通道
*
* @param channel 通道号 (1-2)
*/
public boolean turnOn(int channel) {
return sendCommand(CommandTypeEnum.ON, channel, 0);
}
/**
* 关闭指定通道
*
* @param channel 通道号 (1-2)
*/
public boolean turnOff(int channel) {
return sendCommand(CommandTypeEnum.OFF, channel, 0);
}
/**
* 设置通道亮度
*
* @param channel 通道号 (1-2)
* @param brightness 亮度等级 (0-255)
*/
public boolean setBrightness(int channel, int brightness) {
if (brightness < 0 || brightness > 255) {
log.error("亮度等级必须在0-255之间");
return false;
}
log.info("设置通道 {} 亮度为: {}", channel, brightness);
return sendCommand(CommandTypeEnum.SET_BRIGHTNESS, channel, brightness);
}
/**
* 读取通道亮度
*
* @param channel 通道号 (1-2)
* @return 亮度等级 (0-255),失败返回-1
*/
public int getBrightness(int channel) {
int brightness = sendReadCommand(channel);
if (brightness != -1) {
log.info("通道 {} 当前亮度: {}", channel, brightness);
}
return brightness;
}
/**
* 发送命令到控制器
*/
private boolean sendCommand(CommandTypeEnum type, int channel, int brightness) {
if (!checkAndReconnect()) {
log.error("控制器未连接");
return false;
}
byte[] command = buildCommand(type, channel, brightness);
// 发送命令
serialHandler.sendData(command);
// 等待响应对于ON/OFF/SET命令返回'$'表示成功)
if (type != CommandTypeEnum.READ) {
String response = serialHandler.receiveResponse(100);
boolean success = response != null && response.contains("$");
if (success) {
log.debug("命令执行成功");
} else {
log.error("命令执行失败,响应: {}", response);
}
return success;
}
return true;
}
/**
* 发送读取命令并获取返回值
*/
private int sendReadCommand(int channel) {
if (!checkAndReconnect()) {
log.error("控制器未连接");
return -1;
}
byte[] command = buildCommand(CommandTypeEnum.READ, channel, 0);
// 发送命令
serialHandler.sendData(command);
// 接收响应(返回格式同发送格式,包含亮度数据)
String response = serialHandler.receiveResponse(200);
if (response == null || !response.startsWith("$")) {
log.error("读取亮度失败,响应: {}", response);
return -1;
}
// 解析返回的亮度值
int brightness = parseBrightnessFromResponse(response);
if (brightness == -1) {
log.error("解析亮度值失败,响应: {}", response);
}
return brightness;
}
/**
* 构建命令字节数组
* 格式: 特征字(1) + 指令字(1) + 通道字(1) + 数据(3) + 异或校验字(2)
* 所有字节采用ASCII码
* 数据部分3个ASCII字符表示亮度的十六进制值高位在前
*/
private byte[] buildCommand(CommandTypeEnum type, int channel, int brightness) {
// 特征字: $ (ASCII 36)
char featureChar = '$';
// 指令字
char cmdChar = type.getCommandCode();
// 通道字 (1-2)
char channelChar = (char)(channel + '0');
// 数据: 3字节亮度的十六进制表示高位在前
// 亮度值范围0-255需要转换为3个十六进制ASCII字符
String dataStr;
if (type == CommandTypeEnum.SET_BRIGHTNESS) {
// 将亮度值转换为十六进制字符串并确保是3位
dataStr = String.format("%03X", brightness);
} else {
// ON/OFF/READ命令的数据字段固定为 "000"
dataStr = "000";
}
// 构建命令字符串用于计算校验和
String cmdStr = featureChar + String.valueOf(cmdChar) + channelChar + dataStr;
byte[] cmdBytes = cmdStr.getBytes(StandardCharsets.US_ASCII);
// 计算异或校验和
byte xorSum = 0;
for (int i = 0; i < cmdBytes.length; i++) {
xorSum ^= cmdBytes[i];
}
// 将校验和转换为2位十六进制ASCII码
String xorStr = String.format("%02X", xorSum & 0xFF).toUpperCase();
// 最终命令字符串
String fullCommand = cmdStr + xorStr;
log.debug("构建命令: {} (亮度: {} -> 十六进制: {})", fullCommand, brightness, dataStr);
return fullCommand.getBytes(StandardCharsets.US_ASCII);
}
/**
* 从响应字符串解析亮度值
* 响应格式: $ + 指令字 + 通道字 + 数据(3) + 校验字(2)
* 例如: "$43038E" 表示通道3亮度56
*/
private int parseBrightnessFromResponse(String response) {
if (response == null || response.length() < 8) {
return -1;
}
try {
// 提取数据部分第4-6个字符索引3-5
String dataStr = response.substring(3, 6);
return Integer.parseInt(dataStr);
} catch (NumberFormatException e) {
log.error("解析亮度值失败: {}", e.getMessage());
return -1;
}
}
/**
* 便捷方法:设置所有通道亮度
*
* @param brightness 亮度等级 (0-255)
*/
public void setAllBrightness(int brightness) {
log.info("设置所有通道亮度为: {}", brightness);
for (int channel = 1; channel <= 2; channel++) {
setBrightness(channel, brightness);
}
}
/**
* 便捷方法:关闭所有通道
*/
public void turnAllOff() {
log.info("关闭所有通道");
for (int channel = 1; channel <= 2; channel++) {
turnOff(channel);
}
}
/**
* 便捷方法:打开所有通道
*/
public void turnAllOn() {
log.info("打开所有通道");
for (int channel = 1; channel <= 2; channel++) {
turnOn(channel);
}
}
}

View File

@@ -0,0 +1,288 @@
package top.wms.admin.light;
import com.fazecast.jSerialComm.SerialPort;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
/**
* 串口通信处理器
* 负责与DPA6024V-2T-1.0数字控制器进行串口通信
*/
@Slf4j
public class SerialPortHandler {
private InputStream inputStream;
private OutputStream outputStream;
private SerialPort serialPort;
/**
* 串口名称
*/
@Getter
private final String portName;
/**
* 波特率
*/
@Getter
private final int baudRate;
/**
* 构造函数
*
* @param portName 串口名称,如 "COM3" (Windows) 或 "/dev/ttyUSB0" (Linux)
* @param baudRate 波特率
*/
public SerialPortHandler(String portName, int baudRate) {
this.portName = portName;
this.baudRate = baudRate;
}
/**
* 打开串口连接
*
* @return 打开成功返回true失败返回false
*/
public boolean open() {
try {
// 获取所有可用的串口
SerialPort[] ports = SerialPort.getCommPorts();
log.info("可用串口列表: {}", java.util.Arrays.stream(ports).map(SerialPort::getSystemPortName).toList());
// 查找指定的串口
SerialPort targetPort = null;
for (SerialPort port : ports) {
String portName = port.getSystemPortName();
log.debug("检查串口: {}", portName);
if (portName.equals(this.portName)) {
targetPort = port;
log.info("找到目标串口: {}", portName);
break;
}
}
if (targetPort == null) {
log.error("未找到串口: {}", this.portName);
return false;
}
// 配置串口参数
targetPort.setBaudRate(baudRate);
targetPort.setNumDataBits(8);
targetPort.setNumStopBits(1);
targetPort.setParity(SerialPort.NO_PARITY);
targetPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
// 打开串口
boolean opened = targetPort.openPort();
if (opened) {
// 设置读取超时时间(毫秒)
targetPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0);
this.serialPort = targetPort;
this.inputStream = targetPort.getInputStream();
this.outputStream = targetPort.getOutputStream();
log.info("串口 {} 打开成功,波特率: {}", this.portName, baudRate);
} else {
log.error("串口 {} 打开失败", this.portName);
}
return opened;
} catch (Exception e) {
log.error("打开串口 {} 失败: {}", this.portName, e.getMessage());
return false;
}
}
/**
* 检查串口是否已打开
*
* @return 已打开返回true否则返回false
*/
public boolean isOpen() {
return serialPort != null && serialPort.isOpen();
}
/**
* 发送数据
*
* @param data 要发送的字节数组
*/
public void sendData(byte[] data) {
try {
// 验证串口是否正确打开
if (serialPort == null || !serialPort.isOpen()) {
log.error("串口 {} 未打开,无法发送数据", portName);
return;
}
if (outputStream == null) {
log.error("串口 {} 输出流未初始化", portName);
return;
}
outputStream.write(data);
outputStream.flush();
log.info("串口 {} 发送数据: {}", portName, new String(data));
} catch (IOException e) {
log.error("串口 {} 发送数据失败: {}", portName, e.getMessage());
}
}
/**
* 发送字符串数据
*
* @param data 要发送的字符串
*/
public void sendData(String data) {
try {
log.info("串口 {} 准备发送字符串: {}", portName, data);
sendData(data.getBytes(StandardCharsets.US_ASCII));
} catch (Exception e) {
log.error("串口 {} 发送字符串数据失败: {}", portName, e.getMessage());
}
}
/**
* 接收响应数据
*
* @param timeoutMs 超时时间(毫秒)
* @return 接收到的字符串超时或无数据返回null
*/
public String receiveResponse(int timeoutMs) {
try {
// 验证串口是否正确打开
if (serialPort == null || !serialPort.isOpen()) {
log.error("串口 {} 未打开,无法接收数据", portName);
return null;
}
// 验证输入流是否初始化
if (inputStream == null) {
log.error("串口 {} 输入流未初始化", portName);
return null;
}
// 等待数据到达
long startTime = System.currentTimeMillis();
while (inputStream.available() == 0 && (System.currentTimeMillis() - startTime) < timeoutMs) {
Thread.sleep(10);
}
// 读取数据
byte[] buffer = new byte[1024];
int available = inputStream.available();
if (available > 0) {
int len = inputStream.read(buffer, 0, Math.min(available, buffer.length));
String response = new String(buffer, 0, len);
log.info("串口 {} 接收数据: {}", portName, response);
return response;
}
} catch (Exception e) {
log.error("串口 {} 接收响应失败: {}", portName, e.getMessage());
}
return null;
}
/**
* 接收指定长度的响应数据
*
* @param length 期望接收的字节数
* @param timeoutMs 超时时间(毫秒)
* @return 接收到的字符串超时或无数据返回null
*/
public String receiveResponse(int length, int timeoutMs) {
try {
// 验证串口是否正确打开
if (serialPort == null || !serialPort.isOpen()) {
log.error("串口 {} 未打开,无法接收数据", portName);
return null;
}
// 验证输入流是否初始化
if (inputStream == null) {
log.error("串口 {} 输入流未初始化", portName);
return null;
}
byte[] buffer = new byte[length];
int bytesRead = 0;
long startTime = System.currentTimeMillis();
while (bytesRead < length && (System.currentTimeMillis() - startTime) < timeoutMs) {
if (inputStream.available() > 0) {
int read = inputStream.read(buffer, bytesRead, length - bytesRead);
if (read > 0) {
bytesRead += read;
}
}
Thread.sleep(10);
}
if (bytesRead > 0) {
String response = new String(buffer, 0, bytesRead);
log.info("串口 {} 接收数据: {}", portName, response);
return response;
}
} catch (Exception e) {
log.error("串口 {} 接收响应失败: {}", portName, e.getMessage());
}
return null;
}
/**
* 清空输入缓冲区
*/
public void clearInputBuffer() {
try {
if (inputStream != null && inputStream.available() > 0) {
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
log.debug("清空输入缓冲区: {}", new String(buffer));
}
} catch (IOException e) {
log.error("清空输入缓冲区失败: {}", e.getMessage());
}
}
/**
* 清空输出缓冲区
*/
public void clearOutputBuffer() {
try {
if (outputStream != null) {
outputStream.flush();
}
} catch (IOException e) {
log.error("清空输出缓冲区失败: {}", e.getMessage());
}
}
/**
* 关闭串口连接
*/
public void close() {
try {
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
if (outputStream != null) {
outputStream.close();
outputStream = null;
}
if (serialPort != null && serialPort.isOpen()) {
serialPort.closePort();
log.info("串口 {} 已关闭", portName);
}
} catch (IOException e) {
log.error("关闭串口失败: {}", e.getMessage());
} finally {
serialPort = null;
}
}
}

View File

@@ -1,8 +1,16 @@
package top.wms.admin.material.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.material.model.entity.MaterialInfoDO;
import org.springframework.stereotype.Repository;
import top.wms.admin.material.model.resp.MaterialInfoResp;
import java.util.List;
/**
* 物料信息 Mapper
@@ -12,5 +20,16 @@ import org.springframework.stereotype.Repository;
*/
@Repository
public interface MaterialInfoMapper extends BaseMapper<MaterialInfoDO> {
int updateByName(@Param("list") List<MaterialInfoDO> list);
int updateByCode(@Param("list") List<MaterialInfoDO> list);
IPage<MaterialInfoResp> selectMaterialInfoPage(@Param("page") Page<Object> objectPage,
@Param(Constants.WRAPPER) QueryWrapper<MaterialInfoDO> queryWrapper);
List<MaterialInfoResp> selectMaterialInfoExport(@Param(Constants.WRAPPER) QueryWrapper<MaterialInfoDO> queryWrapper);
void updateBatchNull();
int updateBatchByCode(List<MaterialInfoDO> materialInfoDOS);
}

View File

@@ -38,12 +38,43 @@ public class MaterialInfoDO extends BaseDO {
private BigDecimal unitWeight;
/*
物料规格
物料直径
*/
private String materialSpec;
private Double materialSpec;
/**
* 物料照片地址
*/
private String photoUrl;
/**
* 物料品类ID
*/
private Long materialTypeId;
/**
* 物料流程
*/
private String materialProcess;
/**
* 灯光等级
*/
private Integer lightLevel;
/**
* 颜色
*/
private String color;
/**
* 批次
*/
private String batch;
/**
* 标记
*/
private String mark;
}

View File

@@ -36,4 +36,18 @@ public class MaterialInfoQuery implements Serializable {
@Schema(description = "物料编码")
@Query(type = QueryType.EQ)
private String encoding;
/**
* 批次
*/
@Schema(description = "批次")
@Query(type = QueryType.EQ)
private String batch;
/**
* 标记
*/
@Schema(description = "标记")
@Query(type = QueryType.EQ)
private String mark;
}

View File

@@ -0,0 +1,24 @@
package top.wms.admin.material.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
@Schema(description = "批次导入参数")
public class BatchImportReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话KEY
*/
@Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
@NotBlank(message = "导入已过期,请重新上传")
private String importKey;
}

View File

@@ -0,0 +1,33 @@
package top.wms.admin.material.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
@Data
@Schema(description = "物料信息导入行数据")
public class BatchImportRowReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 物料名称
*/
@Schema(description = "物料编码")
@NotBlank(message = "物料编码不能为空")
@Length(max = 255, message = "物料编码长度不能超过 {max} 个字符")
private String encoding;
/**
* 批次
*/
@Schema(description = "批次")
@NotBlank(message = "批次不能为空")
@Length(max = 255, message = "批次长度不能超过 {max} 个字符")
private String batch;
}

View File

@@ -0,0 +1,72 @@
package top.wms.admin.material.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@Schema(description = "物料信息导入行数据")
public class MaterialImportRowReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 物料名称
*/
@Schema(description = "物料名称")
@NotBlank(message = "物料名称不能为空")
@Length(max = 255, message = "物料名称长度不能超过 {max} 个字符")
private String materialName;
/**
* 物料编码
*/
@Schema(description = "物料编码")
@NotBlank(message = "物料编码不能为空")
@Length(max = 255, message = "物料编码长度不能超过 {max} 个字符")
private String encoding;
/**
* 物料单位重量(g)
*/
@Schema(description = "物料单位重量(g)")
@NotNull(message = "物料单位重量不能为空")
private BigDecimal unitWeight;
/**
* 灯光等级
*/
@Schema(description = "灯光等级")
private String lightLevelName;
/*
* 物料直径
* */
@Schema(description = "物料直径")
private Double materialSpec;
/**
* 物料品类名称
*/
@Schema(description = "物料品类名称")
private String typeName;
/**
* 流程名称
*/
@Schema(description = "物料流程")
private String materialProcess;
/**
* 颜色
*/
@Schema(description = "颜色")
private String color;
}

View File

@@ -0,0 +1,47 @@
package top.wms.admin.material.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import top.wms.admin.common.enums.DisEnableStatusEnum;
import top.wms.admin.system.enums.ImportPolicyEnum;
import java.io.Serial;
import java.io.Serializable;
@Data
@Schema(description = "物料导入参数")
public class MaterialInfoImportReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话KEY
*/
@Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
@NotBlank(message = "导入已过期,请重新上传")
private String importKey;
/**
* 物料名称重复策略
*/
@Schema(description = "物料名称重复策略", example = "1")
@NotNull(message = "物料名称重复策略不能为空")
private ImportPolicyEnum duplicateName;
/**
* 重复编码策略
*/
@Schema(description = "重复编码策略", example = "1")
@NotNull(message = "重复编码策略不能为空")
private ImportPolicyEnum duplicateCode;
/**
* 默认状态
*/
@Schema(description = "默认状态", example = "1")
private DisEnableStatusEnum defaultStatus;
}

View File

@@ -44,13 +44,14 @@ public class MaterialInfoReq implements Serializable {
* 物料单位重量(g)
*/
@Schema(description = "物料单位重量(g)")
@NotNull(message = "物料单位重量不能为空")
private Double unitWeight;
/*
* 物料规格
* */
@Schema(description = "物料规格")
private String materialSpec;
* 物料直径
* */
@Schema(description = "物料直径")
private Double materialSpec;
/**
* 物料照片地址
@@ -58,4 +59,43 @@ public class MaterialInfoReq implements Serializable {
@Schema(description = "物料照片地址")
@Length(max = 255, message = "物料照片地址长度不能超过 {max} 个字符")
private String photoUrl;
/**
* 物料品类ID
*/
@Schema(description = "物料品类ID")
@NotNull(message = "物料品类ID不能为空")
private Long materialTypeId;
/**
* 流程ID
*/
@Schema(description = "流程ID")
@NotBlank(message = "流程ID不能为空")
private String materialProcess;
/**
* 灯光等级
*/
@Schema(description = "灯光等级")
private Integer lightLevel;
/**
* 颜色
*/
@Schema(description = "颜色")
private String color;
/**
* 批次
*/
@Schema(description = "批次")
private String batch;
/**
* 标记
*/
@Schema(description = "标记")
private String mark;
}

View File

@@ -0,0 +1,55 @@
package top.wms.admin.material.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 物料导入解析结果
*
* @author kils
* @since 2024/6/18 14:37
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "用户导入解析结果")
public class MaterialImportParseResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话 Key
*/
@Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
private String importKey;
/**
* 总计行数
*/
@Schema(description = "总计行数", example = "100")
private Integer totalRows;
/**
* 有效行数
*/
@Schema(description = "有效行数", example = "100")
private Integer validRows;
/**
* 重复物料名行数
*/
@Schema(description = "重复物料名行数", example = "100")
private Integer duplicateNameRows;
/**
* 重复物料编码行数
*/
@Schema(description = "重复物料编码行数", example = "100")
private Integer duplicateCodeRows;
}

View File

@@ -0,0 +1,34 @@
package top.wms.admin.material.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Schema(description = "物料信息导入结果")
@AllArgsConstructor
@NoArgsConstructor
public class MaterialInfoImportResp implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总计行数
*/
@Schema(description = "总计行数", example = "100")
private Integer totalRows;
/**
* 新增行数
*/
@Schema(description = "新增行数", example = "100")
private Integer insertRows;
/**
* 修改行数
*/
@Schema(description = "修改行数", example = "100")
private Integer updateRows;
}

View File

@@ -2,13 +2,12 @@ package top.wms.admin.material.model.resp;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.wms.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 物料信息信息
@@ -27,28 +26,28 @@ public class MaterialInfoResp extends BaseDetailResp {
* 物料名称
*/
@Schema(description = "物料名称")
@ExcelProperty(value = "物料名称")
@ExcelProperty(value = "物料名称", order = 1)
private String materialName;
/**
* 物料编码
*/
@Schema(description = "物料编码")
@ExcelProperty(value = "物料编码")
@ExcelProperty(value = "物料编码", order = 2)
private String encoding;
/**
* 物料单位重量(g)
*/
@Schema(description = "物料单位重量(g)")
@ExcelProperty(value = "物料单位重量(g)")
@ExcelProperty(value = "物料单位重量(g)", order = 3)
private Double unitWeight;
/**
* 物料规格
* 物料直径
*/
@Schema(description = "物料规格")
@ExcelProperty(value = "物料规格")
@Schema(description = "物料直径")
@ExcelProperty(value = "物料直径", order = 5)
private Double materialSpec;
/**
@@ -57,4 +56,68 @@ public class MaterialInfoResp extends BaseDetailResp {
@Schema(description = "物料照片地址")
@ExcelIgnore
private String photoUrl;
/**
* 物料品类名称
*/
@Schema(description = "物料品类名称")
@ExcelProperty(value = "物料品类")
private String typeName;
/**
* 物料流程
*/
@Schema(description = "物料流程")
@ExcelProperty(value = "物料流程")
private String materialProcess;
/**
* 物料品类ID
*/
@Schema(description = "物料品类ID")
@ExcelIgnore
private Long materialTypeId;
/**
* 品类下行浮动范围(%
*/
@Schema(description = "品类下行浮动范围(%")
@ExcelIgnore
private BigDecimal downFloatRatio;
/**
* 品类上行浮动范围(%
*/
@Schema(description = "品类上行浮动范围(%")
@ExcelIgnore
private BigDecimal upFloatRatio;
/**
* 颜色灯光值
*/
@Schema(description = "灯光等级")
@ExcelProperty(value = "灯光等级", order = 4)
private Integer lightLevel;
/**
* 颜色
*/
@Schema(description = "颜色")
@ExcelProperty(value = "颜色", order = 6)
private String color;
/**
* 批次
*/
@Schema(description = "批次")
@ExcelProperty(value = "批次", order = 7)
private String batch;
/**
* 标记
*/
@Schema(description = "标记")
@ExcelProperty(value = "标记", order = 8)
private String mark;
}

View File

@@ -1,11 +1,18 @@
package top.wms.admin.material.service;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.extension.crud.service.BaseService;
import top.wms.admin.material.model.entity.MaterialInfoDO;
import top.wms.admin.material.model.query.MaterialInfoQuery;
import top.wms.admin.material.model.req.BatchImportReq;
import top.wms.admin.material.model.req.MaterialInfoImportReq;
import top.wms.admin.material.model.req.MaterialInfoReq;
import top.wms.admin.material.model.resp.MaterialImportParseResp;
import top.wms.admin.material.model.resp.MaterialInfoImportResp;
import top.wms.admin.material.model.resp.MaterialInfoResp;
import java.io.IOException;
/**
* 物料信息业务接口
*
@@ -13,5 +20,78 @@ import top.wms.admin.material.model.resp.MaterialInfoResp;
* @since 2026/02/27 14:19
*/
public interface MaterialInfoService extends BaseService<MaterialInfoResp, MaterialInfoResp, MaterialInfoQuery, MaterialInfoReq> {
public MaterialInfoDO getMaterialInfoByCode(String code);
/**
* 根据编码查询物料信息
*
* @param code 物料编码
* @return 物料信息
*/
MaterialInfoResp getMaterialInfoByCode(String code);
/**
* 根据物料ID查询亮度
*
* @param materialId 物料ID
* @return 亮度
*/
Integer getBrightness(Long materialId);
/**
* 下载导入模板
*
* @param response 响应对象
* @throws IOException /
*/
void downloadImportTemplate(HttpServletResponse response) throws IOException;
/**
* 解析导入数据
*
* @param file 导入文件
* @return 解析结果
*/
MaterialImportParseResp parseImport(MultipartFile file);
/**
* 导入数据
*
* @param req 导入信息
* @return 导入结果
*/
MaterialInfoImportResp importMaterial(MaterialInfoImportReq req);
/*
* 照片批量上传处理
* */
void uploadMaterialPhotos(MultipartFile file);
/*
* 称重时照片抓取
* */
String catchPhoto(MultipartFile file);
/**
* 下载批量导入模板
*
* @param response 响应对象
* @throws IOException /
*/
void downloadBatchImportTemplate(HttpServletResponse response) throws IOException;
/**
* 解析批量导入数据
*
* @param file 导入文件
* @return 解析结果
*/
MaterialImportParseResp parseBatchImport(MultipartFile file);
/**
* 导入批量数据
*
* @param req 导入信息
* @return 导入结果
*/
MaterialInfoImportResp importBatchMaterial(BatchImportReq req);
}

View File

@@ -1,20 +1,71 @@
package top.wms.admin.material.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.result.R;
import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.file.excel.util.ExcelUtils;
import top.continew.starter.web.util.FileUploadUtils;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.common.util.SecureUtils;
import top.wms.admin.material.mapper.MaterialInfoMapper;
import top.wms.admin.material.model.entity.MaterialInfoDO;
import top.wms.admin.material.model.query.MaterialInfoQuery;
import top.wms.admin.material.model.req.MaterialInfoReq;
import top.wms.admin.material.model.req.*;
import top.wms.admin.material.model.resp.MaterialImportParseResp;
import top.wms.admin.material.model.resp.MaterialInfoImportResp;
import top.wms.admin.material.model.resp.MaterialInfoResp;
import top.wms.admin.material.service.MaterialInfoService;
import top.wms.admin.materialType.mapper.MaterialTypeMapper;
import top.wms.admin.materialType.model.entity.MaterialTypeDO;
import top.wms.admin.system.service.FileService;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static top.wms.admin.system.enums.ImportPolicyEnum.*;
/**
* 物料信息业务实现
@@ -24,14 +75,511 @@ import top.wms.admin.material.service.MaterialInfoService;
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper, MaterialInfoDO, MaterialInfoResp, MaterialInfoResp, MaterialInfoQuery, MaterialInfoReq> implements MaterialInfoService {
private static final Set<String> IMAGE_EXTENSIONS = Set.of("jpg", "jpeg", "png", "gif", "bmp");
private final FileService fileService;
private final MaterialTypeMapper materialTypeMapper;
@Override
public MaterialInfoDO getMaterialInfoByCode(String code) {
if(StrUtil.isNotBlank(code)){
return baseMapper.lambdaQuery().eq(MaterialInfoDO::getEncoding, code).one();
}else{
return null;
public void beforeAdd(MaterialInfoReq materialInfoReq) {
MaterialInfoDO materialInfoDO = baseMapper.selectOne(new LambdaQueryWrapper<MaterialInfoDO>()
.eq(MaterialInfoDO::getEncoding, materialInfoReq.getEncoding()));
CheckUtils.throwIf(materialInfoDO != null, "物料编码已存在");
}
@Override
public void beforeUpdate(MaterialInfoReq materialInfoReq, Long id) {
MaterialInfoDO materialInfoDO = baseMapper.selectOne(new LambdaQueryWrapper<MaterialInfoDO>()
.eq(MaterialInfoDO::getEncoding, materialInfoReq.getEncoding())
.ne(MaterialInfoDO::getId, id));
CheckUtils.throwIf(materialInfoDO != null, "物料编码已存在");
}
@Override
public PageResp<MaterialInfoResp> page(MaterialInfoQuery query, PageQuery pageQuery) {
QueryWrapper<MaterialInfoDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialName()), "mi.material_name", query.getMaterialName());
queryWrapper.like(StrUtil.isNotBlank(query.getEncoding()), "mi.encoding", query.getEncoding());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "mi.batch", query.getBatch());
this.sort(queryWrapper, pageQuery);
IPage<MaterialInfoResp> page = baseMapper.selectMaterialInfoPage(new Page<>(pageQuery.getPage(), pageQuery
.getSize()), queryWrapper);
return PageResp.build(page);
}
@Override
public void export(MaterialInfoQuery query, SortQuery sortQuery, HttpServletResponse response) {
QueryWrapper<MaterialInfoDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialName()), "mi.material_name", query.getMaterialName());
queryWrapper.like(StrUtil.isNotBlank(query.getEncoding()), "mi.encoding", query.getEncoding());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "mi.batch", query.getBatch());
this.sort(queryWrapper, sortQuery);
List<MaterialInfoResp> list = baseMapper.selectMaterialInfoExport(queryWrapper);
ExcelUtils.export(list, "物料信息导出", MaterialInfoResp.class, response);
}
@Override
public MaterialInfoResp getMaterialInfoByCode(String code) {
MaterialInfoResp materialInfoResp = new MaterialInfoResp();
MaterialInfoDO materialInfoDO = baseMapper.lambdaQuery().eq(MaterialInfoDO::getEncoding, code).one();
CheckUtils.throwIf(materialInfoDO == null, "物料信息不存在");
MaterialTypeDO materialTypeDO = materialTypeMapper.selectById(materialInfoDO.getMaterialTypeId());
CheckUtils.throwIf(materialTypeDO == null, "该物料对应类型不存在");
BeanUtil.copyProperties(materialInfoDO, materialInfoResp);
materialInfoResp.setUpFloatRatio(materialTypeDO.getUpFloatRatio());
materialInfoResp.setDownFloatRatio(materialTypeDO.getDownFloatRatio());
return materialInfoResp;
}
@Override
public Integer getBrightness(Long materialId) {
MaterialInfoDO materialInfoDO = baseMapper.selectById(materialId);
CheckUtils.throwIf(materialInfoDO == null, "物料信息不存在");
return materialInfoDO.getLightLevel();
}
@Override
public void downloadImportTemplate(HttpServletResponse response) throws IOException {
try {
FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/materialInfo.xlsx"), "物料信息导入模板.xlsx");
} catch (Exception e) {
log.error("下载物料信息导入模板失败:", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setContentType(ContentType.JSON.toString());
response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载物料信息导入模板失败")));
}
}
}
@Override
public MaterialImportParseResp parseImport(MultipartFile file) {
MaterialImportParseResp materialImportResp = new MaterialImportParseResp();
List<MaterialImportRowReq> importRowList;
// 读取表格数据
try {
importRowList = EasyExcel.read(file.getInputStream())
.head(MaterialImportRowReq.class)
.sheet()
.headRowNumber(1)
.doReadSync();
} catch (Exception e) {
log.error("物料信息导入数据文件解析异常:", e);
throw new BusinessException("数据文件解析异常");
}
// 总计行数
materialImportResp.setTotalRows(importRowList.size());
CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误");
// 有效行数:过滤无效(同名物料)数据
List<MaterialImportRowReq> validRowList = this.filterImportData(importRowList);
materialImportResp.setValidRows(validRowList.size());
CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误");
// 检测表格内数据是否合法
Set<String> seenCode = new HashSet<>();
boolean hasDuplicateEncoding = validRowList.stream()
.map(MaterialImportRowReq::getEncoding)
.anyMatch(encoding -> encoding != null && !seenCode.add(encoding));
CheckUtils.throwIf(hasDuplicateEncoding, "存在重复物料编码,请检测数据");
// 查询重复物料名
materialImportResp
.setDuplicateNameRows(countExistByField(validRowList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName, false));
// 查询重复物料编码
materialImportResp
.setDuplicateCodeRows(countExistByField(validRowList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding, false));
// 设置导入会话并缓存数据有效期10分钟
String importKey = UUID.fastUUID().toString(true);
RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration
.ofMinutes(10));
materialImportResp.setImportKey(importKey);
return materialImportResp;
}
@Override
@Transactional(rollbackFor = Exception.class)
public MaterialInfoImportResp importMaterial(MaterialInfoImportReq req) {
// 校验导入会话是否过期
List<MaterialImportRowReq> importMaterialList;
try {
String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
importMaterialList = JSONUtil.toList(data, MaterialImportRowReq.class);
CheckUtils.throwIf(CollUtil.isEmpty(importMaterialList), "导入已过期,请重新上传");
} catch (Exception e) {
log.error("导入异常:", e);
throw new BusinessException("导入已过期,请重新上传");
}
// 已存在数据查询
List<String> existName = listExistByField(importMaterialList, MaterialImportRowReq::getMaterialName, MaterialInfoDO::getMaterialName);
List<String> existCode = listExistByField(importMaterialList, MaterialImportRowReq::getEncoding, MaterialInfoDO::getEncoding);
CheckUtils.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入");
//查询物料品类
List<String> collect = importMaterialList.stream().map(MaterialImportRowReq::getTypeName).distinct().toList();
Map<String, Long> materialTypeMap = new HashMap<>();
if (CollUtil.isNotEmpty(collect)) {
List<MaterialTypeDO> materialTypeList = materialTypeMapper
.selectList(new LambdaQueryWrapper<MaterialTypeDO>().in(MaterialTypeDO::getTypeName, collect));
materialTypeMap = materialTypeList.stream()
.collect(Collectors.toMap(MaterialTypeDO::getTypeName, MaterialTypeDO::getId, (k1, v1) -> v1));
}
// 批量操作数据库集合
List<MaterialInfoDO> insertList = new ArrayList<>();
List<MaterialInfoDO> updateByNameList = new ArrayList<>();
List<MaterialInfoDO> updateByCodeList = new ArrayList<>();
// ID生成器
for (MaterialImportRowReq row : importMaterialList) {
if (isSkipMaterialImport(req, row, existName, existCode)) {
continue;
}
MaterialInfoDO materialDO = BeanUtil.toBeanIgnoreError(row, MaterialInfoDO.class);
materialDO.setUnitWeight(NumberUtil.isValidNumber(row.getUnitWeight()) ? row.getUnitWeight() : null);
materialDO.setMaterialSpec(row.getMaterialSpec());
materialDO.setColor(row.getColor());
materialDO.setMaterialProcess(StrUtil.isNotBlank(row.getMaterialProcess())
? row.getMaterialProcess()
: null);
materialDO.setMaterialTypeId(materialTypeMap.get(row.getTypeName()));
materialDO.setLightLevel(StrUtil.isNotBlank(row.getLightLevelName())
? Integer.parseInt(row.getLightLevelName())
: null);
// 修改 or 新增
if (UPDATE.validate(req.getDuplicateName(), row.getMaterialName(), existName)) {
materialDO.setMaterialName(row.getMaterialName());
materialDO.setUpdateUser(UserContextHolder.getUserId());
updateByNameList.add(materialDO);
} else if (UPDATE.validate(req.getDuplicateCode(), row.getEncoding(), existCode)) {
materialDO.setEncoding(row.getEncoding());
materialDO.setUpdateUser(UserContextHolder.getUserId());
updateByCodeList.add(materialDO);
} else {
insertList.add(materialDO);
}
}
doImportMaterial(insertList, updateByNameList, updateByCodeList);
RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
int insertCount = insertList.size();
int updateByNameCount = updateByNameList.size();
int updateByCodeCount = updateByCodeList.size();
int totalUpdateCount = updateByNameCount + updateByCodeCount;
int totalHandleCount = insertCount + totalUpdateCount;
return new MaterialInfoImportResp(totalHandleCount, // 总处理数
insertCount, // 新增数
totalUpdateCount // 更新总数
);
}
/**
* 按指定数据集获取数据库已存在的数量
*
* @param materialRowList 导入的数据源
* @param rowField 导入数据的字段
* @param dbField 对比数据库的字段
* @return 存在的数量
*/
private int countExistByField(List<MaterialImportRowReq> materialRowList,
Function<MaterialImportRowReq, String> rowField,
SFunction<MaterialInfoDO, ?> dbField,
boolean fieldEncrypt) {
List<String> fieldValues = materialRowList.stream().map(rowField).filter(Objects::nonNull).toList();
if (fieldValues.isEmpty()) {
return 0;
}
return (int)this.count(Wrappers.<MaterialInfoDO>lambdaQuery()
.in(dbField, fieldEncrypt ? SecureUtils.encryptFieldByAes(fieldValues) : fieldValues));
}
/**
* 过滤无效的导入用户数据(批量导入不严格校验数据)
*
* @param importRowList 导入数据
*/
private List<MaterialImportRowReq> filterImportData(List<MaterialImportRowReq> importRowList) {
// 校验过滤
return importRowList.stream().filter(row -> ValidationUtil.validate(row).isEmpty()).toList();
}
/**
* 过滤无效的导入用户数据(批量导入不严格校验数据)
*
* @param importRowList 导入数据
*/
private List<BatchImportRowReq> filterImportData2(List<BatchImportRowReq> importRowList) {
// 校验过滤
return importRowList.stream().filter(row -> ValidationUtil.validate(row).isEmpty()).toList();
}
/**
* 按指定数据集获取数据库已存在内容
*
* @param materialRowList 导入的数据源
* @param rowField 导入数据的字段
* @param dbField 对比数据库的字段
* @return 存在的内容
*/
private List<String> listExistByField(List<MaterialImportRowReq> materialRowList,
Function<MaterialImportRowReq, String> rowField,
SFunction<MaterialInfoDO, String> dbField) {
List<String> fieldValues = materialRowList.stream().map(rowField).filter(Objects::nonNull).toList();
if (fieldValues.isEmpty()) {
return Collections.emptyList();
}
List<MaterialInfoDO> materialDOList = baseMapper.selectList(Wrappers.<MaterialInfoDO>lambdaQuery()
.in(dbField, fieldValues)
.select(dbField));
return materialDOList.stream().map(dbField).filter(Objects::nonNull).toList();
}
/**
* 判断是否退出导入
*
* @param req 导入参数
* @param list 导入数据
* @param existName 导入数据中已存在的物料名
* @param existCode 导入数据中已存在的物料编号
* @return 是否退出
*/
private boolean isExitImportMaterial(MaterialInfoImportReq req,
List<MaterialImportRowReq> list,
List<String> existName,
List<String> existCode) {
return list.stream()
.anyMatch(row -> EXIT.validate(req.getDuplicateName(), row.getMaterialName(), existName) || EXIT
.validate(req.getDuplicateCode(), row.getEncoding(), existCode));
}
/**
* 判断是否跳过导入
*
* @param req 导入参数
* @param row 导入数据
* @param existName 导入数据中已存在的物料名称
* @param existCode 导入数据中已存在的物料编号
* @return 是否跳过
*/
private boolean isSkipMaterialImport(MaterialInfoImportReq req,
MaterialImportRowReq row,
List<String> existName,
List<String> existCode) {
return SKIP.validate(req.getDuplicateName(), row.getMaterialName(), existName) || SKIP.validate(req
.getDuplicateCode(), row.getEncoding(), existCode);
}
/**
* 导入用户
*
* @param insertList 新增用户
* @param updateByNameList 修改用户
* @param updateByCodeList 用户角色关联
*/
private void doImportMaterial(List<MaterialInfoDO> insertList,
List<MaterialInfoDO> updateByNameList,
List<MaterialInfoDO> updateByCodeList) {
if (CollUtil.isNotEmpty(insertList)) {
baseMapper.insertBatch(insertList);
}
if (CollUtil.isNotEmpty(updateByNameList)) {
baseMapper.updateByName(updateByNameList);
}
if (CollUtil.isNotEmpty(updateByCodeList)) {
baseMapper.updateByCode(updateByCodeList);
}
}
@Override
public void uploadMaterialPhotos(MultipartFile zipFile) {
// 1. 初始化物料编码-照片地址Map
Map<String, String> codeUrlMap = new HashMap<>();
// 物料照片存储路径(自定义,比如按日期分目录)
String photoStoragePath = "material/";
try (ZipInputStream zipInputStream = new ZipInputStream(zipFile.getInputStream())) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
// 跳过目录、非图片文件
if (zipEntry.isDirectory() || !isImageFile(zipEntry.getName())) {
zipInputStream.closeEntry();
continue;
}
// 2. 提取物料编码(照片名 = 物料编码,去掉后缀)
String fileName = zipEntry.getName();
log.info("正在处理的照片:{}", fileName);
//去除windows或linux环境下 可能存在的多层级目录
if (fileName.contains("/")) {
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
}
if (fileName.contains("\\")) {
fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
}
String materialCode = fileName.substring(0, fileName.lastIndexOf("."));
// 3. 读取ZIP中的图片为BufferedImage
BufferedImage image = ImageIO.read(zipInputStream);
if (ObjectUtil.isEmpty(image)) {
log.warn("无法读取图片: {}", fileName);
zipInputStream.closeEntry();
continue;
}
// 4. 将BufferedImage转为字节流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String imageExt = getImageExtension(fileName);
ImageIO.write(image, imageExt, baos);
byte[] imageBytes = baos.toByteArray();
// 5. 转换为MultipartFile使用MockMultipartFile
MultipartFile singleImageFile = new MockMultipartFile("file", fileName, "image/" + (imageExt
.equals("jpg") ? "jpeg" : imageExt), imageBytes);
// 6. 调用upload方法上传图片
FileInfo fileInfo = fileService.upload(singleImageFile, photoStoragePath, null, false, false);
// 7. 将物料编码和图片URL存入Map
codeUrlMap.put(materialCode, fileInfo.getUrl());
zipInputStream.closeEntry();
}
setPhotosByCode(codeUrlMap);
} catch (Exception e) {
throw new BusinessException("照片批量导入失败:" + e.getMessage());
}
}
private boolean isImageFile(String fileName) {
if (fileName == null || !fileName.contains("."))
return false;
String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
return IMAGE_EXTENSIONS.contains(ext);
}
private String getImageExtension(String fileName) {
String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
return "jpeg".equals(ext) ? "jpg" : ext;
}
private void setPhotosByCode(Map<String, String> codeUrlMap) {
CheckUtils.throwIfEmpty(codeUrlMap, "照片为空,请重新上传");
List<MaterialInfoDO> existList = baseMapper.selectList(Wrappers.<MaterialInfoDO>lambdaQuery()
.in(MaterialInfoDO::getEncoding, codeUrlMap.keySet()));
if (existList.isEmpty()) {
log.warn("未找到任何匹配的物料编码");
return;
}
List<MaterialInfoDO> updateList = existList.stream().map(exist -> {
MaterialInfoDO updateDO = new MaterialInfoDO();
updateDO.setId(exist.getId());
updateDO.setPhotoUrl(codeUrlMap.get(exist.getEncoding()));
return updateDO;
}).collect(Collectors.toList());
if (!updateList.isEmpty()) {
baseMapper.updateBatchById(updateList);
log.info("成功更新 {} 个物料的照片", updateList.size());
}
//记录未找到的物料编码(可选,方便排查问题)
Set<String> existCodes = existList.stream().map(MaterialInfoDO::getEncoding).collect(Collectors.toSet());
codeUrlMap.keySet()
.stream()
.filter(code -> !existCodes.contains(code))
.forEach(code -> log.warn("物料编码 [{}] 不存在,照片更新失败", code));
}
@Override
public String catchPhoto(MultipartFile file) {
String photoStoragePath = "catch" + DateUtil.today() + "/";
FileInfo fileInfo = fileService.upload(file, photoStoragePath, null, true, true);
CheckUtils.throwIfNull(fileInfo, "照片上传失败");
return fileInfo.getUrl();
}
@Override
public void downloadBatchImportTemplate(HttpServletResponse response) throws IOException {
try {
FileUploadUtils.download(response, ResourceUtil.getStream("templates/import/batch.xlsx"), "批次导入模板.xlsx");
} catch (Exception e) {
log.error("下载批次导入模板失败:", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setContentType(ContentType.JSON.toString());
response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载批次导入模板失败")));
}
}
@Override
public MaterialImportParseResp parseBatchImport(MultipartFile file) {
MaterialImportParseResp materialImportResp = new MaterialImportParseResp();
List<BatchImportRowReq> importRowList;
// 读取表格数据
try {
importRowList = EasyExcel.read(file.getInputStream())
.head(BatchImportRowReq.class)
.sheet()
.headRowNumber(1)
.doReadSync();
} catch (Exception e) {
log.error("物料信息导入数据文件解析异常:", e);
throw new BusinessException("数据文件解析异常");
}
// 总计行数
materialImportResp.setTotalRows(importRowList.size());
CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误");
// 有效行数:过滤无效数据
List<BatchImportRowReq> validRowList = this.filterImportData2(importRowList);
materialImportResp.setValidRows(validRowList.size());
CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误");
Set<String> batchSet = validRowList.stream().map(BatchImportRowReq::getBatch).collect(Collectors.toSet());
if (batchSet.size() > 1) {
throw new BusinessException("存在不同批次数据,请检查批次信息");
}
validRowList = validRowList.stream().distinct().collect(Collectors.toList());
// 设置导入会话并缓存数据有效期10分钟
String importKey = UUID.fastUUID().toString(true);
RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration
.ofMinutes(10));
materialImportResp.setImportKey(importKey);
return materialImportResp;
}
@Override
public MaterialInfoImportResp importBatchMaterial(BatchImportReq req) {
// 校验导入会话是否过期
List<BatchImportRowReq> batchImportRowReqs;
try {
String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
batchImportRowReqs = JSONUtil.toList(data, BatchImportRowReq.class);
CheckUtils.throwIf(CollUtil.isEmpty(batchImportRowReqs), "导入已过期,请重新上传");
} catch (Exception e) {
log.error("导入异常:", e);
throw new BusinessException("导入已过期,请重新上传");
}
// 转换为MaterialInfoDO列表
List<MaterialInfoDO> materialInfoDOS = batchImportRowReqs.stream().map(row -> {
MaterialInfoDO materialInfoDO = new MaterialInfoDO();
materialInfoDO.setEncoding(row.getEncoding());
materialInfoDO.setBatch(row.getBatch());
materialInfoDO.setUpdateUser(UserContextHolder.getUserId());
return materialInfoDO;
}).toList();
// 批量操作数据库集合
try {
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().blockAttack(true).build());
baseMapper.updateBatchNull();
} finally {
InterceptorIgnoreHelper.clearIgnoreStrategy();
}
int totalUpdateCount = baseMapper.updateBatchByCode(materialInfoDOS);
RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
return new MaterialInfoImportResp(materialInfoDOS.size(), 0, totalUpdateCount);
}
}

View File

@@ -0,0 +1,24 @@
package top.wms.admin.materialProcess.mapper;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.materialProcess.model.entity.MaterialProcessDO;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 海康物料流程 Mapper
*
* @author zc
* @since 2026/03/16 17:22
*/
@Repository
public interface MaterialProcessMapper extends BaseMapper<MaterialProcessDO> {
void updateByName(@Param("list") List<MaterialProcessDO> updateByNameList);
void updateByCode(@Param("list") List<MaterialProcessDO> updateByCodeList);
String getCodeByMaterial(@Param("materialCode") String materialCode);
}

View File

@@ -0,0 +1,19 @@
package top.wms.admin.materialProcess.mapstruct;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.wms.admin.materialProcess.model.entity.MaterialProcessDO;
import java.util.List;
@Mapper(componentModel = "spring")
public interface MaterialProcessConvert {
@Mapping(source = "id", target = "value")
@Mapping(source = "processName", target = "label")
LabelValueResp labelValue(MaterialProcessDO materialProcessDO);
List<LabelValueResp> labelValueList(List<MaterialProcessDO> materialProcessDOList);
}

View File

@@ -0,0 +1,33 @@
package top.wms.admin.materialProcess.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.wms.admin.common.model.entity.BaseDO;
import java.io.Serial;
/**
* 海康物料流程实体
*
* @author zc
* @since 2026/03/16 17:22
*/
@Data
@TableName("sys_material_process")
public class MaterialProcessDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程名称
*/
private String processName;
/**
* 流程编码
*/
private String processCode;
}

View File

@@ -0,0 +1,40 @@
package top.wms.admin.materialProcess.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 海康物料流程查询条件
*
* @author zc
* @since 2026/03/16 17:22
*/
@Data
@Schema(description = "海康物料流程查询条件")
public class MaterialProcessQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程名称
*/
@Schema(description = "流程名称")
@Query(type = QueryType.EQ)
private String processName;
/**
* 流程编码
*/
@Schema(description = "流程编码")
@Query(type = QueryType.EQ)
private String processCode;
}

View File

@@ -0,0 +1,36 @@
package top.wms.admin.materialProcess.model.req;
import lombok.Data;
import top.wms.admin.system.enums.ImportPolicyEnum;
import java.io.Serial;
import java.io.Serializable;
/**
* 物料流程导入请求
*
* @author zc
* @since 2026/03/20
*/
@Data
public class MaterialProcessImportReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入key
*/
private String importKey;
/**
* 重复流程名称处理策略: 0-跳过 1-更新 2-退出
*/
private ImportPolicyEnum duplicateName;
/**
* 重复流程编码处理策略: 0-跳过 1-更新 2-退出
*/
private ImportPolicyEnum duplicateCode;
}

View File

@@ -0,0 +1,33 @@
package top.wms.admin.materialProcess.model.req;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 物料流程导入行数据
*
* @author zc
* @since 2026/03/20
*/
@Data
public class MaterialProcessImportRowReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程名称
*/
@NotBlank(message = "流程名称不能为空")
private String processName;
/**
* 流程编码
*/
@NotBlank(message = "流程编码不能为空")
private String processCode;
}

View File

@@ -0,0 +1,43 @@
package top.wms.admin.materialProcess.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 创建或修改海康物料流程参数
*
* @author zc
* @since 2026/03/16 17:22
*/
@Data
@Schema(description = "创建或修改海康物料流程参数")
public class MaterialProcessReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程名称
*/
@Schema(description = "流程名称")
@NotBlank(message = "流程名称不能为空")
@Length(max = 255, message = "流程名称长度不能超过 {max} 个字符")
private String processName;
/**
* 流程编码
*/
@Schema(description = "流程编码")
@NotBlank(message = "流程编码不能为空")
@Length(max = 255, message = "流程编码长度不能超过 {max} 个字符")
private String processCode;
}

View File

@@ -0,0 +1,42 @@
package top.wms.admin.materialProcess.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import top.wms.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 海康物料流程详情信息
*
* @author zc
* @since 2026/03/16 17:22
*/
@Data
@ExcelIgnoreUnannotated
@Schema(description = "海康物料流程详情信息")
public class MaterialProcessDetailResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程名称
*/
@Schema(description = "流程名称")
@ExcelProperty(value = "流程名称")
private String processName;
/**
* 流程编码
*/
@Schema(description = "流程编码")
@ExcelProperty(value = "流程编码")
private String processCode;
}

View File

@@ -0,0 +1,49 @@
package top.wms.admin.materialProcess.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "用户导入解析结果")
public class MaterialProcessImportParseResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话 Key
*/
@Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
private String importKey;
/**
* 总计行数
*/
@Schema(description = "总计行数", example = "100")
private Integer totalRows;
/**
* 有效行数
*/
@Schema(description = "有效行数", example = "100")
private Integer validRows;
/**
* 重复名称行数
*/
@Schema(description = "重复名称行数", example = "100")
private Integer duplicateNameRows;
/**
* 重复编码行数
*/
@Schema(description = "重复编码行数", example = "100")
private Integer duplicateCodeRows;
}

View File

@@ -0,0 +1,41 @@
package top.wms.admin.materialProcess.model.resp;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 物料流程导入响应
*
* @author zc
* @since 2026/03/20
*/
@Data
public class MaterialProcessImportResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 总处理数
*/
private Integer totalHandleCount;
/**
* 新增数
*/
private Integer insertCount;
/**
* 更新数
*/
private Integer updateCount;
public MaterialProcessImportResp(Integer totalHandleCount, Integer insertCount, Integer updateCount) {
this.totalHandleCount = totalHandleCount;
this.insertCount = insertCount;
this.updateCount = updateCount;
}
}

View File

@@ -0,0 +1,39 @@
package top.wms.admin.materialProcess.model.resp;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.wms.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
/**
* 海康物料流程信息
*
* @author zc
* @since 2026/03/16 17:22
*/
@Data
@Schema(description = "海康物料流程信息")
public class MaterialProcessResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程名称
*/
@Schema(description = "流程名称")
@ExcelProperty(value = "流程名称", order = 1)
private String processName;
/**
* 流程编码
*/
@Schema(description = "流程编码")
@ExcelProperty(value = "流程编码", order = 2)
private String processCode;
}

View File

@@ -0,0 +1,53 @@
package top.wms.admin.materialProcess.service;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.wms.admin.materialProcess.model.query.MaterialProcessQuery;
import top.wms.admin.materialProcess.model.req.MaterialProcessImportReq;
import top.wms.admin.materialProcess.model.req.MaterialProcessReq;
import top.wms.admin.materialProcess.model.resp.MaterialProcessImportParseResp;
import top.wms.admin.materialProcess.model.resp.MaterialProcessImportResp;
import top.wms.admin.materialProcess.model.resp.MaterialProcessResp;
import java.util.List;
/**
* 海康物料流程业务接口
*
* @author zc
* @since 2026/03/16 17:22
*/
public interface MaterialProcessService extends BaseService<MaterialProcessResp, MaterialProcessResp, MaterialProcessQuery, MaterialProcessReq> {
/**
* 获取流程下拉列表
*
* @return 流程下拉列表
*/
List<LabelValueResp> getSelectList();
/**
* 下载导入模板
*
* @param response 响应对象
*/
void downloadImportTemplate(HttpServletResponse response) throws Exception;
/**
* 解析导入文件
*
* @param file 导入文件
* @return 解析响应
*/
MaterialProcessImportParseResp parseImport(MultipartFile file);
/**
* 导入物料流程
*
* @param req 导入请求
* @return 导入响应
*/
MaterialProcessImportResp importMaterialProcess(MaterialProcessImportReq req);
}

View File

@@ -0,0 +1,314 @@
package top.wms.admin.materialProcess.service.impl;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import net.dreamlu.mica.core.result.R;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.file.excel.util.ExcelUtils;
import top.continew.starter.web.util.FileUploadUtils;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.materialProcess.mapper.MaterialProcessMapper;
import top.wms.admin.materialProcess.mapstruct.MaterialProcessConvert;
import top.wms.admin.materialProcess.model.entity.MaterialProcessDO;
import top.wms.admin.materialProcess.model.query.MaterialProcessQuery;
import top.wms.admin.materialProcess.model.req.MaterialProcessImportReq;
import top.wms.admin.materialProcess.model.req.MaterialProcessImportRowReq;
import top.wms.admin.materialProcess.model.req.MaterialProcessReq;
import top.wms.admin.materialProcess.model.resp.MaterialProcessImportParseResp;
import top.wms.admin.materialProcess.model.resp.MaterialProcessImportResp;
import top.wms.admin.materialProcess.model.resp.MaterialProcessResp;
import top.wms.admin.materialProcess.service.MaterialProcessService;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import static top.wms.admin.system.enums.ImportPolicyEnum.*;
/**
* 海康物料流程业务实现
*
* @author zc
* @since 2026/03/16 17:22
*/
@Service
@RequiredArgsConstructor
public class MaterialProcessServiceImpl extends BaseServiceImpl<MaterialProcessMapper, MaterialProcessDO, MaterialProcessResp, MaterialProcessResp, MaterialProcessQuery, MaterialProcessReq> implements MaterialProcessService {
private final MaterialProcessConvert materialProcessConvert;
@Override
public void beforeAdd(MaterialProcessReq req) {
// 校验流程名称是否重复
MaterialProcessDO processDO = baseMapper.selectOne(Wrappers.lambdaQuery(MaterialProcessDO.class)
.eq(MaterialProcessDO::getProcessName, req.getProcessName()));
CheckUtils.throwIf(ObjectUtil.isNotEmpty(processDO), "流程名称已存在");
// 校验流程编码是否重复
processDO = baseMapper.selectOne(Wrappers.lambdaQuery(MaterialProcessDO.class)
.eq(MaterialProcessDO::getProcessCode, req.getProcessCode()));
CheckUtils.throwIf(ObjectUtil.isNotEmpty(processDO), "流程编码已存在");
}
@Override
public void beforeUpdate(MaterialProcessReq req, Long id) {
// 校验流程名称是否重复
MaterialProcessDO processDO = baseMapper.selectOne(Wrappers.lambdaQuery(MaterialProcessDO.class)
.eq(MaterialProcessDO::getProcessName, req.getProcessName())
.ne(MaterialProcessDO::getId, id));
CheckUtils.throwIf(ObjectUtil.isNotEmpty(processDO), "流程名称已存在");
// 校验流程编码是否重复
processDO = baseMapper.selectOne(Wrappers.lambdaQuery(MaterialProcessDO.class)
.eq(MaterialProcessDO::getProcessCode, req.getProcessCode())
.ne(MaterialProcessDO::getId, id));
CheckUtils.throwIf(ObjectUtil.isNotEmpty(processDO), "流程编码已存在");
}
@Override
public void export(MaterialProcessQuery query, SortQuery sortQuery, HttpServletResponse response) {
List<MaterialProcessResp> list = super.list(query, sortQuery, this.getDetailClass());
list.forEach(super::fill);
ExcelUtils.export(list, "物料流程导出", MaterialProcessResp.class, response);
}
@Override
public List<LabelValueResp> getSelectList() {
List<MaterialProcessDO> materialProcessDOS = baseMapper.selectList(new QueryWrapper<>());
return materialProcessConvert.labelValueList(materialProcessDOS);
}
@Override
public void downloadImportTemplate(HttpServletResponse response) throws Exception {
try {
FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/materialProcess.xlsx"), "物料流程导入模板.xlsx");
} catch (Exception e) {
log.error("下载用户导入模板失败:", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setContentType(ContentType.JSON.toString());
response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载用户导入模板失败")));
}
}
@Override
public MaterialProcessImportParseResp parseImport(MultipartFile file) {
MaterialProcessImportParseResp importResp = new MaterialProcessImportParseResp();
List<MaterialProcessImportRowReq> importRowList;
// 读取表格数据
try {
importRowList = com.alibaba.excel.EasyExcel.read(file.getInputStream())
.head(MaterialProcessImportRowReq.class)
.sheet()
.headRowNumber(1)
.doReadSync();
} catch (Exception e) {
log.error("物料流程导入数据文件解析异常:", e);
throw new BusinessException("数据文件解析异常");
}
// 总计行数
importResp.setTotalRows(importRowList.size());
CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误");
// 有效行数:过滤无效数据
List<MaterialProcessImportRowReq> validRowList = this.filterImportData(importRowList);
importResp.setValidRows(validRowList.size());
CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误");
// 检测表格内数据是否合法
Set<String> seenCode = new HashSet<>();
boolean hasDuplicateCode = validRowList.stream()
.map(MaterialProcessImportRowReq::getProcessCode)
.anyMatch(code -> code != null && !seenCode.add(code));
CheckUtils.throwIf(hasDuplicateCode, "存在重复流程编码,请检测数据");
// 查询重复流程名称
importResp
.setDuplicateNameRows(countExistByField(validRowList, MaterialProcessImportRowReq::getProcessName, MaterialProcessDO::getProcessName));
// 查询重复流程编码
importResp
.setDuplicateCodeRows(countExistByField(validRowList, MaterialProcessImportRowReq::getProcessCode, MaterialProcessDO::getProcessCode));
// 设置导入会话并缓存数据有效期10分钟
String importKey = java.util.UUID.randomUUID().toString().replace("-", "");
RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration
.ofMinutes(10));
importResp.setImportKey(importKey);
return importResp;
}
@Override
@Transactional(rollbackFor = Exception.class)
public MaterialProcessImportResp importMaterialProcess(MaterialProcessImportReq req) {
// 校验导入会话是否过期
List<MaterialProcessImportRowReq> importMaterialList;
try {
String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
importMaterialList = JSONUtil.toList(data, MaterialProcessImportRowReq.class);
CheckUtils.throwIf(ObjectUtil.isEmpty(importMaterialList), "导入已过期,请重新上传");
} catch (Exception e) {
log.error("导入异常:", e);
throw new BusinessException("导入已过期,请重新上传");
}
// 已存在数据查询
List<String> existName = listExistByField(importMaterialList, MaterialProcessImportRowReq::getProcessName, MaterialProcessDO::getProcessName);
List<String> existCode = listExistByField(importMaterialList, MaterialProcessImportRowReq::getProcessCode, MaterialProcessDO::getProcessCode);
CheckUtils.throwIf(isExitImportMaterial(req, importMaterialList, existName, existCode), "数据不符合导入策略,已退出导入");
// 批量操作数据库集合
List<MaterialProcessDO> insertList = new ArrayList<>();
List<MaterialProcessDO> updateByNameList = new ArrayList<>();
List<MaterialProcessDO> updateByCodeList = new ArrayList<>();
// 处理导入数据
for (MaterialProcessImportRowReq row : importMaterialList) {
if (isSkipMaterialImport(req, row, existName, existCode)) {
continue;
}
MaterialProcessDO materialProcessDO = new MaterialProcessDO();
materialProcessDO.setProcessName(row.getProcessName());
materialProcessDO.setProcessCode(row.getProcessCode());
// 修改 or 新增
if (UPDATE.validate(req.getDuplicateName(), row.getProcessName(), existName)) {
materialProcessDO.setProcessCode(row.getProcessCode());
materialProcessDO.setUpdateUser(UserContextHolder.getUserId());
updateByNameList.add(materialProcessDO);
} else if (UPDATE.validate(req.getDuplicateCode(), row.getProcessCode(), existCode)) {
materialProcessDO.setProcessCode(row.getProcessCode());
materialProcessDO.setUpdateUser(UserContextHolder.getUserId());
updateByCodeList.add(materialProcessDO);
} else {
insertList.add(materialProcessDO);
}
}
doImportMaterial(insertList, updateByNameList, updateByCodeList);
RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
int insertCount = insertList.size();
int updateByNameCount = updateByNameList.size();
int updateByCodeCount = updateByCodeList.size();
int totalUpdateCount = updateByNameCount + updateByCodeCount;
int totalHandleCount = insertCount + totalUpdateCount;
return new MaterialProcessImportResp(totalHandleCount, // 总处理数
insertCount, // 新增数
totalUpdateCount // 更新总数
);
}
/**
* 按指定数据集获取数据库已存在的数量
*
* @param materialRowList 导入的数据源
* @param rowField 导入数据的字段
* @param dbField 对比数据库的字段
* @return 存在的数量
*/
private int countExistByField(List<MaterialProcessImportRowReq> materialRowList,
Function<MaterialProcessImportRowReq, String> rowField,
SFunction<MaterialProcessDO, ?> dbField) {
List<String> fieldValues = materialRowList.stream().map(rowField).filter(Objects::nonNull).toList();
if (fieldValues.isEmpty()) {
return 0;
}
return (int)this.count(Wrappers.<MaterialProcessDO>lambdaQuery().in(dbField, fieldValues));
}
/**
* 过滤无效的导入数据
*
* @param importRowList 导入数据
*/
private List<MaterialProcessImportRowReq> filterImportData(List<MaterialProcessImportRowReq> importRowList) {
// 校验过滤
return importRowList.stream().filter(row -> ValidationUtil.validate(row).isEmpty()).toList();
}
/**
* 按指定数据集获取数据库已存在内容
*
* @param materialRowList 导入的数据源
* @param rowField 导入数据的字段
* @param dbField 对比数据库的字段
* @return 存在的内容
*/
private List<String> listExistByField(List<MaterialProcessImportRowReq> materialRowList,
Function<MaterialProcessImportRowReq, String> rowField,
SFunction<MaterialProcessDO, String> dbField) {
List<String> fieldValues = materialRowList.stream().map(rowField).filter(Objects::nonNull).toList();
if (fieldValues.isEmpty()) {
return Collections.emptyList();
}
List<MaterialProcessDO> materialProcessDOList = baseMapper.selectList(Wrappers.<MaterialProcessDO>lambdaQuery()
.in(dbField, fieldValues)
.select(dbField));
return materialProcessDOList.stream().map(dbField).filter(Objects::nonNull).toList();
}
/**
* 判断是否退出导入
*
* @param req 导入参数
* @param list 导入数据
* @param existName 导入数据中已存在的流程名称
* @param existCode 导入数据中已存在的流程编码
* @return 是否退出
*/
private boolean isExitImportMaterial(MaterialProcessImportReq req,
List<MaterialProcessImportRowReq> list,
List<String> existName,
List<String> existCode) {
return list.stream()
.anyMatch(row -> EXIT.validate(req.getDuplicateName(), row.getProcessName(), existName) || EXIT.validate(req
.getDuplicateCode(), row.getProcessCode(), existCode));
}
/**
* 判断是否跳过导入
*
* @param req 导入参数
* @param row 导入数据
* @param existName 导入数据中已存在的流程名称
* @param existCode 导入数据中已存在的流程编码
* @return 是否跳过
*/
private boolean isSkipMaterialImport(MaterialProcessImportReq req,
MaterialProcessImportRowReq row,
List<String> existName,
List<String> existCode) {
return SKIP.validate(req.getDuplicateName(), row.getProcessName(), existName) || SKIP.validate(req
.getDuplicateCode(), row.getProcessCode(), existCode);
}
/**
* 导入物料流程
*
* @param insertList 新增流程
* @param updateByNameList 根据名称修改流程
* @param updateByCodeList 根据编码修改流程
*/
private void doImportMaterial(List<MaterialProcessDO> insertList,
List<MaterialProcessDO> updateByNameList,
List<MaterialProcessDO> updateByCodeList) {
if (insertList != null && !insertList.isEmpty()) {
baseMapper.insertBatch(insertList);
}
if (updateByNameList != null && !updateByNameList.isEmpty()) {
baseMapper.updateByName(updateByNameList);
}
if (updateByCodeList != null && !updateByCodeList.isEmpty()) {
baseMapper.updateByCode(updateByCodeList);
}
}
}

View File

@@ -0,0 +1,16 @@
package top.wms.admin.materialType.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.materialType.model.entity.MaterialTypeDO;
import org.springframework.stereotype.Repository;
/**
* 物料品类 Mapper
*
* @author zc
* @since 2026/03/16 11:18
*/
@Repository
public interface MaterialTypeMapper extends BaseMapper<MaterialTypeDO> {
}

View File

@@ -0,0 +1,18 @@
package top.wms.admin.materialType.mapstruct;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.wms.admin.materialType.model.entity.MaterialTypeDO;
import java.util.List;
@Mapper(componentModel = "spring")
public interface MaterialTypeConvert {
@Mapping(source = "id", target = "value")
@Mapping(source = "typeName", target = "label")
LabelValueResp labelValue(MaterialTypeDO materialTypeDO);
List<LabelValueResp> labelValueList(List<MaterialTypeDO> materialTypeDOList);
}

View File

@@ -0,0 +1,39 @@
package top.wms.admin.materialType.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.wms.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 物料品类实体
*
* @author zc
* @since 2026/03/16 11:18
*/
@Data
@TableName("sys_material_type")
public class MaterialTypeDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 品类名称
*/
private String typeName;
/**
* 品类下行浮动范围(%
*/
private BigDecimal downFloatRatio;
/**
* 品类上行浮动范围(%
*/
private BigDecimal upFloatRatio;
}

View File

@@ -0,0 +1,34 @@
package top.wms.admin.materialType.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
/**
* 物料品类查询条件
*
* @author zc
* @since 2026/03/16 11:18
*/
@Data
@Schema(description = "物料品类查询条件")
public class MaterialTypeQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 品类名称
*/
@Schema(description = "品类名称")
@Query(type = QueryType.EQ)
private String typeName;
}

View File

@@ -0,0 +1,32 @@
package top.wms.admin.materialType.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import top.wms.admin.system.enums.ImportPolicyEnum;
import java.io.Serial;
import java.io.Serializable;
@Data
@Schema(description = "物料品类导入参数")
public class MaterialTypeImportReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话KEY
*/
@Schema(description = "导入会话KEY", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
@NotBlank(message = "导入已过期,请重新上传")
private String importKey;
/**
* 品类名称重复策略
*/
@Schema(description = "品类名称重复策略", example = "1")
@NotNull(message = "品类名称重复策略不能为空")
private ImportPolicyEnum duplicateTypeName;
}

View File

@@ -0,0 +1,40 @@
package top.wms.admin.materialType.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@Schema(description = "物料品类导入行数据")
public class MaterialTypeImportRowReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 品类名称
*/
@Schema(description = "品类名称")
@NotBlank(message = "品类名称不能为空")
@Length(max = 255, message = "品类名称长度不能超过 {max} 个字符")
private String typeName;
/**
* 品类下行浮动范围(%
*/
@Schema(description = "品类下行浮动范围(%")
@NotNull(message = "品类下行浮动范围不能为空")
private BigDecimal downFloatRatio;
/**
* 品类上行浮动范围(%
*/
@Schema(description = "品类上行浮动范围(%")
@NotNull(message = "品类上行浮动范围不能为空")
private BigDecimal upFloatRatio;
}

View File

@@ -0,0 +1,50 @@
package top.wms.admin.materialType.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import java.math.BigDecimal;
/**
* 创建或修改物料品类参数
*
* @author zc
* @since 2026/03/16 11:18
*/
@Data
@Schema(description = "创建或修改物料品类参数")
public class MaterialTypeReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 品类名称
*/
@Schema(description = "品类名称")
@NotBlank(message = "品类名称不能为空")
@Length(max = 255, message = "品类名称长度不能超过 {max} 个字符")
private String typeName;
/**
* 品类浮动比
*/
@Schema(description = "品类浮动比")
@NotNull(message = "品类下行浮动范围不能为空")
private BigDecimal downFloatRatio;
/**
* 品类上行浮动范围(%
*/
@Schema(description = "品类上行浮动范围(%")
@NotNull(message = "品类上行浮动范围不能为空")
private BigDecimal upFloatRatio;
}

View File

@@ -0,0 +1,43 @@
package top.wms.admin.materialType.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "物料品类导入解析结果")
public class MaterialTypeImportParseResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 导入会话 Key
*/
@Schema(description = "导入会话Key", example = "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed")
private String importKey;
/**
* 总计行数
*/
@Schema(description = "总计行数", example = "100")
private Integer totalRows;
/**
* 有效行数
*/
@Schema(description = "有效行数", example = "100")
private Integer validRows;
/**
* 重复品类名称行数
*/
@Schema(description = "重复品类名称行数", example = "100")
private Integer duplicateNameRows;
}

View File

@@ -0,0 +1,34 @@
package top.wms.admin.materialType.model.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Schema(description = "物料品类导入结果")
@AllArgsConstructor
@NoArgsConstructor
public class MaterialTypeImportResp implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总计行数
*/
@Schema(description = "总计行数", example = "100")
private Integer totalRows;
/**
* 新增行数
*/
@Schema(description = "新增行数", example = "100")
private Integer insertRows;
/**
* 修改行数
*/
@Schema(description = "修改行数", example = "100")
private Integer updateRows;
}

View File

@@ -0,0 +1,43 @@
package top.wms.admin.materialType.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.wms.admin.common.model.resp.BaseDetailResp;
import java.io.Serial;
import java.time.*;
import java.math.BigDecimal;
/**
* 物料品类信息
*
* @author zc
* @since 2026/03/16 11:18
*/
@Data
@Schema(description = "物料品类信息")
public class MaterialTypeResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 品类名称
*/
@Schema(description = "品类名称")
private String typeName;
/**
* 品类下行浮动范围(%
*/
@Schema(description = "品类下行浮动范围(%")
private BigDecimal downFloatRatio;
/**
* 品类上行浮动范围(%
*/
@Schema(description = "品类上行浮动范围(%")
private BigDecimal upFloatRatio;
}

View File

@@ -0,0 +1,32 @@
package top.wms.admin.materialType.service;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.wms.admin.materialType.model.query.MaterialTypeQuery;
import top.wms.admin.materialType.model.req.MaterialTypeImportReq;
import top.wms.admin.materialType.model.req.MaterialTypeReq;
import top.wms.admin.materialType.model.resp.MaterialTypeImportParseResp;
import top.wms.admin.materialType.model.resp.MaterialTypeImportResp;
import top.wms.admin.materialType.model.resp.MaterialTypeResp;
import java.io.IOException;
import java.util.List;
/**
* 物料品类业务接口
*
* @author zc
* @since 2026/03/16 11:18
*/
public interface MaterialTypeService extends BaseService<MaterialTypeResp, MaterialTypeResp, MaterialTypeQuery, MaterialTypeReq> {
List<LabelValueResp> getSelectList();
void downloadImportTemplate(HttpServletResponse response) throws IOException;
MaterialTypeImportParseResp parseImport(MultipartFile file);
MaterialTypeImportResp importMaterial(MaterialTypeImportReq req);
}

View File

@@ -0,0 +1,211 @@
package top.wms.admin.materialType.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.validation.ValidationUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.result.R;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.validation.CheckUtils;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.continew.starter.web.util.FileUploadUtils;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.materialType.mapper.MaterialTypeMapper;
import top.wms.admin.materialType.mapstruct.MaterialTypeConvert;
import top.wms.admin.materialType.model.entity.MaterialTypeDO;
import top.wms.admin.materialType.model.query.MaterialTypeQuery;
import top.wms.admin.materialType.model.req.MaterialTypeImportReq;
import top.wms.admin.materialType.model.req.MaterialTypeImportRowReq;
import top.wms.admin.materialType.model.req.MaterialTypeReq;
import top.wms.admin.materialType.model.resp.MaterialTypeImportParseResp;
import top.wms.admin.materialType.model.resp.MaterialTypeImportResp;
import top.wms.admin.materialType.model.resp.MaterialTypeResp;
import top.wms.admin.materialType.service.MaterialTypeService;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import static top.wms.admin.system.enums.ImportPolicyEnum.*;
/**
* 物料品类业务实现
*
* @author zc
* @since 2026/03/16 11:18
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class MaterialTypeServiceImpl extends BaseServiceImpl<MaterialTypeMapper, MaterialTypeDO, MaterialTypeResp, MaterialTypeResp, MaterialTypeQuery, MaterialTypeReq> implements MaterialTypeService {
private final MaterialTypeConvert materialTypeConvert;
@Override
public List<LabelValueResp> getSelectList() {
List<MaterialTypeDO> materialTypeDOS = baseMapper.selectList(new QueryWrapper<>());
return materialTypeConvert.labelValueList(materialTypeDOS);
}
@Override
public void downloadImportTemplate(HttpServletResponse response) throws IOException {
try {
FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/materialType.xlsx"), "物料品类导入模板.xlsx");
} catch (Exception e) {
log.error("下载物料品类导入模板失败:", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setContentType(ContentType.JSON.toString());
response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载批次导入模板失败")));
}
}
@Override
public MaterialTypeImportParseResp parseImport(MultipartFile file) {
MaterialTypeImportParseResp materialTypeImportResp = new MaterialTypeImportParseResp();
List<MaterialTypeImportRowReq> importRowList;
try {
importRowList = EasyExcel.read(file.getInputStream())
.head(MaterialTypeImportRowReq.class)
.sheet()
.headRowNumber(1)
.doReadSync();
} catch (Exception e) {
log.error("物料品类导入数据文件解析异常:", e);
throw new BusinessException("数据文件解析异常");
}
materialTypeImportResp.setTotalRows(importRowList.size());
CheckUtils.throwIfEmpty(importRowList, "数据文件格式错误");
List<MaterialTypeImportRowReq> validRowList = this.filterImportData(importRowList);
materialTypeImportResp.setValidRows(validRowList.size());
CheckUtils.throwIfEmpty(validRowList, "数据文件格式错误");
Set<String> seenTypeName = new HashSet<>();
boolean hasDuplicateTypeName = validRowList.stream()
.map(MaterialTypeImportRowReq::getTypeName)
.anyMatch(typeName -> typeName != null && !seenTypeName.add(typeName));
CheckUtils.throwIf(hasDuplicateTypeName, "存在重复品类名称,请检测数据");
materialTypeImportResp
.setDuplicateNameRows(countExistByField(validRowList, MaterialTypeImportRowReq::getTypeName, MaterialTypeDO::getTypeName, false));
String importKey = UUID.fastUUID().toString(true);
RedisUtils.set(CacheConstants.DATA_IMPORT_KEY + importKey, JSONUtil.toJsonStr(validRowList), Duration
.ofMinutes(10));
materialTypeImportResp.setImportKey(importKey);
return materialTypeImportResp;
}
@Override
@Transactional(rollbackFor = Exception.class)
public MaterialTypeImportResp importMaterial(MaterialTypeImportReq req) {
List<MaterialTypeImportRowReq> importMaterialTypeList;
try {
String data = RedisUtils.get(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
importMaterialTypeList = JSONUtil.toList(data, MaterialTypeImportRowReq.class);
CheckUtils.throwIf(CollUtil.isEmpty(importMaterialTypeList), "导入已过期,请重新上传");
} catch (Exception e) {
log.error("导入异常:", e);
throw new BusinessException("导入已过期,请重新上传");
}
List<String> existTypeName = listExistByField(importMaterialTypeList, MaterialTypeImportRowReq::getTypeName, MaterialTypeDO::getTypeName);
CheckUtils.throwIf(isExitImportMaterialType(req, importMaterialTypeList, existTypeName), "数据不符合导入策略,已退出导入");
List<MaterialTypeDO> insertList = new ArrayList<>();
List<MaterialTypeDO> updateList = new ArrayList<>();
for (MaterialTypeImportRowReq row : importMaterialTypeList) {
if (isSkipImportMaterialType(req, row, existTypeName)) {
continue;
}
MaterialTypeDO materialTypeDO = BeanUtil.toBeanIgnoreError(row, MaterialTypeDO.class);
if (UPDATE.validate(req.getDuplicateTypeName(), row.getTypeName(), existTypeName)) {
materialTypeDO.setTypeName(row.getTypeName());
materialTypeDO.setUpdateUser(UserContextHolder.getUserId());
updateList.add(materialTypeDO);
} else {
insertList.add(materialTypeDO);
}
}
doImportMaterialType(insertList, updateList);
RedisUtils.delete(CacheConstants.DATA_IMPORT_KEY + req.getImportKey());
int insertCount = insertList.size();
int updateCount = updateList.size();
int totalHandleCount = insertCount + updateCount;
return new MaterialTypeImportResp(totalHandleCount, insertCount, updateCount);
}
private int countExistByField(List<MaterialTypeImportRowReq> materialTypeRowList,
Function<MaterialTypeImportRowReq, String> rowField,
com.baomidou.mybatisplus.core.toolkit.support.SFunction<MaterialTypeDO, ?> dbField,
boolean fieldEncrypt) {
List<String> fieldValues = materialTypeRowList.stream().map(rowField).filter(Objects::nonNull).toList();
if (fieldValues.isEmpty()) {
return 0;
}
return (int)this.count(Wrappers.<MaterialTypeDO>lambdaQuery().in(dbField, fieldValues));
}
private List<MaterialTypeImportRowReq> filterImportData(List<MaterialTypeImportRowReq> importRowList) {
return importRowList.stream().filter(row -> ValidationUtil.validate(row).isEmpty()).toList();
}
private List<String> listExistByField(List<MaterialTypeImportRowReq> materialTypeRowList,
Function<MaterialTypeImportRowReq, String> rowField,
com.baomidou.mybatisplus.core.toolkit.support.SFunction<MaterialTypeDO, String> dbField) {
List<String> fieldValues = materialTypeRowList.stream().map(rowField).filter(Objects::nonNull).toList();
if (fieldValues.isEmpty()) {
return Collections.emptyList();
}
List<MaterialTypeDO> materialTypeDOList = baseMapper.selectList(Wrappers.<MaterialTypeDO>lambdaQuery()
.in(dbField, fieldValues)
.select(dbField));
return materialTypeDOList.stream().map(dbField).filter(Objects::nonNull).toList();
}
private boolean isExitImportMaterialType(MaterialTypeImportReq req,
List<MaterialTypeImportRowReq> list,
List<String> existTypeName) {
return list.stream()
.anyMatch(row -> EXIT.validate(req.getDuplicateTypeName(), row.getTypeName(), existTypeName));
}
private boolean isSkipImportMaterialType(MaterialTypeImportReq req,
MaterialTypeImportRowReq row,
List<String> existTypeName) {
return SKIP.validate(req.getDuplicateTypeName(), row.getTypeName(), existTypeName);
}
private void doImportMaterialType(List<MaterialTypeDO> insertList, List<MaterialTypeDO> updateList) {
if (CollUtil.isNotEmpty(insertList)) {
baseMapper.insertBatch(insertList);
}
if (CollUtil.isNotEmpty(updateList)) {
for (MaterialTypeDO materialTypeDO : updateList) {
baseMapper.update(materialTypeDO, Wrappers.<MaterialTypeDO>lambdaUpdate()
.eq(MaterialTypeDO::getTypeName, materialTypeDO.getTypeName()));
}
}
}
}

View File

@@ -1,18 +0,0 @@
package top.wms.admin.system.mapper;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.system.model.entity.RuleRelationDO;
/**
* 通行规则-设备关联 Mapper
*
* @author zc
* @since 2025/12/22 17:16
*/
@Repository
public interface RuleRelationMapper extends BaseMapper<RuleRelationDO> {
int deleteByRuleId(RuleRelationDO ruleRelation);
}

View File

@@ -94,4 +94,13 @@ public interface UserMapper extends DataPermissionMapper<UserDO> {
* @return 用户数量
*/
Long selectCountByPhone(@FieldEncrypt @Param("phone") String phone, @Param("id") Long id);
/**
* 根据卡片号查询
*
* @param card 卡片号
* @return 用户信息
*/
@Select("SELECT * FROM sys_user WHERE card_no = #{card}")
UserDO selectByCard(@Param("card") String card);
}

View File

@@ -1,37 +0,0 @@
package top.wms.admin.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 通行规则-设备关联实体
*
* @author zc
* @since 2025/12/22 17:16
*/
@Data
@TableName("equipment_rule_relation")
public class RuleRelationDO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 平台侧规则ID关联规则表主键
*/
private Long ruleId;
/**
* 设备ID关联设备表主键
*/
private Long equipmentId;
/**
* 设备侧的规则ID设备本地存储的规则标识
*/
private String equipmentRuleId;
}

View File

@@ -92,4 +92,9 @@ public class UserDO extends BaseDO {
*/
@TableField(exist = false)
private Long equipmentId;
}
/**
* 数据源标识wms/wms_ql
*/
private String dataSource;
}

View File

@@ -1,52 +0,0 @@
package top.wms.admin.system.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 创建或修改通行规则-设备关联参数
*
* @author zc
* @since 2025/12/22 17:16
*/
@Data
@Schema(description = "创建或修改通行规则-设备关联参数")
public class RuleRelationReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 平台侧规则ID关联规则表主键
*/
@Schema(description = "平台侧规则ID关联规则表主键")
@NotNull(message = "平台侧规则ID关联规则表主键不能为空")
private Long ruleId;
/**
* 设备ID关联设备表主键
*/
@Schema(description = "设备ID关联设备表主键")
@NotNull(message = "设备ID关联设备表主键不能为空")
private Long equipmentId;
/**
* 设备侧的规则ID设备本地存储的规则标识
*/
@Schema(description = "设备侧的规则ID设备本地存储的规则标识")
@NotBlank(message = "设备侧的规则ID设备本地存储的规则标识不能为空")
@Length(max = 64, message = "设备侧的规则ID设备本地存储的规则标识长度不能超过 {max} 个字符")
private String equipmentRuleId;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -93,6 +93,12 @@ public class UserReq implements Serializable {
@Schema(description = "状态", example = "1")
private DisEnableStatusEnum status;
/**
* 状态
*/
@Schema(description = "数据源")
private String dataSource;
/**
* 设备ID
*/

View File

@@ -116,6 +116,12 @@ public class UserDetailResp extends BaseDetailResp {
@Schema(description = "设备ID")
private Long equipmentId;
/**
* 数据源
*/
@Schema(description = "数据源")
private String dataSource;
@Override
public Boolean getDisabled() {
return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId());

View File

@@ -81,6 +81,12 @@ public class UserResp extends BaseDetailResp {
@Schema(description = "描述", example = "张三描述信息")
private String description;
/**
* 状态
*/
@Schema(description = "数据源")
private String dataSource;
/**
* 角色名称列表
*/

View File

@@ -100,13 +100,18 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
}
});
// 处理本地存储文件 URL
FileInfo fileInfo = uploadPretreatment.upload();
String domain = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH);
fileInfo.setUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getFilename()));
if (StrUtil.isNotBlank(fileInfo.getThFilename())) {
fileInfo.setThUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getThFilename()));
} else {
fileInfo.setThUrl(fileInfo.getUrl());
FileInfo fileInfo = null;
try {
fileInfo = uploadPretreatment.upload();
String domain = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH);
fileInfo.setUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getFilename()));
if (StrUtil.isNotBlank(fileInfo.getThFilename())) {
fileInfo.setThUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getThFilename()));
} else {
fileInfo.setThUrl(fileInfo.getUrl());
}
} catch (Exception e) {
}
return fileInfo;
}
@@ -146,4 +151,4 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode()));
}
}
}
}

View File

@@ -1,23 +0,0 @@
package top.wms.admin.system.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.wms.admin.system.mapper.PeopleEquipmentMapper;
import top.wms.admin.system.model.entity.PeopleEquipmentDO;
import top.wms.admin.system.model.query.PeopleEquipmentQuery;
import top.wms.admin.system.model.req.PeopleEquipmentReq;
import top.wms.admin.system.model.resp.PeopleEquipmentResp;
import top.wms.admin.system.service.PeopleEquipmentService;
/**
* 人员设备下发信息业务实现
*
* @author zc
* @since 2025/03/27 14:59
*/
@Service
@RequiredArgsConstructor
public class PeopleEquipmentServiceImpl extends BaseServiceImpl<PeopleEquipmentMapper, PeopleEquipmentDO, PeopleEquipmentResp, PeopleEquipmentResp, PeopleEquipmentQuery, PeopleEquipmentReq> implements PeopleEquipmentService {}

View File

@@ -36,6 +36,7 @@ import net.dreamlu.mica.core.result.R;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -131,6 +132,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
CheckUtils.throwIf(StrUtil.isNotBlank(email) && this.isEmailExists(email, null), errorMsgTemplate, email);
String phone = req.getPhone();
CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, null), errorMsgTemplate, phone);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
req.setPassword("{bcrypt}" + encoder.encode(req.getPassword()));
}
@Override
@@ -461,7 +464,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
@Override
public UserDO getByCard(String card) {
return baseMapper.selectOne(new QueryWrapper<UserDO>().eq("card_no", card));
return baseMapper.selectByCard(card);
}
@Override

View File

@@ -0,0 +1,16 @@
package top.wms.admin.weighManage.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import org.springframework.stereotype.Repository;
import top.wms.admin.weighManage.model.entity.WorkOrderInfoDO;
/**
* 任务工单详情 Mapper
*
* @author zc
* @since 2026/03/04 14:16
*/
@Repository
public interface WorkOrderInfoMapper extends BaseMapper<WorkOrderInfoDO> {
}

View File

@@ -0,0 +1,50 @@
package top.wms.admin.weighManage.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.weighManage.model.entity.WorkOrderDO;
import org.springframework.stereotype.Repository;
import top.wms.admin.weighManage.model.resp.WorkOrderInfoResp;
import top.wms.admin.weighManage.model.resp.WorkOrderResp;
import java.util.List;
/**
* 任务工单信息 Mapper
*
* @author zc
* @since 2026/03/03 17:09
*/
@Repository
public interface WorkOrderMapper extends BaseMapper<WorkOrderDO> {
/**
* 获取任务工单分页列表
*
* @param objectPage 分页参数
* @param queryWrapper 查询参数
* @return 任务工单分页列表
*/
IPage<WorkOrderResp> selectWorkOrderPage(@Param("page") Page<Object> objectPage,
@Param(Constants.WRAPPER) QueryWrapper<WorkOrderDO> queryWrapper);
/**
* 获取任务工单详情信息
*
* @param id 任务工单主键id
* @return 任务工单详情信息
*/
List<WorkOrderInfoResp> getDetail(Long id);
/**
* 导出任务工单列表
*
* @param queryWrapper 查询参数
* @return 任务工单列表
*/
List<WorkOrderResp> selectExport(@Param(Constants.WRAPPER) QueryWrapper<WorkOrderDO> queryWrapper);
}

View File

@@ -0,0 +1,65 @@
package top.wms.admin.weighManage.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.wms.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 任务工单信息实体
*
* @author zc
* @since 2026/03/03 17:09
*/
@Data
@TableName("sys_work_order")
public class WorkOrderDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标题
*/
private String title;
/**
* 任务工单号
*/
private String orderNo;
/**
* 物料主键id
*/
private Long materialId;
/**
* 实际总重量
*/
private BigDecimal totalWeight;
/**
* 标准总重量(标重)
*/
private BigDecimal totalCalculatedWeight;
/**
* 总数量
*/
private Integer totalCount;
/**
* 批次
*/
private String batch;
/**
* 标记号
*/
private String mark;
}

View File

@@ -0,0 +1,75 @@
package top.wms.admin.weighManage.model.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import top.wms.admin.common.model.entity.BaseDO;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 任务工单详情实体
*
* @author zc
* @since 2026/03/04 14:16
*/
@Data
@TableName("sys_work_order_info")
public class WorkOrderInfoDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 工单主键id
*/
private Long workOrderId;
/**
* 物料主键id
*/
private Long materialId;
/**
* 称重次数
*/
private int weightTime;
/**
* 抓拍的图片
*/
private String imgUrl;
/**
* 物料数量
*/
private Integer quantity;
/**
* 称重重量
*/
private BigDecimal weight;
/**
* 计算重量
*/
private BigDecimal calculatedWeight;
/**
* 批次
*/
private String batch;
/**
* 标记号
*/
private String mark;
/**
* 称重数量
*/
private Integer weightQuantity;
}

View File

@@ -0,0 +1,75 @@
package top.wms.admin.weighManage.model.query;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import java.util.Date;
/**
* 任务工单信息查询条件
*
* @author zc
* @since 2026/03/03 17:09
*/
@Data
@Schema(description = "任务工单信息查询条件")
public class WorkOrderQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
private String orderNo;
/**
* 物料名称
*/
@Schema(description = "物料名称")
private String materialName;
/**
* 物料编码
*/
@Schema(description = "物料编码")
private String encoding;
/**
* 创建人
*/
@Schema(description = "创建人")
private String userName;
/**
* 卡号
*/
@Schema(description = "卡号")
private String carNo;
/**
* 创建开始时间
*/
@Schema(description = "创建开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startDate;
/**
* 创建结束时间
*/
@Schema(description = "创建结束时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endDate;
/**
* 批次
*/
private String batch;
}

View File

@@ -0,0 +1,79 @@
package top.wms.admin.weighManage.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import jakarta.validation.constraints.*;
/**
* 创建或修改任务工单信息参数
*
* @author zc
* @since 2026/03/03 17:09
*/
@Data
@Schema(description = "创建或修改任务工单信息参数")
public class WorkOrderInfoReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 工单主键id
*/
@Schema(description = "工单主键id")
private Long workOrderId;
/**
* 物料主键id
*/
@Schema(description = "物料主键id")
@NotNull(message = "物料主键id不能为空")
private Long materialId;
/**
* 称重次数
*/
@Schema(description = "称重次数")
private Integer weightTime;
/**
* 物料数量
*/
@Schema(description = "物料数量")
private Integer quantity;
/**
* 称重重量
*/
@Schema(description = "称重重量")
private BigDecimal weight;
/**
* 计算重量
*/
@Schema(description = "计算重量")
private BigDecimal calculatedWeight;
/**
* 抓拍的图片
*/
@Schema(description = "抓拍的图片")
private String imgUrl;
/**
* 称重数量
*/
private Integer weightQuantity;
/**
* 标记
*/
@Schema(description = "标记")
private String mark;
}

View File

@@ -0,0 +1,65 @@
package top.wms.admin.weighManage.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 创建或修改任务工单信息参数
*
* @author zc
* @since 2026/03/03 17:09
*/
@Data
@Schema(description = "创建或修改任务工单信息参数")
public class WorkOrderReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 物料主键id
*/
@Schema(description = "物料主键id")
@NotNull(message = "物料主键id不能为空")
private Long materialId;
/**
* 称重列表
*/
@Schema(description = "称重列表")
@NotEmpty(message = "称重列表不能为空")
private List<WorkOrderInfoReq> workOrderInfos;
/**
* 手动填写的物料数量
*/
private String inputQuantity;
/**
* 电子秤重量
*/
private String ahDeviceWeight;
/**
* 计算重量
*/
private String calculatedWeight;
/**
* 批次
*/
private String batch;
/**
* 标记号
*/
private String mark;
}

View File

@@ -1,15 +0,0 @@
package top.wms.admin.weighManage.model.resp;
import lombok.Data;
@Data
public class MaterialResp {
private Long id;
private String materialCode;
private String materialName;
private String materialSpec;
private String weight;
private String imageUrl;
}

View File

@@ -0,0 +1,70 @@
package top.wms.admin.weighManage.model.resp;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 任务工单导出信息(扁平化,包含主表和子表字段)
*
* @author zc
* @since 2026/04/16
*/
@Data
public class WorkOrderExportResp {
@ExcelProperty(value = "人员卡号", order = 1)
private String cardNo;
@ExcelProperty(value = "工单编号", order = 2)
private String orderNo;
@ExcelProperty(value = "标题", order = 3)
private String title;
@ExcelProperty(value = "物料名称", order = 4)
private String materialName;
@ExcelProperty(value = "物料编码", order = 5)
private String encoding;
@ExcelProperty(value = "单位重量", order = 6)
private BigDecimal unitWeight;
@ExcelProperty(value = "物料直径", order = 7)
private Double materialSpec;
@ExcelProperty(value = "图片地址", order = 8)
private String photoUrl;
@ExcelProperty(value = "标准总重量(g)", order = 11)
private BigDecimal totalCalculatedWeight;
@ExcelProperty(value = "实际总重量(g)", order = 12)
private BigDecimal totalWeight;
@ExcelProperty(value = "总数量", order = 13)
private Integer totalCount;
@ExcelProperty(value = "输入数量", order = 14)
private Integer quantity;
@ExcelProperty(value = "标准重量(g)", order = 15)
private BigDecimal calculatedWeight;
@ExcelProperty(value = "称重数量", order = 16)
private Integer weightQuantity;
@ExcelProperty(value = "称重重量(g)", order = 17)
private BigDecimal weight;
@ExcelProperty(value = "图片地址", order = 18)
private String imgUrl;
@ExcelProperty(value = "批次", order = 9)
private String infoBatch;
@ExcelProperty(value = "标记号", order = 10)
private String infoMark;
}

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