51 Commits

Author SHA1 Message Date
zc
6355059ee3 打印编码需求变更 2026-06-15 18:03:06 +08:00
zc
2c7e05cbad 打印编码需求变更 2026-06-15 15:36:16 +08:00
zc
9f56c47105 打印编码 2026-06-15 13:49:30 +08:00
zc
22c7963bf5 打印编码 2026-06-10 10:37:06 +08:00
zc
e932af0404 整箱导出优化 2026-05-07 16:02:16 +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
186 changed files with 23551 additions and 2796 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>

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

@@ -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>

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,63 @@
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 encodingPrint;
/**
* 图片地址
*/
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,77 @@
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,102 @@
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 top.wms.admin.material.mapper.MaterialInfoMapper;
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;
private final MaterialInfoMapper materialInfoMapper;
@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,14 @@
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;
@@ -14,7 +20,16 @@ import java.util.List;
*/
@Repository
public interface MaterialInfoMapper extends BaseMapper<MaterialInfoDO> {
public int updateByName(List<MaterialInfoDO> list);
int updateByName(@Param("list") List<MaterialInfoDO> list);
public int updateByCode(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

@@ -32,18 +32,54 @@ public class MaterialInfoDO extends BaseDO {
*/
private String encoding;
/**
* 打印物料编码
*/
private String encodingPrint;
/**
* 物料单位重量(g)
*/
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

@@ -2,6 +2,7 @@ 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;
@@ -28,18 +29,44 @@ public class MaterialImportRowReq implements Serializable {
*/
@Schema(description = "物料编码")
@NotBlank(message = "物料编码不能为空")
@Length(max = 255, message = "物料编码长度不能超过 {max} 个字符")
@Length(max = 64, message = "物料编码长度不能超过 {max} 个字符")
private String encoding;
/**
* 物料单位重量(g)
*/
@Schema(description = "物料单位重量(g)")
@NotNull(message = "物料单位重量不能为空")
private BigDecimal unitWeight;
/**
* 灯光等级
*/
@Schema(description = "灯光等级")
private String lightLevelName;
/*
* 物料规格
* 物料直径
* */
@Schema(description = "物料规格")
private String materialSpec;
@Schema(description = "物料直径")
private Double materialSpec;
/**
* 物料品类名称
*/
@Schema(description = "物料品类名称")
private String typeName;
/**
* 流程名称
*/
@Schema(description = "物料流程")
private String materialProcess;
/**
* 颜色
*/
@Schema(description = "颜色")
private String color;
}

View File

@@ -37,20 +37,21 @@ public class MaterialInfoReq implements Serializable {
*/
@Schema(description = "物料编码")
@NotBlank(message = "物料编码不能为空")
@Length(max = 255, message = "物料编码长度不能超过 {max} 个字符")
@Length(max = 64, message = "物料编码长度不能超过 {max} 个字符")
private String encoding;
/**
* 物料单位重量(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

@@ -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,29 +26,29 @@ 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 = "物料规格")
private String materialSpec;
@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

@@ -3,8 +3,8 @@ 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;
@@ -21,11 +21,21 @@ import java.io.IOException;
*/
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);
/**
* 下载导入模板
@@ -56,9 +66,32 @@ public interface MaterialInfoService extends BaseService<MaterialInfoResp, Mater
* */
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

@@ -5,29 +5,39 @@ 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.*;
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 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 cn.hutool.core.util.StrUtil;
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;
@@ -35,13 +45,13 @@ 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.MaterialImportRowReq;
import top.wms.admin.material.model.req.MaterialInfoImportReq;
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;
@@ -49,7 +59,6 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -57,7 +66,6 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static top.wms.admin.system.enums.ImportPolicyEnum.*;
import static top.wms.admin.system.enums.ImportPolicyEnum.SKIP;
/**
* 物料信息业务实现
@@ -74,13 +82,68 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
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
@@ -89,10 +152,10 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
FileUploadUtils.download(response, ResourceUtil
.getStream("templates/import/materialInfo.xlsx"), "物料信息导入模板.xlsx");
} catch (Exception e) {
log.error("下载用户导入模板失败:{}", e);
log.error("下载物料信息导入模板失败:", e);
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setContentType(ContentType.JSON.toString());
response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载用户导入模板失败")));
response.getWriter().write(JSONUtil.toJsonStr(R.fail("下载物料信息导入模板失败")));
}
}
@@ -108,7 +171,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.headRowNumber(1)
.doReadSync();
} catch (Exception e) {
log.error("物料信息导入数据文件解析异常:{}", e);
log.error("物料信息导入数据文件解析异常:", e);
throw new BusinessException("数据文件解析异常");
}
// 总计行数
@@ -126,10 +189,10 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.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分钟
@@ -157,33 +220,47 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
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生成器
// IdGenerator idGenerator = DefaultIdGeneratorProvider.INSTANCE.getShare();
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(StrUtil.isNotBlank(row.getMaterialSpec()) ? row.getMaterialSpec() : 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.setUpdateTime(LocalDateTime.now());
materialDO.setUpdateUser(UserContextHolder.getUserId());
updateByNameList.add(materialDO);
} else if (UPDATE.validate(req.getDuplicateCode(), row.getEncoding(), existCode)) {
materialDO.setEncoding(row.getEncoding());
materialDO.setUpdateTime(LocalDateTime.now());
materialDO.setUpdateUser(UserContextHolder.getUserId());
updateByCodeList.add(materialDO);
} else {
// materialDO.setId(idGenerator.generate());
insertList.add(materialDO);
}
}
@@ -227,16 +304,17 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
*/
private List<MaterialImportRowReq> filterImportData(List<MaterialImportRowReq> importRowList) {
// 校验过滤
List<MaterialImportRowReq> list = importRowList.stream()
.filter(row -> ValidationUtil.validate(row).isEmpty())
.toList();
// 物料名去重
return list.stream()
.collect(Collectors.toMap(MaterialImportRowReq::getMaterialName, row -> row, (existing,
replacement) -> existing))
.values()
.stream()
.toList();
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();
}
/**
@@ -321,7 +399,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
// 1. 初始化物料编码-照片地址Map
Map<String, String> codeUrlMap = new HashMap<>();
// 物料照片存储路径(自定义,比如按日期分目录)
String photoStoragePath = "/" + DateUtil.today() + "/";
String photoStoragePath = "material/";
try (ZipInputStream zipInputStream = new ZipInputStream(zipFile.getInputStream())) {
ZipEntry zipEntry;
@@ -333,7 +411,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
}
// 2. 提取物料编码(照片名 = 物料编码,去掉后缀)
String fileName = zipEntry.getName();
log.info("正在处理的照片:" + fileName);
log.info("正在处理的照片:{}", fileName);
//去除windows或linux环境下 可能存在的多层级目录
if (fileName.contains("/")) {
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
@@ -360,7 +438,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.equals("jpg") ? "jpeg" : imageExt), imageBytes);
// 6. 调用upload方法上传图片
FileInfo fileInfo = fileService.upload(singleImageFile, photoStoragePath, null, true, true);
FileInfo fileInfo = fileService.upload(singleImageFile, photoStoragePath, null, false, false);
// 7. 将物料编码和图片URL存入Map
codeUrlMap.put(materialCode, fileInfo.getUrl());
@@ -413,13 +491,95 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
.forEach(code -> log.warn("物料编码 [{}] 不存在,照片更新失败", code));
}
@Override
public String catchPhoto(MultipartFile file){
public String catchPhoto(MultipartFile file) {
String photoStoragePath = "catch" + DateUtil.today() + "/";
FileInfo fileInfo = fileService.upload(file, photoStoragePath, null, true, true);
CheckUtils.throwIfNull(fileInfo,"照片上传失败");
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

@@ -0,0 +1,16 @@
package top.wms.admin.print.mapper;
import org.springframework.stereotype.Repository;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.print.model.entity.PrintInfoDO;
/**
* 打印记录 Mapper
*
* @author zc
* @since 2026/06/15 16:57
*/
@Repository
public interface PrintInfoMapper extends BaseMapper<PrintInfoDO> {
}

View File

@@ -0,0 +1,16 @@
package top.wms.admin.print.mapper;
import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.print.model.entity.PrintDO;
import org.springframework.stereotype.Repository;
/**
* 打印记录 Mapper
*
* @author zc
* @since 2026/06/15 16:57
*/
@Repository
public interface PrintMapper extends BaseMapper<PrintDO> {
}

View File

@@ -0,0 +1,68 @@
package top.wms.admin.print.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/06/15 16:57
*/
@Data
@TableName("sys_print")
public class PrintDO extends BaseDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 物料编码
*/
private String encoding;
/**
* 打印编码
*/
private String encodingPrint;
/**
* 物料名称
*/
private String materialName;
/**
* 打印名称
*/
private String materialNamePrint;
/**
* 任务工单号
*/
private String orderNo;
/**
* 任务工单id
*/
private Long workerOrderId;
/**
* 批次
*/
private String batch;
/**
* 标签数量
*/
private Integer labelCount;
/**
* 打印类型 0明细标签打印 1整体标签打印
*/
private Integer labelType;
}

View File

@@ -0,0 +1,86 @@
package top.wms.admin.print.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 打印记录详情实体
*
* @author zc
* @since 2026/06/15 16:58
*/
@Data
@TableName("sys_print_info")
public class PrintInfoDO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
private Long printId;
/**
* 打印编码
*/
private String encodingPrint;
/**
* 检验签字
*/
private String inspectionSignature;
/**
* 标记号
*/
private String mark;
/**
* 打印名称
*/
private String materialNamePrint;
/**
* 包装签字
*/
private String packingSignature;
/**
* 生产日期
*/
private String productionDate;
/**
* 二维码数据
*/
private String qrCodeData;
/**
* 批次
*/
private String batch;
/**
* 标重
*/
private BigDecimal totalCalculatedWeight;
/**
* 数量
*/
private Integer totalCount;
/**
* 实重
*/
private BigDecimal totalWeight;
/**
* 任务工单ID
*/
private Long workerOrderId;
}

View File

@@ -0,0 +1,82 @@
package top.wms.admin.print.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/06/15 16:57
*/
@Data
@Schema(description = "打印记录查询条件")
public class PrintQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 打印编码
*/
@Schema(description = "打印编码")
@Query(type = QueryType.EQ)
private String encodingPrint;
/**
* 物料名称
*/
@Schema(description = "物料名称")
@Query(type = QueryType.EQ)
private String materialName;
/**
* 打印名称
*/
@Schema(description = "打印名称")
@Query(type = QueryType.EQ)
private String materialNamePrint;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
@Query(type = QueryType.EQ)
private String orderNo;
/**
* 任务工单id
*/
@Schema(description = "任务工单id")
@Query(type = QueryType.EQ)
private Long workerOrderId;
/**
* 批次
*/
@Schema(description = "批次")
@Query(type = QueryType.EQ)
private String batch;
/**
* 创建人
*/
@Schema(description = "创建人")
@Query(type = QueryType.EQ)
private Long createUser;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@Query(type = QueryType.EQ)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,109 @@
package top.wms.admin.print.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.math.BigDecimal;
/**
* 创建或修改打印记录详情参数
*
* @author zc
* @since 2026/06/15 17:03
*/
@Data
@Schema(description = "创建或修改打印记录详情参数")
public class PrintInfoReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 打印编码
*/
@Schema(description = "打印编码")
@Length(max = 255, message = "打印编码长度不能超过 {max} 个字符")
private String encodingPrint;
/**
* 检验签字
*/
@Schema(description = "检验签字")
@Length(max = 25, message = "检验签字长度不能超过 {max} 个字符")
private String inspectionSignature;
/**
* 标记号
*/
@Schema(description = "标记号")
@Length(max = 25, message = "标记号长度不能超过 {max} 个字符")
private String mark;
/**
* 打印名称
*/
@Schema(description = "打印名称")
@NotBlank(message = "打印名称不能为空")
@Length(max = 255, message = "打印名称长度不能超过 {max} 个字符")
private String materialNamePrint;
/**
* 包装签字
*/
@Schema(description = "包装签字")
@Length(max = 25, message = "包装签字长度不能超过 {max} 个字符")
private String packingSignature;
/**
* 生产日期
*/
@Schema(description = "生产日期")
@Length(max = 50, message = "生产日期长度不能超过 {max} 个字符")
private String productionDate;
/**
* 二维码数据
*/
@Schema(description = "二维码数据")
@Length(max = 500, message = "二维码数据长度不能超过 {max} 个字符")
private String qrCodeData;
/**
* 批次
*/
@Schema(description = "批次")
@NotBlank(message = "批次不能为空")
@Length(max = 255, message = "批次长度不能超过 {max} 个字符")
private String batch;
/**
* 标重
*/
@Schema(description = "标重")
private BigDecimal totalCalculatedWeight;
/**
* 数量
*/
@Schema(description = "数量")
private Integer totalCount;
/**
* 实重
*/
@Schema(description = "实重")
private BigDecimal totalWeight;
/**
* 任务工单ID
*/
@Schema(description = "任务工单ID")
private Long workerOrderId;
}

View File

@@ -0,0 +1,88 @@
package top.wms.admin.print.model.req;
import jakarta.validation.constraints.*;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import top.wms.admin.print.model.entity.PrintInfoDO;
import java.io.Serial;
import java.io.Serializable;
import java.time.*;
import java.util.List;
/**
* 创建或修改打印记录参数
*
* @author zc
* @since 2026/06/15 17:04
*/
@Data
@Schema(description = "创建或修改打印记录参数")
public class PrintReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 物料编码
*/
@Schema(description = "物料编码")
private String encoding;
/**
* 打印编码
*/
@Schema(description = "打印编码")
private String encodingPrint;
/**
* 物料名称
*/
@Schema(description = "物料名称")
@NotBlank(message = "物料名称不能为空")
private String materialName;
/**
* 打印名称
*/
@Schema(description = "打印名称")
private String materialNamePrint;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
@NotBlank(message = "任务工单号不能为空")
private String orderNo;
/**
* 任务工单id
*/
@Schema(description = "任务工单id")
@NotNull(message = "任务工单id不能为空")
private Long workerOrderId;
/**
* 批次
*/
@Schema(description = "批次")
@NotBlank(message = "批次不能为空")
private String batch;
/**
* 标签数量
*/
@Schema(description = "标签数量")
private Integer labelCount;
/**
* 打印类型 0明细标签打印 1整体标签打印
*/
@Schema(description = "打印类型 0明细标签打印 1整体标签打印")
private Integer labelType;
private List<PrintInfoDO> printInfoList;
}

View File

@@ -0,0 +1,97 @@
package top.wms.admin.print.model.resp;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 打印记录详情信息
*
* @author zc
* @since 2026/06/15 17:08
*/
@Data
@Schema(description = "打印记录详情信息")
public class PrintInfoResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
/**
* 打印编码
*/
@Schema(description = "打印编码")
private String encodingPrint;
/**
* 检验签字
*/
@Schema(description = "检验签字")
private String inspectionSignature;
/**
* 标记号
*/
@Schema(description = "标记号")
private String mark;
/**
* 打印名称
*/
@Schema(description = "打印名称")
private String materialNamePrint;
/**
* 包装签字
*/
@Schema(description = "包装签字")
private String packingSignature;
/**
* 生产日期
*/
@Schema(description = "生产日期")
private String productionDate;
/**
* 二维码数据
*/
@Schema(description = "二维码数据")
private String qrCodeData;
/**
* 批次
*/
@Schema(description = "批次")
private String batch;
/**
* 标重
*/
@Schema(description = "标重")
private BigDecimal totalCalculatedWeight;
/**
* 数量
*/
@Schema(description = "数量")
private Integer totalCount;
/**
* 实重
*/
@Schema(description = "实重")
private BigDecimal totalWeight;
/**
* 任务工单ID
*/
@Schema(description = "任务工单ID")
private Long workerOrderId;
}

View File

@@ -0,0 +1,90 @@
package top.wms.admin.print.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.*;
/**
* 打印记录信息
*
* @author zc
* @since 2026/06/15 16:57
*/
@Data
@Schema(description = "打印记录信息")
public class PrintResp extends BaseDetailResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 物料编码
*/
@Schema(description = "物料编码")
private String encoding;
/**
* 打印编码
*/
@Schema(description = "打印编码")
private String encodingPrint;
/**
* 物料名称
*/
@Schema(description = "物料名称")
private String materialName;
/**
* 打印名称
*/
@Schema(description = "打印名称")
private String materialNamePrint;
/**
* 任务工单号
*/
@Schema(description = "任务工单号")
private String orderNo;
/**
* 任务工单id
*/
@Schema(description = "任务工单id")
private Long workerOrderId;
/**
* 批次
*/
@Schema(description = "批次")
private String batch;
/**
* 标签数量
*/
@Schema(description = "标签数量")
private Integer labelCount;
/**
* 打印类型 0明细标签打印 1整体标签打印
*/
@Schema(description = "打印类型 0明细标签打印 1整体标签打印")
private Integer labelType;
/**
* 修改人
*/
@Schema(description = "修改人")
private Long updateUser;
/**
* 修改时间
*/
@Schema(description = "修改时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,20 @@
package top.wms.admin.print.service;
import top.continew.starter.extension.crud.service.BaseService;
import top.wms.admin.print.model.query.PrintQuery;
import top.wms.admin.print.model.req.PrintReq;
import top.wms.admin.print.model.resp.PrintInfoResp;
import top.wms.admin.print.model.resp.PrintResp;
import java.util.List;
/**
* 打印记录业务接口
*
* @author zc
* @since 2026/06/15 16:57
*/
public interface PrintService extends BaseService<PrintResp, PrintResp, PrintQuery, PrintReq> {
List<PrintInfoResp> getInfo(Long id);
}

View File

@@ -0,0 +1,51 @@
package top.wms.admin.print.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.service.BaseServiceImpl;
import top.wms.admin.print.mapper.PrintInfoMapper;
import top.wms.admin.print.mapper.PrintMapper;
import top.wms.admin.print.model.entity.PrintDO;
import top.wms.admin.print.model.entity.PrintInfoDO;
import top.wms.admin.print.model.query.PrintQuery;
import top.wms.admin.print.model.req.PrintReq;
import top.wms.admin.print.model.resp.PrintInfoResp;
import top.wms.admin.print.model.resp.PrintResp;
import top.wms.admin.print.service.PrintService;
import java.util.List;
import java.util.stream.Collectors;
/**
* 打印记录业务实现
*
* @author zc
* @since 2026/06/15 16:57
*/
@Service
@RequiredArgsConstructor
public class PrintServiceImpl extends BaseServiceImpl<PrintMapper, PrintDO, PrintResp, PrintResp, PrintQuery, PrintReq> implements PrintService {
private final PrintInfoMapper printInfoMapper;
@Override
public List<PrintInfoResp> getInfo(Long id) {
PrintDO printDO = baseMapper.selectById(id);
List<PrintInfoDO> printInfoRespList = printInfoMapper.selectList(new QueryWrapper<PrintInfoDO>()
.eq("worker_order_id", printDO.getWorkerOrderId()));
return BeanUtil.copyToList(printInfoRespList, PrintInfoResp.class);
}
@Override
public void afterAdd(PrintReq req, PrintDO printDO) {
printInfoMapper.insertBatch(req.getPrintInfoList()
.stream()
.peek(item -> item.setPrintId(printDO.getId()))
.collect(Collectors.toList()));
}
}

View File

@@ -63,7 +63,7 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
}
@Override
public FileInfo upload(MultipartFile file, String path, String storageCode, Boolean createMin, Boolean store) {
public FileInfo upload(MultipartFile file, String path, String storageCode, Boolean createMin, Boolean store) {
StorageDO storage;
if (StrUtil.isBlank(storageCode)) {
storage = storageService.getDefaultStorage();

View File

@@ -40,4 +40,11 @@ public interface WorkOrderMapper extends BaseMapper<WorkOrderDO> {
*/
List<WorkOrderInfoResp> getDetail(Long id);
/**
* 导出任务工单列表
*
* @param queryWrapper 查询参数
* @return 任务工单列表
*/
List<WorkOrderResp> selectExport(@Param(Constants.WRAPPER) QueryWrapper<WorkOrderDO> queryWrapper);
}

View File

@@ -38,13 +38,33 @@ public class WorkOrderDO extends BaseDO {
private Long materialId;
/**
* 总重量
* 打印物料编码
*/
private String encodingPrint;
/**
* 实际总重量
*/
private BigDecimal totalWeight;
/**
* 标准总重量(标重)
*/
private BigDecimal totalCalculatedWeight;
/**
* 总数量
*/
private Integer totalCount;
/**
* 批次
*/
private String batch;
/**
* 标记号
*/
private String mark;
}

View File

@@ -45,7 +45,7 @@ public class WorkOrderInfoDO extends BaseDO {
/**
* 物料数量
*/
private BigDecimal quantity;
private Integer quantity;
/**
* 称重重量
@@ -56,4 +56,20 @@ public class WorkOrderInfoDO extends BaseDO {
* 计算重量
*/
private BigDecimal calculatedWeight;
/**
* 批次
*/
private String batch;
/**
* 标记号
*/
private String mark;
/**
* 称重数量
*/
private Integer weightQuantity;
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -56,11 +57,19 @@ public class WorkOrderQuery implements Serializable {
* 创建开始时间
*/
@Schema(description = "创建开始时间")
private Date startTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startDate;
/**
* 创建结束时间
*/
@Schema(description = "创建结束时间")
private Date endTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endDate;
/**
* 批次
*/
private String batch;
}

View File

@@ -65,4 +65,15 @@ public class WorkOrderInfoReq implements Serializable {
@Schema(description = "抓拍的图片")
private String imgUrl;
/**
* 称重数量
*/
private Integer weightQuantity;
/**
* 标记
*/
@Schema(description = "标记")
private String mark;
}

View File

@@ -30,13 +30,6 @@ public class WorkOrderReq implements Serializable {
@NotNull(message = "物料主键id不能为空")
private Long materialId;
/**
* 物料名称
*/
@Schema(description = "物料名称")
@NotBlank(message = "物料名称不能为空")
private String materialName;
/**
* 称重列表
*/
@@ -44,16 +37,29 @@ public class WorkOrderReq implements Serializable {
@NotEmpty(message = "称重列表不能为空")
private List<WorkOrderInfoReq> workOrderInfos;
/**
* 手动填写的物料数量
*/
private String inputQuantity;
/**
* 电子秤重量
*/
private String ahDeviceWeight;
/**
* 计算重量
*/
private String calculatedWeight;
/**
* 批次
*/
private String batch;
/**
* 标记号
*/
private String mark;
}

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;
}

View File

@@ -1,5 +1,6 @@
package top.wms.admin.weighManage.model.resp;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -34,9 +35,10 @@ public class WorkOrderInfoResp {
private Long materialId;
/**
* 物料主键id
* 物料名称
*/
@Schema(description = "物料主键id")
@Schema(description = "物料名称")
@ExcelProperty(value = "物料名称", order = 1)
private String materialName;
/**
@@ -49,24 +51,48 @@ public class WorkOrderInfoResp {
* 物料数量
*/
@Schema(description = "物料数量")
private BigDecimal quantity;
@ExcelProperty(value = "输入数量", order = 2)
private Integer quantity;
/**
* 计算重量
*/
@Schema(description = "计算重量")
@ExcelProperty(value = "标准重量(g)", order = 3)
private BigDecimal calculatedWeight;
/**
* 称重数量
*/
@Schema(description = "称重数量")
@ExcelProperty(value = "称重数量", order = 4)
private Integer weightQuantity;
/**
* 称重重量
*/
@Schema(description = "称重重量")
@ExcelProperty(value = "称重重量(g)", order = 5)
private BigDecimal weight;
/**
* 计算重量
*/
@Schema(description = "计算重量")
private BigDecimal calculatedWeight;
/**
* 抓拍的图片
*/
@Schema(description = "抓拍的图片")
@ExcelProperty(value = "图片地址", order = 6)
private String imgUrl;
/**
* 批次
*/
@Schema(description = "批次")
@ExcelProperty(value = "批次", order = 7)
private String batch;
/**
* 标记号
*/
@Schema(description = "标记号")
@ExcelProperty(value = "标记号", order = 8)
private String mark;
}

View File

@@ -1,5 +1,7 @@
package top.wms.admin.weighManage.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;
@@ -28,78 +30,104 @@ public class WorkOrderResp extends BaseDetailResp {
* 卡号
*/
@Schema(description = "卡号")
@ExcelProperty(value = "人员卡号", order = 1)
private String cardNo;
/**
* 工单编号
*/
@Schema(description = "工单编号")
@ExcelProperty(value = "工单编号", order = 2)
private String orderNo;
/**
* 标题
*/
@Schema(description = "标题")
@ExcelProperty(value = "标题", order = 3)
private String title;
/**
* 物料主键id
*/
@Schema(description = "物料主键id")
@ExcelIgnore
private Long materialId;
/**
* 物料名称
*/
@Schema(description = "物料名称")
@ExcelProperty(value = "物料名称", order = 4)
private String materialName;
/**
* 物料编码
*/
@Schema(description = "物料编码")
@ExcelProperty(value = "物料编码", order = 5)
private String encoding;
/**
* 物料单位重量
*/
@Schema(description = "物料单位重量")
@ExcelProperty(value = "单位重量", order = 6)
private BigDecimal unitWeight;
/**
* 物料规格
* 物料直径
*/
@Schema(description = "物料规格")
private String materialSpec;
@Schema(description = "物料直径")
@ExcelProperty(value = "物料直径", order = 7)
private Double materialSpec;
/**
* 物料图片
*/
@Schema(description = "物料图片")
@ExcelProperty(value = "图片地址", order = 8)
private String photoUrl;
/**
* 计算总重量(标重)
*/
@Schema(description = "计算总重量(标重)")
@ExcelProperty(value = "标准总重量(g)", order = 9)
private BigDecimal totalCalculatedWeight;
/**
* 实际总重量
*/
@Schema(description = "实际总重量")
@ExcelProperty(value = "实际总重量(g)", order = 10)
private BigDecimal totalWeight;
/**
* 总数量
*/
@Schema(description = "总数量")
@ExcelProperty(value = "总数量", order = 11)
private int totalCount;
/**
* 任务工单详情信息列表
*/
@Schema(description = "任务工单详情信息列表")
@ExcelProperty(value = "任务工单详情信息列表", order = 12)
private List<WorkOrderInfoResp> workOrderInfos;
/**
* 批次
*/
@Schema(description = "批次")
@ExcelProperty(value = "批次", order = 12)
private String batch;
/**
* 标记号
*/
@Schema(description = "标记号")
@ExcelProperty(value = "标记号", order = 13)
private String mark;
}

View File

@@ -7,6 +7,7 @@ import top.wms.admin.weighManage.model.resp.WorkOrderInfoResp;
import top.wms.admin.weighManage.model.resp.WorkOrderResp;
import java.util.List;
import java.util.Map;
/**
* 任务工单信息业务接口
@@ -24,7 +25,7 @@ public interface WorkOrderService extends BaseService<WorkOrderResp, WorkOrderRe
*/
WorkOrderResp getDetail(Long id);
/**
/**
* 创建任务工单
*
* @param req 创建任务工单参数
@@ -41,9 +42,10 @@ public interface WorkOrderService extends BaseService<WorkOrderResp, WorkOrderRe
List<WorkOrderInfoResp> getWorkOrderInfos(Long id);
/**
* 校验物料数量和重量是否匹配
* 校验物料数量和重量是否匹配
*
* @param req
* @return
*/
int validateWeighing(WorkOrderReq req);
Map<String, String> validateWeighing(WorkOrderReq req);
}

View File

@@ -4,26 +4,34 @@ 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.IdUtil;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
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.wms.admin.common.context.UserContextHolder;
import top.wms.admin.material.mapper.MaterialInfoMapper;
import top.wms.admin.material.model.entity.MaterialInfoDO;
import top.wms.admin.system.service.ConfigService;
import top.wms.admin.materialType.mapper.MaterialTypeMapper;
import top.wms.admin.materialType.model.entity.MaterialTypeDO;
import top.wms.admin.weighManage.mapper.WorkOrderInfoMapper;
import top.wms.admin.weighManage.mapper.WorkOrderMapper;
import top.wms.admin.weighManage.model.entity.WorkOrderDO;
import top.wms.admin.weighManage.model.entity.WorkOrderInfoDO;
import top.wms.admin.weighManage.model.query.WorkOrderQuery;
import top.wms.admin.weighManage.model.resp.WorkOrderExportResp;
import top.wms.admin.weighManage.model.resp.WorkOrderInfoResp;
import top.wms.admin.weighManage.model.resp.WorkOrderResp;
import top.wms.admin.weighManage.model.req.WorkOrderInfoReq;
import top.wms.admin.weighManage.model.req.WorkOrderReq;
import top.wms.admin.weighManage.model.resp.WorkOrderInfoResp;
@@ -31,8 +39,8 @@ import top.wms.admin.weighManage.model.resp.WorkOrderResp;
import top.wms.admin.weighManage.service.WorkOrderService;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
* 任务工单信息业务实现
@@ -45,18 +53,23 @@ import java.util.List;
@RequiredArgsConstructor
public class WorkOrderServiceImpl extends BaseServiceImpl<WorkOrderMapper, WorkOrderDO, WorkOrderResp, WorkOrderResp, WorkOrderQuery, WorkOrderReq> implements WorkOrderService {
private final WorkOrderInfoMapper workOrderInfoMapper;
private final WorkOrderInfoMapper workOrderInfoMapper;
private final MaterialInfoMapper materialInfoMapper;
private final ConfigService configService;
private final MaterialInfoMapper materialInfoMapper;
private final MaterialTypeMapper materialTypeMapper;
@Override
public PageResp<WorkOrderResp> page(WorkOrderQuery query, PageQuery pageQuery) {
QueryWrapper<WorkOrderDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialName()), "m.material_name", query.getMaterialName());
queryWrapper.eq(null != query.getOrderNo(), "w.order_no", query.getOrderNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "w.batch", query.getBatch());
queryWrapper.eq(StrUtil.isNotBlank(query.getUserName()), "u.username", query.getUserName());
queryWrapper.eq(StrUtil.isNotBlank(query.getCarNo()), "u.card_no", query.getCarNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getEncoding()), "m.encoding", query.getEncoding());
queryWrapper.ge(null != query.getStartDate(), "w.create_time", query.getStartDate());
queryWrapper.le(null != query.getEndDate(), "w.create_time", query.getEndDate());
this.sort(queryWrapper, pageQuery);
IPage<WorkOrderResp> page = baseMapper.selectWorkOrderPage(new Page<>(pageQuery.getPage(), pageQuery
@@ -65,6 +78,55 @@ private final ConfigService configService;
return PageResp.build(page);
}
@Override
public void export(WorkOrderQuery query, SortQuery sortQuery, HttpServletResponse response) {
QueryWrapper<WorkOrderDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialName()), "m.material_name", query.getMaterialName());
queryWrapper.eq(null != query.getOrderNo(), "w.order_no", query.getOrderNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "w.batch", query.getBatch());
queryWrapper.eq(StrUtil.isNotBlank(query.getUserName()), "u.username", query.getUserName());
queryWrapper.eq(StrUtil.isNotBlank(query.getCarNo()), "u.card_no", query.getCarNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getEncoding()), "m.encoding", query.getEncoding());
queryWrapper.ge(null != query.getStartDate(), "w.create_time", query.getStartDate());
queryWrapper.le(null != query.getEndDate(), "w.create_time", query.getEndDate());
queryWrapper.orderByDesc("w.id");
List<WorkOrderResp> workOrders = baseMapper.selectExport(queryWrapper);
Map<Long, List<WorkOrderInfoResp>> collect = new HashMap<>();
if (CollUtil.isNotEmpty(workOrders)) {
List<Long> ids = workOrders.stream().map(WorkOrderResp::getId).collect(Collectors.toList());
List<WorkOrderInfoDO> workOrderInfos = workOrderInfoMapper.selectList(new QueryWrapper<WorkOrderInfoDO>()
.in("work_order_id", ids));
List<WorkOrderInfoResp> workOrderInfoResps = BeanUtil.copyToList(workOrderInfos, WorkOrderInfoResp.class);
collect = workOrderInfoResps.stream().collect(Collectors.groupingBy(WorkOrderInfoResp::getWorkOrderId));
}
List<WorkOrderExportResp> exportList = new ArrayList<>();
for (WorkOrderResp workOrder : workOrders) {
List<WorkOrderInfoResp> infos = collect.get(workOrder.getId());
WorkOrderExportResp mainExport = new WorkOrderExportResp();
BeanUtil.copyProperties(workOrder, mainExport);
mainExport.setInfoBatch(workOrder.getBatch());
mainExport.setInfoMark(workOrder.getMark());
exportList.add(mainExport);
if (CollUtil.isNotEmpty(infos)) {
for (WorkOrderInfoResp info : infos) {
WorkOrderExportResp infoExport = new WorkOrderExportResp();
infoExport.setOrderNo(workOrder.getOrderNo());
infoExport.setQuantity(info.getQuantity());
infoExport.setCalculatedWeight(info.getCalculatedWeight());
infoExport.setWeightQuantity(info.getWeightQuantity());
infoExport.setWeight(info.getWeight());
infoExport.setImgUrl(info.getImgUrl());
infoExport.setInfoBatch(info.getBatch());
infoExport.setInfoMark(info.getMark());
exportList.add(infoExport);
}
}
}
ExcelUtils.export(exportList, "任务工单导出", WorkOrderExportResp.class, response);
}
@Override
public WorkOrderResp getDetail(Long id) {
WorkOrderDO workOrderDO = baseMapper.selectById(id);
@@ -74,16 +136,11 @@ private final ConfigService configService;
workOrderResp.setMaterialName(materialInfoDO.getMaterialName());
workOrderResp.setEncoding(materialInfoDO.getEncoding());
}
List<WorkOrderInfoDO> workOrderInfos = workOrderInfoMapper.selectList(new QueryWrapper<WorkOrderInfoDO>().eq("work_order_id", id));
if (CollUtil.isNotEmpty(workOrderInfos)) {
BigDecimal bigDecimal = new BigDecimal("0");
for (WorkOrderInfoDO workOrderInfoDO : workOrderInfos) {
bigDecimal = bigDecimal.add(workOrderInfoDO.getCalculatedWeight());
}
workOrderResp.setTotalCalculatedWeight(bigDecimal);
List<WorkOrderInfoDO> infos = workOrderInfoMapper.selectList(new QueryWrapper<WorkOrderInfoDO>()
.eq("work_order_id", id));
if (CollUtil.isNotEmpty(infos)) {
workOrderResp.setWorkOrderInfos(BeanUtil.copyToList(infos, WorkOrderInfoResp.class));
}
return workOrderResp;
}
@@ -92,17 +149,19 @@ private final ConfigService configService;
return baseMapper.getDetail(id);
}
@Override
public void afterDelete(List<Long> ids) {
workOrderInfoMapper.delete(new QueryWrapper<WorkOrderInfoDO>().in("work_order_id", ids));
}
@Override
public WorkOrderResp addWorKerOrder(WorkOrderReq req) {
if (CollUtil.isEmpty(req.getWorkOrderInfos())) {
throw new IllegalArgumentException("称重列表不能为空");
throw new BusinessException("称重列表不能为空");
}
if (StrUtil.isBlank(req.getWorkOrderInfos().get(0).getMark())) {
throw new BusinessException("标记号不能为空");
}
BigDecimal totalWeight = new BigDecimal("0");
@@ -112,58 +171,79 @@ private final ConfigService configService;
totalCount += workOrderInfoReq.getQuantity();
}
MaterialInfoDO materialInfoDO = materialInfoMapper.selectById(req.getMaterialId());
WorkOrderDO workOrder = new WorkOrderDO();
// 生成纯数字订单号:年月日时分秒 + 6位随机数
String timestamp = DateUtil.format(new Date(), "yyyyMMddHHmmss");
String randomNum = String.format("%06d", (int)(Math.random() * 1000000));
workOrder.setOrderNo(timestamp + randomNum);
String title = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_PATTERN) + "-"
+ UserContextHolder.getUsername() + "-" + req.getMaterialName();
String title = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_PATTERN) + "-" + UserContextHolder
.getUsername() + "-" + materialInfoDO.getMaterialName();
workOrder.setTitle(title);
workOrder.setMaterialId(req.getMaterialId());
workOrder.setBatch(materialInfoDO.getBatch());
workOrder.setMark(req.getWorkOrderInfos().get(0).getMark());
workOrder.setTotalWeight(totalWeight);
workOrder.setTotalCount(totalCount);
workOrder.setTotalCalculatedWeight(materialInfoDO.getUnitWeight().multiply(BigDecimal.valueOf(totalCount)));
baseMapper.insert(workOrder);
//新增工单详情列表信息
List<WorkOrderInfoDO> workOrderInfoDOList = BeanUtil.copyToList(req.getWorkOrderInfos(), WorkOrderInfoDO.class);
workOrderInfoDOList.forEach(workOrderInfoDO -> workOrderInfoDO.setWorkOrderId(workOrder.getId()));
workOrderInfoDOList.forEach(workOrderInfoDO -> {
workOrderInfoDO.setWorkOrderId(workOrder.getId());
workOrderInfoDO.setBatch(materialInfoDO.getBatch());
});
workOrderInfoMapper.insertBatch(workOrderInfoDOList);
return BeanUtil.copyProperties(workOrder, WorkOrderResp.class);
}
@Override
public int validateWeighing(WorkOrderReq req) {
public Map<String, String> validateWeighing(WorkOrderReq req) {
Map<String, String> map = new HashMap<>();
map.put("code", "200");
log.info("validateWeighing req:{}", req);
String configValue = configService.getConfigValue("weight_float_ratio");
BigDecimal weightFloat = new BigDecimal("0.06");
if (StrUtil.isNotBlank(configValue)) {
weightFloat = new BigDecimal(configValue);
}
//计算标准重量
MaterialInfoDO materialInfoDO = materialInfoMapper.selectById(req.getMaterialId());
BigDecimal standardWeight = materialInfoDO.getUnitWeight().subtract(new BigDecimal(req.getInputQuantity()));
CheckUtils.throwIf((materialInfoDO == null || null == materialInfoDO.getMaterialTypeId()), "该物料信息不全,请先维护!");
MaterialTypeDO materialTypeDO = materialTypeMapper.selectById(materialInfoDO.getMaterialTypeId());
CheckUtils.throwIf((materialTypeDO == null || null == materialTypeDO
.getDownFloatRatio() || null == materialTypeDO.getUpFloatRatio()), "该物料品类信息不全,请先维护!");
//下行浮动范围
BigDecimal downFloatRatio = materialTypeDO.getDownFloatRatio();
//上行浮动范围
BigDecimal upFloatRatio = materialTypeDO.getUpFloatRatio();
//计算标准重量
BigDecimal standardWeight = new BigDecimal(req.getCalculatedWeight());
//电子秤重量
BigDecimal electronicWeight = new BigDecimal(req.getAhDeviceWeight());
// 检查 electronicWeight 是否大于 standardWeight
if (electronicWeight.compareTo(standardWeight) <= 0) {
log.error("电子秤重量必须大于标准重量");
return 500; // 电子秤重量必须大于标准重量
}
// 计算比值:(electronicWeight - standardWeight) / standardWeight
// 计算比值:(实重 - 标重) / 标重
BigDecimal weightDifference = electronicWeight.subtract(standardWeight);
BigDecimal ratio = weightDifference.divide(standardWeight, 4, BigDecimal.ROUND_HALF_UP);
// 检查比值是否超过 6%
if (ratio.compareTo(weightFloat) > 0) {
log.error("比值超过 6%,当前比值: {}", ratio);
return 500; // 比值超过 6%
BigDecimal ratio = weightDifference.divide(standardWeight, 4, BigDecimal.ROUND_HALF_UP)
.multiply(BigDecimal.valueOf(100));
log.info("ratio当前比值:{}%", ratio);
// 检查比值是否超过上行浮动范围
if (ratio.compareTo(upFloatRatio) > 0) {
log.info("比值超过 {}%", upFloatRatio);
map.put("code", "502");
map.put("msg", "数量过多,请减少");
return map;
}
return 200; // 验证通过
// 检查比值是否超过下行浮动范围
if (ratio.compareTo(downFloatRatio) < 0) {
log.info("比值超过下限 {}%", downFloatRatio);
map.put("code", "501");
map.put("msg", "数量不足,请添加");
return map;
}
return map;
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.wms.admin.fullWorkOrder.mapper.FullWorkOrderMapper">
<select id="selectFullWorkOrderPage" resultType="top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp">
select
f.*,
m.material_name materialName,
u.username createUserString
from
sys_full_work_order f
left join sys_material_info m on f.material_code = m.encoding
left join sys_user u on f.create_user = u.id
${ew.customSqlSegment}
</select>
<select id="selectFullWorkOrderExport" resultType="top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp">
</select>
</mapper>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.wms.admin.materialProcess.mapper.MaterialProcessMapper">
<!-- 按流程名称批量更新 -->
<update id="updateByName">
UPDATE sys_material_process
<trim prefix="SET" suffixOverrides=",">
<if test="list != null and list.size() > 0">
process_code = CASE
<foreach collection="list" item="item" separator="">
<if test="item.processCode != null and item.processCode != ''">
WHEN process_name = #{item.processName} THEN #{item.processCode}
</if>
</foreach>
ELSE process_code
END,
update_user = CASE
<foreach collection="list" item="item" separator="">
<if test="item.updateUser != null">
WHEN process_name = #{item.processName} THEN #{item.updateUser}
</if>
</foreach>
ELSE update_user
END,
update_time = NOW()
</if>
</trim>
WHERE process_name IN
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item.processName}
</foreach>
</update>
<update id="updateByCode">
UPDATE sys_material_process
<trim prefix="SET" suffixOverrides=",">
<if test="list != null and list.size() > 0">
process_name = CASE
<foreach collection="list" item="item" separator="">
<if test="item.processName != null and item.processName != ''">
WHEN process_code = #{item.processCode} THEN #{item.processName}
</if>
</foreach>
ELSE process_name
END,
update_user = CASE
<foreach collection="list" item="item" separator="">
<if test="item.updateUser != null">
WHEN process_code = #{item.processCode} THEN #{item.updateUser}
</if>
</foreach>
ELSE update_user
END,
update_time = NOW()
</if>
</trim>
WHERE process_code IN
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item.processCode}
</foreach>
</update>
<select id="getCodeByMaterial" resultType="java.lang.String">
SELECT process_code
FROM sys_material_process p
left join sys_material_info m on p.id = m.material_process_id
WHERE m.encoding = #{materialCode}
</select>
</mapper>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.wms.admin.materialType.mapper.MaterialTypeMapper">
</mapper>

View File

@@ -4,49 +4,129 @@
<!-- 按物料名称批量更新 -->
<update id="updateByName">
<foreach collection="list" item="item" separator=";">
UPDATE sys_material_info
SET
<if test="item.encoding != null and item.encoding != ''" >
encoding = #{item.encoding},
UPDATE sys_material_info
<trim prefix="SET" suffixOverrides=",">
<if test="list != null and list.size() > 0">
encoding = CASE
<foreach collection="list" item="item" separator="">
<if test="item.encoding != null and item.encoding != ''">
WHEN material_name = #{item.materialName} THEN #{item.encoding}
</if>
</foreach>
ELSE encoding
END,
unit_weight = CASE
<foreach collection="list" item="item" separator="">
<if test="item.unitWeight != null">
WHEN material_name = #{item.materialName} THEN #{item.unitWeight}
</if>
</foreach>
ELSE unit_weight
END,
update_user = CASE
<foreach collection="list" item="item" separator="">
<if test="item.updateUser != null">
WHEN material_name = #{item.materialName} THEN #{item.updateUser}
</if>
</foreach>
ELSE update_user
END,
update_time = NOW()
</if>
<if test="item.unitWeight != null">
unit_weight = #{item.unitWeight},
</if>
<if test="item.materialSpec != null and item.materialSpec != ''">
material_spec = #{item.materialSpec},
</if>
<if test="item.updateTime != null">
update_time = NOW(),
</if>
<if test="item.updateUser != null and item.updateUser != ''">
update_user = #{item.updateUser}
</if>
WHERE material_name = #{item.materialName}
</trim>
WHERE material_name IN
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item.materialName}
</foreach>
</update>
<update id="updateByCode">
<foreach collection="list" item="item" separator=";">
UPDATE sys_material_info
SET
<if test="item.materialName != null and item.materialName != ''">
material_name = #{item.materialName},
UPDATE sys_material_info
<trim prefix="SET" suffixOverrides=",">
<if test="list != null and list.size() > 0">
material_name = CASE
<foreach collection="list" item="item" separator="">
<if test="item.materialName != null and item.materialName != ''">
WHEN encoding = #{item.encoding} THEN #{item.materialName}
</if>
</foreach>
ELSE material_name
END,
unit_weight = CASE
<foreach collection="list" item="item" separator="">
<if test="item.unitWeight != null">
WHEN encoding = #{item.encoding} THEN #{item.unitWeight}
</if>
</foreach>
ELSE unit_weight
END,
update_user = CASE
<foreach collection="list" item="item" separator="">
<if test="item.updateUser != null">
WHEN encoding = #{item.encoding} THEN #{item.updateUser}
</if>
</foreach>
ELSE update_user
END,
update_time = NOW()
</if>
<if test="item.unitWeight != null">
unit_weight = #{item.unitWeight},
</if>
<if test="item.materialSpec != null and item.materialSpec != ''">
material_spec = #{item.materialSpec},
</if>
<if test="item.updateTime != null">
update_time = NOW(),
</if>
<if test="item.updateUser != null and item.updateUser != ''">
update_user = #{item.updateUser}
</if>
WHERE encoding = #{item.encoding}
</trim>
WHERE encoding IN
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item.encoding}
</foreach>
</update>
</mapper>
<update id="updateBatchNull">
UPDATE sys_material_info SET batch = null
</update>
<update id="updateBatchByCode">
UPDATE sys_material_info
<trim prefix="SET" suffixOverrides=",">
<if test="list != null and list.size() > 0">
batch = CASE
<foreach collection="list" item="item" separator="">
<if test="item.batch != null and item.batch != ''">
WHEN encoding = #{item.encoding} THEN #{item.batch}
</if>
</foreach>
ELSE batch
END,
update_user = CASE
<foreach collection="list" item="item" separator="">
<if test="item.updateUser != null">
WHEN encoding = #{item.encoding} THEN #{item.updateUser}
</if>
</foreach>
ELSE update_user
END,
update_time = NOW()
</if>
</trim>
WHERE encoding IN
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item.encoding}
</foreach>
</update>
<select id="selectMaterialInfoPage" resultType="top.wms.admin.material.model.resp.MaterialInfoResp">
SELECT
mi.*,
mt.type_name typeName
FROM
sys_material_info mi
left join sys_material_type mt on mi.material_type_id = mt.id
${ew.customSqlSegment}
</select>
<select id="selectMaterialInfoExport" resultType="top.wms.admin.material.model.resp.MaterialInfoResp">
SELECT
mi.*,
mt.type_name typeName
FROM
sys_material_info mi
left join sys_material_type mt on mi.material_type_id = mt.id
${ew.customSqlSegment}
</select>
</mapper>

View File

@@ -26,4 +26,19 @@
where wi.work_order_id = #{id}
</select>
<select id="selectExport" resultType="top.wms.admin.weighManage.model.resp.WorkOrderResp">
select
w.*,
m.material_name,
m.encoding,
m.photo_url,
m.unit_weight,
u.card_no,
u.username createUserString
from sys_work_order w
left join sys_material_info m on w.material_id = m.id
left join sys_user u on w.create_user = u.id
${ew.customSqlSegment}
</select>
</mapper>

View File

@@ -1,26 +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-plugin</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>
<dependencies>
<dependency>
<groupId>top.wms</groupId>
<artifactId>wms-common</artifactId>
</dependency>
</dependencies>
</project>

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