diff --git a/pom.xml b/pom.xml
index 632ab9e5..e6c4b20a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,6 +21,7 @@
yudao-module-mp
yudao-module-mall
zn-module-smartfactory
+ yudao-module-wms
${project.artifactId}
@@ -172,4 +173,4 @@
-
\ No newline at end of file
+
diff --git a/yudao-module-wms/pom.xml b/yudao-module-wms/pom.xml
new file mode 100644
index 00000000..0a917531
--- /dev/null
+++ b/yudao-module-wms/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ cn.iocoder.cloud
+ yudao
+ ${revision}
+
+ 4.0.0
+
+ yudao-module-wms-api
+ yudao-module-wms-biz
+
+ yudao-module-wms
+ pom
+
+ ${project.artifactId}
+
+ infra 模块,主要提供两块能力:
+ 1. 我们放基础设施的运维与管理,支撑上层的通用与核心业务。 例如说:定时任务的管理、服务器的信息等等
+ 2. 研发工具,提升研发效率与质量。 例如说:代码生成器、接口文档等等
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-api/pom.xml b/yudao-module-wms/yudao-module-wms-api/pom.xml
new file mode 100644
index 00000000..cb1847b6
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-api/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-wms
+ ${revision}
+
+ 4.0.0
+ yudao-module-wms-api
+ jar
+
+ ${project.artifactId}
+
+ infra 模块 API,暴露给其它模块调用
+
+
+
+
+ cn.iocoder.cloud
+ yudao-common
+
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ provided
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ true
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+ true
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/package-info.java b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/package-info.java
new file mode 100644
index 00000000..299f681f
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/api/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * infra API 包,定义暴露给其它模块的 API
+ */
+package cn.iocoder.yudao.module.wms.api;
diff --git a/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/ApiConstants.java b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/ApiConstants.java
new file mode 100644
index 00000000..3d534436
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/ApiConstants.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.wms.enums;
+
+import cn.iocoder.yudao.framework.common.enums.RpcConstants;
+
+/**
+ * API 相关的枚举
+ */
+public class ApiConstants {
+
+ /**
+ * 服务名
+ *
+ * 注意,需要保证和 spring.application.name 保持一致
+ */
+ public static final String NAME = "wms-server";
+
+ public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/wms";
+
+ public static final String VERSION = "1.0.0";
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
new file mode 100644
index 00000000..2a0e3085
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.wms.enums;
+
+/**
+ * Infra 字典类型的枚举类
+ *
+
+ */
+public interface DictTypeConstants {
+
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
new file mode 100644
index 00000000..f4764657
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-api/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.wms.enums;
+
+/**
+ * Infra 错误码枚举类
+ *
+ * infra 系统,使用 1-001-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/Dockerfile b/yudao-module-wms/yudao-module-wms-biz/Dockerfile
new file mode 100644
index 00000000..578cba76
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/Dockerfile
@@ -0,0 +1,19 @@
+## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
+## 感谢复旦核博士的建议!灰子哥,牛皮!
+FROM eclipse-temurin:8-jre
+
+## 创建目录,并使用它作为工作目录
+RUN mkdir -p /yudao-module-infra-biz
+WORKDIR /yudao-module-infra-biz
+## 将后端项目的 Jar 文件,复制到镜像中
+COPY ./target/yudao-module-infra-biz.jar app.jar
+
+## 设置 TZ 时区
+## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
+ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
+
+## 暴露后端项目的 48080 端口
+EXPOSE 48082
+
+## 启动后端项目
+CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
diff --git a/yudao-module-wms/yudao-module-wms-biz/pom.xml b/yudao-module-wms/yudao-module-wms-biz/pom.xml
new file mode 100644
index 00000000..1a44b3d5
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/pom.xml
@@ -0,0 +1,170 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-wms
+ ${revision}
+
+ 4.0.0
+ yudao-module-wms-biz
+ jar
+
+ ${project.artifactId}
+
+ infra 模块,主要提供两块能力:
+ 1. 我们放基础设施的运维与管理,支撑上层的通用与核心业务。 例如说:定时任务的管理、服务器的信息等等
+ 2. 研发工具,提升研发效率与质量。 例如说:代码生成器、接口文档等等
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-wms-api
+ ${revision}
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-banner
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-operatelog
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-tenant
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-error-code
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-security
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-websocket
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mybatis
+
+
+ com.baomidou
+ mybatis-plus-generator
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-redis
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-rpc
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-job
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mq
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-test
+ test
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-excel
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+
+
+
+ cn.smallbun.screw
+ screw-core
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-monitor
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-file
+
+
+
+
+
+ ${project.artifactId}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/WmsServerApplication.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/WmsServerApplication.java
new file mode 100644
index 00000000..97ecf3a8
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/WmsServerApplication.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.wms;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * 项目的启动类
+ *
+ *
+ *
+ *
+ *
+
+ */
+@SpringBootApplication
+public class WmsServerApplication {
+
+ public static void main(String[] args) {
+ //
+ //
+ //
+
+ SpringApplication.run(WmsServerApplication.class, args);
+
+ //
+ //
+ //
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/api/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/api/package-info.java
new file mode 100644
index 00000000..b8122700
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/api/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.wms.api;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/RedisController.http b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/RedisController.http
new file mode 100644
index 00000000..8a0e70fd
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/RedisController.http
@@ -0,0 +1,4 @@
+### 请求 /infra/redis/get-monitor-info 接口 => 成功
+GET {{baseUrl}}/infra/redis/get-monitor-info
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/RedisController.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/RedisController.java
new file mode 100644
index 00000000..76cc7ec3
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/RedisController.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.wms.controller.admin.redis;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.wms.controller.admin.redis.vo.RedisMonitorRespVO;
+import cn.iocoder.yudao.module.wms.convert.redis.RedisConvert;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.data.redis.connection.RedisServerCommands;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Properties;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - Redis 监控")
+@RestController
+@RequestMapping("/infra/redis")
+public class RedisController {
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ @GetMapping("/get-monitor-info")
+ @Operation(summary = "获得 Redis 监控信息")
+ @PreAuthorize("@ss.hasPermission('infra:redis:get-monitor-info')")
+ public CommonResult getRedisMonitorInfo() {
+ // 获得 Redis 统计信息
+ Properties info = stringRedisTemplate.execute((RedisCallback) RedisServerCommands::info);
+ Long dbSize = stringRedisTemplate.execute(RedisServerCommands::dbSize);
+ Properties commandStats = stringRedisTemplate.execute((
+ RedisCallback) connection -> connection.info("commandstats"));
+ assert commandStats != null; // 断言,避免警告
+ // 拼接结果返回
+ return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats));
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/vo/RedisMonitorRespVO.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/vo/RedisMonitorRespVO.java
new file mode 100644
index 00000000..5cb6f166
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/redis/vo/RedisMonitorRespVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.wms.controller.admin.redis.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Properties;
+
+@Schema(description = "管理后台 - Redis 监控信息 Response VO")
+@Data
+@Builder
+@AllArgsConstructor
+public class RedisMonitorRespVO {
+
+ @Schema(description = "Redis info 指令结果,具体字段,查看 Redis 文档", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Properties info;
+
+ @Schema(description = "Redis key 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long dbSize;
+
+ @Schema(description = "CommandStat 数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List commandStats;
+
+ @Schema(description = "Redis 命令统计结果")
+ @Data
+ @Builder
+ @AllArgsConstructor
+ public static class CommandStat {
+
+ @Schema(description = "Redis 命令", requiredMode = Schema.RequiredMode.REQUIRED, example = "get")
+ private String command;
+
+ @Schema(description = "调用次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long calls;
+
+ @Schema(description = "消耗 CPU 秒数", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
+ private Long usec;
+
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/app/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/app/package-info.java
new file mode 100644
index 00000000..fa43e8b6
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/app/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.wms.controller.app;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/package-info.java
new file mode 100644
index 00000000..c75d9df6
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/controller/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供 RESTful API 给前端:
+ * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目
+ * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
+ */
+package cn.iocoder.yudao.module.wms.controller;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/package-info.java
new file mode 100644
index 00000000..d5c4aaf1
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.yudao.module.wms.convert;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/redis/RedisConvert.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/redis/RedisConvert.java
new file mode 100644
index 00000000..9ef41d70
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/redis/RedisConvert.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.wms.convert.redis;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.wms.controller.admin.redis.vo.RedisMonitorRespVO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.ArrayList;
+import java.util.Properties;
+
+@Mapper
+public interface RedisConvert {
+
+ RedisConvert INSTANCE = Mappers.getMapper(RedisConvert.class);
+
+ default RedisMonitorRespVO build(Properties info, Long dbSize, Properties commandStats) {
+ RedisMonitorRespVO respVO = RedisMonitorRespVO.builder().info(info).dbSize(dbSize)
+ .commandStats(new ArrayList<>(commandStats.size())).build();
+ commandStats.forEach((key, value) -> {
+ respVO.getCommandStats().add(RedisMonitorRespVO.CommandStat.builder()
+ .command(StrUtil.subAfter((String) key, "cmdstat_", false))
+ .calls(Long.valueOf(StrUtil.subBetween((String) value, "calls=", ",")))
+ .usec(Long.valueOf(StrUtil.subBetween((String) value, "usec=", ",")))
+ .build());
+ });
+ return respVO;
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
new file mode 100644
index 00000000..8153487b
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
@@ -0,0 +1 @@
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/enums/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/enums/package-info.java
new file mode 100644
index 00000000..2c311335
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/enums/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.wms.enums;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/codegen/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/codegen/package-info.java
new file mode 100644
index 00000000..ce320958
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/codegen/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 代码生成器
+ */
+package cn.iocoder.yudao.module.wms.framework.codegen;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/config/AdminServerConfiguration.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/config/AdminServerConfiguration.java
new file mode 100644
index 00000000..d7f5d1e3
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/config/AdminServerConfiguration.java
@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.module.wms.framework.monitor.config;
+
+import de.codecentric.boot.admin.server.config.EnableAdminServer;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@EnableAdminServer
+public class AdminServerConfiguration {
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/package-info.java
new file mode 100644
index 00000000..810c98dc
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 使用 Spring Boot Admin 实现简单的监控平台
+ */
+package cn.iocoder.yudao.module.wms.framework.monitor;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/《芋道 Spring Boot 监控工具 Admin 入门》.md b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/《芋道 Spring Boot 监控工具 Admin 入门》.md
new file mode 100644
index 00000000..a1e36768
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/monitor/《芋道 Spring Boot 监控工具 Admin 入门》.md
@@ -0,0 +1 @@
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/package-info.java
new file mode 100644
index 00000000..2ea1946a
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 infra 模块的 framework 封装
+ *
+
+ */
+package cn.iocoder.yudao.module.wms.framework;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/rpc/config/RpcConfiguration.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/rpc/config/RpcConfiguration.java
new file mode 100644
index 00000000..034d8457
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/rpc/config/RpcConfiguration.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.wms.framework.rpc.config;
+
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.PostApi;
+import cn.iocoder.yudao.module.system.api.equipment.AttendanceMachineApi;
+import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@EnableFeignClients(clients = { })
+public class RpcConfiguration {
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/rpc/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/rpc/package-info.java
new file mode 100644
index 00000000..39346d8c
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/rpc/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.wms.framework.rpc;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/security/config/SecurityConfiguration.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/security/config/SecurityConfiguration.java
new file mode 100644
index 00000000..68ecd4a1
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/security/config/SecurityConfiguration.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.wms.framework.security.config;
+
+import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
+import cn.iocoder.yudao.module.infra.enums.ApiConstants;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+
+/**
+ * Infra 模块的 Security 配置
+ */
+@Configuration(proxyBeanMethods = false, value = "infraSecurityConfiguration")
+public class SecurityConfiguration {
+
+ @Value("${spring.boot.admin.context-path:''}")
+ private String adminSeverContextPath;
+
+ @Bean("infraAuthorizeRequestsCustomizer")
+ public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
+ return new AuthorizeRequestsCustomizer() {
+
+ @Override
+ public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) {
+ // Swagger 接口文档
+ registry.antMatchers("/v3/api-docs/**").permitAll() // 元数据
+ .antMatchers("/swagger-ui.html").permitAll(); // Swagger UI
+ // Spring Boot Actuator 的安全配置
+ registry.antMatchers("/actuator").anonymous()
+ .antMatchers("/actuator/**").anonymous();
+ // Druid 监控
+ registry.antMatchers("/druid/**").anonymous();
+ // Spring Boot Admin Server 的安全配置
+ registry.antMatchers(adminSeverContextPath).anonymous()
+ .antMatchers(adminSeverContextPath + "/**").anonymous();
+ // 文件读取
+ registry.antMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll();
+
+ // TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案
+ // RPC 服务的安全配置
+ registry.antMatchers(ApiConstants.PREFIX + "/**").permitAll();
+ }
+
+ };
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/security/core/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/security/core/package-info.java
new file mode 100644
index 00000000..0c573aa6
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/framework/security/core/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.wms.framework.security.core;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/job/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/job/package-info.java
new file mode 100644
index 00000000..79a140a7
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/job/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位,无特殊含义
+ */
+package cn.iocoder.yudao.module.wms.job;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/consumer/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/consumer/package-info.java
new file mode 100644
index 00000000..fda7b30a
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/consumer/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 消息队列的消费者
+ */
+package cn.iocoder.yudao.module.wms.mq.consumer;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/message/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/message/package-info.java
new file mode 100644
index 00000000..e8404c0a
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/message/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 消息队列的消息
+ */
+package cn.iocoder.yudao.module.wms.mq.message;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/producer/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/producer/package-info.java
new file mode 100644
index 00000000..69baf4eb
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/mq/producer/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 消息队列的生产者
+ */
+package cn.iocoder.yudao.module.wms.mq.producer;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/package-info.java b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/package-info.java
new file mode 100644
index 00000000..14eb95dd
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/java/cn/iocoder/yudao/module/wms/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * infra 模块,主要提供两块能力:
+ * 1. 我们放基础设施的运维与管理,支撑上层的通用与核心业务。 例如说:定时任务的管理、服务器的信息等等
+ * 2. 研发工具,提升研发效率与质量。 例如说:代码生成器、接口文档等等
+ *
+ * 1. Controller URL:以 /infra/ 开头,避免和其它 Module 冲突
+ * 2. DataObject 表名:以 infra_ 开头,方便在数据库中区分
+ */
+package cn.iocoder.yudao.module.wms;
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-dev.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-dev.yaml
new file mode 100644
index 00000000..3f827691
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-dev.yaml
@@ -0,0 +1,130 @@
+--- #################### 数据库相关配置 ####################
+spring:
+ # 数据源配置项
+ autoconfigure:
+ exclude:
+ - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
+ datasource:
+ druid: # Druid 【监控】相关的全局配置
+ web-stat-filter:
+ enabled: true
+ stat-view-servlet:
+ enabled: true
+ allow: # 设置白名单,不填则允许所有访问
+ url-pattern: /druid/*
+ login-username: # 控制台管理用户名和密码
+ login-password:
+ filter:
+ stat:
+ enabled: true
+ log-slow-sql: true # 慢 SQL 记录
+ slow-sql-millis: 100
+ merge-sql: true
+ wall:
+ config:
+ multi-statement-allow: true
+ dynamic: # 多数据源配置
+ druid: # Druid 【连接池】相关的全局配置
+ initial-size: 5 # 初始连接数
+ min-idle: 10 # 最小连接池数量
+ max-active: 20 # 最大连接池数量
+ max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+ time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+ min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+ max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+ validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ primary: master
+ datasource:
+ master:
+ name: ruoyi-vue-pro
+ url: jdbc:mysql://192.168.1.105:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+ driver-class-name: com.mysql.jdbc.Driver
+ username: root
+ password: yhtkj@2024!
+ slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
+ name: ruoyi-vue-pro
+ url: jdbc:mysql://192.168.1.105:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+ driver-class-name: com.mysql.jdbc.Driver
+ username: root
+ password: yhtkj@2024!
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ redis:
+ host: 192.168.1.105 # 地址
+ port: 6379 # 端口
+ database: 1 # 数据库索引
+ password: yhtkj@2024! # 密码,建议生产环境开启
+
+--- #################### MQ 消息队列相关配置 ####################
+
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: 127.0.0.1:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: guest # RabbitMQ 服务的账号
+ password: guest # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+--- #################### 定时任务相关配置 ####################
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# Spring Boot Admin 配置项
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ instance:
+ service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
+ # Spring Boot Admin Server 服务端的相关配置
+ context-path: /admin # 配置 Spring
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ env: # 多环境的配置项
+ tag: ${HOSTNAME}
+ security:
+ mock-enable: true
+ xss:
+ enable: false
+ exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+ - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
+ - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
+ access-log: # 访问日志的配置项
+ enable: false
+ error-code: # 错误码相关配置项
+ enable: false
+ demo: false # 关闭演示模式
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-local.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-local.yaml
new file mode 100644
index 00000000..83fce03f
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-local.yaml
@@ -0,0 +1,152 @@
+--- #################### 数据库相关配置 ####################
+spring:
+
+ # 数据源配置项
+ autoconfigure:
+ exclude:
+ - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
+ - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
+ - de.codecentric.boot.admin.server.cloud.config.AdminServerDiscoveryAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
+ - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
+ - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
+ datasource:
+ druid: # Druid 【监控】相关的全局配置
+ web-stat-filter:
+ enabled: true
+ stat-view-servlet:
+ enabled: true
+ allow: # 设置白名单,不填则允许所有访问
+ url-pattern: /druid/*
+ login-username: # 控制台管理用户名和密码
+ login-password:
+ filter:
+ stat:
+ enabled: true
+ log-slow-sql: true # 慢 SQL 记录
+ slow-sql-millis: 100
+ merge-sql: true
+ wall:
+ config:
+ multi-statement-allow: true
+ dynamic: # 多数据源配置
+ druid: # Druid 【连接池】相关的全局配置
+ initial-size: 1 # 初始连接数
+ min-idle: 1 # 最小连接池数量
+ max-active: 20 # 最大连接池数量
+ max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+ time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+ min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+ max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+ validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ primary: master
+ datasource:
+ master:
+ name: ruoyi-vue-pro
+ url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+ # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
+ # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
+ # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+ # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
+ username: root
+ password: root
+ # username: sa
+ # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+ slave: # 模拟从库,可根据自己需要修改
+ name: ruoyi-vue-pro
+ url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+ # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
+ # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
+ # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+ # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
+ username: root
+ password: root
+ # username: sa
+ # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ redis:
+ host: 127.0.0.1 # 地址
+ port: 6379 # 端口
+ database: 0 # 数据库索引
+# password: 123456 # 密码,建议生产环境开启
+
+--- #################### MQ 消息队列相关配置 ####################
+
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: 127.0.0.1:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: guest # RabbitMQ 服务的账号
+ password: guest # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+--- #################### 定时任务相关配置 ####################
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# Spring Boot Admin 配置项
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ instance:
+ service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
+ # Spring Boot Admin Server 服务端的相关配置
+ context-path: /admin # 配置 Spring
+
+# 日志文件配置
+logging:
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ cn.iocoder.yudao.module.wms.dal.mysql: debug
+ cn.iocoder.yudao.module.wms.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ env: # 多环境的配置项
+ tag: ${HOSTNAME}
+ security:
+ mock-enable: true
+ xss:
+ enable: false
+ exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+ - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
+ - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
+ access-log: # 访问日志的配置项
+ enable: false
+ error-code: # 错误码相关配置项
+ enable: false
+ demo: false # 关闭演示模式
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-prod.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-prod.yaml
new file mode 100644
index 00000000..fcff149d
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application-prod.yaml
@@ -0,0 +1,152 @@
+--- #################### 数据库相关配置 ####################
+spring:
+
+ # 数据源配置项
+ autoconfigure:
+ exclude:
+ - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
+ - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
+ - de.codecentric.boot.admin.server.cloud.config.AdminServerDiscoveryAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
+ - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
+ - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
+ datasource:
+ druid: # Druid 【监控】相关的全局配置
+ web-stat-filter:
+ enabled: true
+ stat-view-servlet:
+ enabled: true
+ allow: # 设置白名单,不填则允许所有访问
+ url-pattern: /druid/*
+ login-username: # 控制台管理用户名和密码
+ login-password:
+ filter:
+ stat:
+ enabled: true
+ log-slow-sql: true # 慢 SQL 记录
+ slow-sql-millis: 100
+ merge-sql: true
+ wall:
+ config:
+ multi-statement-allow: true
+ dynamic: # 多数据源配置
+ druid: # Druid 【连接池】相关的全局配置
+ initial-size: 1 # 初始连接数
+ min-idle: 1 # 最小连接池数量
+ max-active: 20 # 最大连接池数量
+ max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
+ time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
+ min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
+ max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
+ validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
+ test-while-idle: true
+ test-on-borrow: false
+ test-on-return: false
+ primary: master
+ datasource:
+ master:
+ name: ruoyi-vue-pro
+ url: jdbc:mysql://rm-bp1yloyj508qld78jno.mysql.rds.aliyuncs.com:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+ # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
+ # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
+ # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+ # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
+ username: root
+ password: Znalyrds2024
+ # username: sa
+ # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+ slave: # 模拟从库,可根据自己需要修改
+ name: ruoyi-vue-pro
+ url: jdbc:mysql://rm-bp1yloyj508qld78jno.mysql.rds.aliyuncs.com:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
+ # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
+ # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
+ # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
+ # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
+ username: root
+ password: Znalyrds2024
+ # username: sa
+ # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ redis:
+ host: 127.0.0.1 # 地址
+ port: 6379 # 端口
+ database: 0 # 数据库索引
+ password: ZNredis2024! # 密码,建议生产环境开启
+
+--- #################### MQ 消息队列相关配置 ####################
+
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ name-server: 127.0.0.1:9876 # RocketMQ Namesrv
+
+spring:
+ # RabbitMQ 配置项,对应 RabbitProperties 配置类
+ rabbitmq:
+ host: 127.0.0.1 # RabbitMQ 服务的地址
+ port: 5672 # RabbitMQ 服务的端口
+ username: guest # RabbitMQ 服务的账号
+ password: guest # RabbitMQ 服务的密码
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
+
+--- #################### 定时任务相关配置 ####################
+xxl:
+ job:
+ enabled: false # 是否开启调度中心,默认为 true 开启
+ admin:
+ addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项
+lock4j:
+ acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
+ expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
+
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ web:
+ base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
+ exposure:
+ include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
+
+# Spring Boot Admin 配置项
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ instance:
+ service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
+ # Spring Boot Admin Server 服务端的相关配置
+ context-path: /admin # 配置 Spring
+
+# 日志文件配置
+logging:
+ level:
+ # 配置自己写的 MyBatis Mapper 打印日志
+ cn.iocoder.yudao.module.wms.dal.mysql: debug
+ cn.iocoder.yudao.module.wms.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ env: # 多环境的配置项
+ tag: ${HOSTNAME}
+ security:
+ mock-enable: true
+ xss:
+ enable: false
+ exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+ - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
+ - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
+ access-log: # 访问日志的配置项
+ enable: false
+ error-code: # 错误码相关配置项
+ enable: false
+ demo: false # 关闭演示模式
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application.yaml
new file mode 100644
index 00000000..c953f31d
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/application.yaml
@@ -0,0 +1,162 @@
+spring:
+ main:
+ allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
+ allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
+
+ # Servlet 配置
+ servlet:
+ # 文件上传相关配置项
+ multipart:
+ max-file-size: 16MB # 单个文件大小
+ max-request-size: 32MB # 设置总上传的文件大小
+ mvc:
+ pathmatch:
+ matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类
+
+ # Jackson 配置项
+ jackson:
+ serialization:
+ write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
+ write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
+ write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
+ fail-on-empty-beans: false # 允许序列化无属性的 Bean
+
+ # Cache 配置项
+ cache:
+ type: REDIS
+ redis:
+ time-to-live: 1h # 设置过期时间为 1 小时
+
+--- #################### 接口文档配置 ####################
+
+springdoc:
+ api-docs:
+ enabled: true # 1. 是否开启 Swagger 接文档的元数据
+ path: /v3/api-docs
+ swagger-ui:
+ enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
+ path: /swagger-ui.html
+ default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
+
+knife4j:
+ enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面
+ setting:
+ language: zh_cn
+
+# MyBatis Plus 的配置项
+mybatis-plus:
+ configuration:
+ map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
+ global-config:
+ db-config:
+ id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
+ # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库
+ # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
+ # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
+ logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+ logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+ banner: false # 关闭控制台的 Banner 打印
+ type-aliases-package: ${yudao.info.base-package}.dal.dataobject
+ encryptor:
+ password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
+
+mybatis-plus-join:
+ banner: false # 关闭控制台的 Banner 打印
+
+# Spring Data Redis 配置
+spring:
+ data:
+ redis:
+ repositories:
+ enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度
+
+--- #################### RPC 远程调用相关配置 ####################
+
+--- #################### 消息队列相关 ####################
+
+# rocketmq 配置项,对应 RocketMQProperties 配置类
+rocketmq:
+ # Producer 配置项
+ producer:
+ group: ${spring.application.name}_PRODUCER # 生产者分组
+
+spring:
+ # Kafka 配置项,对应 KafkaProperties 配置类
+ kafka:
+ # Kafka Producer 配置项
+ producer:
+ acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
+ retries: 3 # 发送失败时,重试发送的次数
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
+ # Kafka Consumer 配置项
+ consumer:
+ auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
+ properties:
+ spring.json.trusted.packages: '*'
+ # Kafka Consumer Listener 监听器配置
+ listener:
+ missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
+
+--- #################### 定时任务相关配置 ####################
+
+xxl:
+ job:
+ executor:
+ appname: ${spring.application.name} # 执行器 AppName
+ logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
+ accessToken: default_token # 执行器通讯TOKEN
+
+--- #################### 芋道相关配置 ####################
+
+yudao:
+ info:
+ version: 1.0.0
+ base-package: cn.iocoder.yudao.module.wms
+ web:
+ admin-ui:
+ url: http://sys.znkjfw.com # Admin 管理后台 UI 的地址
+ websocket:
+ enable: true # websocket的开关
+ path: /wms/ws # 路径
+ sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
+ sender-rocketmq:
+ topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group
+ sender-rabbitmq:
+ exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange
+ queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue
+ sender-kafka:
+ topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic
+ consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group
+ swagger:
+ title: wms管理后台
+ description: 提供管理员管理的所有功能
+ version: ${yudao.info.version}
+ base-package: ${yudao.info.base-package}
+ codegen:
+ base-package: cn.iocoder.yudao
+ db-schemas: ${spring.datasource.dynamic.datasource.master.name}
+ front-type: 10 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
+ error-code: # 错误码相关配置项
+ constants-class-list:
+ - cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants
+ tenant: # 多租户相关配置项
+ enable: true
+ ignore-urls:
+ - /admin-api/wms/file/*/get/** # 获取图片,和租户无关
+ ignore-tables:
+ - wms_codegen_column
+ - wms_codegen_table
+ - wms_test_demo
+ - wms_config
+ - wms_file_config
+ - wms_file
+ - wms_file_content
+ - wms_job
+ - wms_job_log
+ - wms_job_log
+ - wms_data_source_config
+ - sys_region
+
+debug: false
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-dev.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-dev.yaml
new file mode 100644
index 00000000..2de0efbf
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-dev.yaml
@@ -0,0 +1,23 @@
+--- #################### 注册中心相关配置 ####################
+
+spring:
+ cloud:
+ nacos:
+ server-addr: 127.0.0.1:8848
+ discovery:
+ namespace: dev # 命名空间。这里使用 dev 开发环境
+ metadata:
+ version: 1.0.0 # 服务实例的版本号,可用于灰度发布
+
+--- #################### 配置中心相关配置 ####################
+
+spring:
+ cloud:
+ nacos:
+ # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类
+ config:
+ server-addr: 127.0.0.1:8848 # Nacos 服务器地址
+ namespace: dev # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境
+ group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
+ name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name
+ file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-local.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-local.yaml
new file mode 100644
index 00000000..2de0efbf
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-local.yaml
@@ -0,0 +1,23 @@
+--- #################### 注册中心相关配置 ####################
+
+spring:
+ cloud:
+ nacos:
+ server-addr: 127.0.0.1:8848
+ discovery:
+ namespace: dev # 命名空间。这里使用 dev 开发环境
+ metadata:
+ version: 1.0.0 # 服务实例的版本号,可用于灰度发布
+
+--- #################### 配置中心相关配置 ####################
+
+spring:
+ cloud:
+ nacos:
+ # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类
+ config:
+ server-addr: 127.0.0.1:8848 # Nacos 服务器地址
+ namespace: dev # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境
+ group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
+ name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name
+ file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-prod.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-prod.yaml
new file mode 100644
index 00000000..4e48a732
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap-prod.yaml
@@ -0,0 +1,23 @@
+--- #################### 注册中心相关配置 ####################
+
+spring:
+ cloud:
+ nacos:
+ server-addr: 47.97.8.94:8848
+ discovery:
+ namespace: prod # 命名空间。这里使用 dev 开发环境
+ metadata:
+ version: 1.0.0 # 服务实例的版本号,可用于灰度发布
+
+--- #################### 配置中心相关配置 ####################
+
+spring:
+ cloud:
+ nacos:
+ # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类
+ config:
+ server-addr: 47.97.8.94:8848 # Nacos 服务器地址
+ namespace: prod # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境
+ group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
+ name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name
+ file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap.yaml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap.yaml
new file mode 100644
index 00000000..dd00255e
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/bootstrap.yaml
@@ -0,0 +1,15 @@
+spring:
+ application:
+ name: wms-server
+
+ profiles:
+ active: local #local
+# active: prod
+
+server:
+ port: 48088
+
+# 日志文件配置。注意,如果 logging.file.name 不放在 bootstrap.yaml 配置文件,而是放在 application.yaml 中,会导致出现 LOG_FILE_IS_UNDEFINED 文件
+logging:
+ file:
+ name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/controller.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/controller.vm
new file mode 100644
index 00000000..4c047c94
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/controller.vm
@@ -0,0 +1,233 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName};
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.constraints.*;
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import ${PageParamClassName};
+import ${PageResultClassName};
+import ${CommonResultClassName};
+import ${BeanUtils};
+import static ${CommonResultClassName}.success;
+
+import ${ExcelUtilsClassName};
+
+import ${OperateLogClassName};
+import static ${OperateTypeEnumClassName}.*;
+
+import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
+import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
+import ${basePackage}.module.${table.moduleName}.service.${table.businessName}.${table.className}Service;
+
+@Tag(name = "${sceneEnum.name} - ${table.classComment}")
+@RestController
+##二级的 businessName 暂时不算在 HTTP 路径上,可以根据需要写
+@RequestMapping("/${table.moduleName}/${simpleClassName_strikeCase}")
+@Validated
+public class ${sceneEnum.prefixClass}${table.className}Controller {
+
+ @Resource
+ private ${table.className}Service ${classNameVar}Service;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建${table.classComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")
+#end
+ public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) {
+ return success(${classNameVar}Service.create${simpleClassName}(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新${table.classComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")
+#end
+ public CommonResult update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) {
+ ${classNameVar}Service.update${simpleClassName}(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除${table.classComment}")
+ @Parameter(name = "id", description = "编号", required = true)
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")
+#end
+ public CommonResult delete${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
+ ${classNameVar}Service.delete${simpleClassName}(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得${table.classComment}")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult<${sceneEnum.prefixClass}${table.className}RespVO> get${simpleClassName}(@RequestParam("id") ${primaryColumn.javaType} id) {
+ ${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);
+ return success(BeanUtils.toBean(${classNameVar}, ${sceneEnum.prefixClass}${table.className}RespVO.class));
+ }
+
+#if ( $table.templateType != 2 )
+ @GetMapping("/page")
+ @Operation(summary = "获得${table.classComment}分页")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
+ PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, ${sceneEnum.prefixClass}${table.className}RespVO.class));
+ }
+
+## 特殊:树表专属逻辑(树不需要分页接口)
+#else
+ @GetMapping("/list")
+ @Operation(summary = "获得${table.classComment}列表")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult> get${simpleClassName}List(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {
+ List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);
+ return success(BeanUtils.toBean(list, ${sceneEnum.prefixClass}${table.className}RespVO.class));
+ }
+
+#end
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出${table.classComment} Excel")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')")
+#end
+ @OperateLog(type = EXPORT)
+#if ( $table.templateType != 2 )
+ public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO).getList();
+ // 导出 Excel
+ ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${table.className}RespVO.class,
+ BeanUtils.toBean(list, ${table.className}RespVO.class));
+ }
+## 特殊:树表专属逻辑(树不需要分页接口)
+#else
+ public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO,
+ HttpServletResponse response) throws IOException {
+ List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);
+ // 导出 Excel
+ ExcelUtils.write(response, "${table.classComment}.xls", "数据", ${table.className}RespVO.class,
+ BeanUtils.toBean(list, ${table.className}RespVO.class));
+ }
+#end
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+#set ($subClassNameVar = $subClassNameVars.get($index))
+ // ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+ @GetMapping("/${subSimpleClassName_strikeCase}/page")
+ @Operation(summary = "获得${subTable.classComment}分页")
+ @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult> get${subSimpleClassName}Page(PageParam pageReqVO,
+ @RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return success(${classNameVar}Service.get${subSimpleClassName}Page(pageReqVO, ${subJoinColumn.javaField}));
+ }
+
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+ #if ( $subTable.subJoinMany )
+ @GetMapping("/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}")
+ @Operation(summary = "获得${subTable.classComment}列表")
+ @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult> get${subSimpleClassName}ListBy${SubJoinColumnName}(@RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return success(${classNameVar}Service.get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}));
+ }
+
+ #else
+ @GetMapping("/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}")
+ @Operation(summary = "获得${subTable.classComment}")
+ @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult<${subTable.className}DO> get${subSimpleClassName}By${SubJoinColumnName}(@RequestParam("${subJoinColumn.javaField}") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return success(${classNameVar}Service.get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}));
+ }
+
+ #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+ @PostMapping("/${subSimpleClassName_strikeCase}/create")
+ @Operation(summary = "创建${subTable.classComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')")
+#end
+ public CommonResult<${subPrimaryColumn.javaType}> create${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) {
+ return success(${classNameVar}Service.create${subSimpleClassName}(${subClassNameVar}));
+ }
+
+ @PutMapping("/${subSimpleClassName_strikeCase}/update")
+ @Operation(summary = "更新${subTable.classComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')")
+#end
+ public CommonResult update${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) {
+ ${classNameVar}Service.update${subSimpleClassName}(${subClassNameVar});
+ return success(true);
+ }
+
+ @DeleteMapping("/${subSimpleClassName_strikeCase}/delete")
+ @Parameter(name = "id", description = "编号", required = true)
+ @Operation(summary = "删除${subTable.classComment}")
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')")
+#end
+ public CommonResult delete${subSimpleClassName}(@RequestParam("id") ${subPrimaryColumn.javaType} id) {
+ ${classNameVar}Service.delete${subSimpleClassName}(id);
+ return success(true);
+ }
+
+ @GetMapping("/${subSimpleClassName_strikeCase}/get")
+ @Operation(summary = "获得${subTable.classComment}")
+ @Parameter(name = "id", description = "编号", required = true)
+#if ($sceneEnum.scene == 1)
+ @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')")
+#end
+ public CommonResult<${subTable.className}DO> get${subSimpleClassName}(@RequestParam("id") ${subPrimaryColumn.javaType} id) {
+ return success(${classNameVar}Service.get${subSimpleClassName}(id));
+ }
+
+#end
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm
new file mode 100644
index 00000000..46b6a259
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm
@@ -0,0 +1,45 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import ${PageParamClassName};
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperation} && ${column.javaType} == "LocalDateTime")
+import java.time.LocalDateTime;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+#break
+#end
+#end
+## 字段模板
+#macro(columnTpl $prefix $prefixStr)
+ @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+ private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
+#end
+
+@Schema(description = "${sceneEnum.name} - ${table.classComment}列表 Request VO")
+@Data
+public class ${sceneEnum.prefixClass}${table.className}ListReqVO {
+
+#foreach ($column in $columns)
+#if (${column.listOperation})##查询操作
+#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候
+ @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private ${column.javaType}[] ${column.javaField};
+#else##情况二,非 Between 的时间
+ #columnTpl('', '')
+#end
+
+#end
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/pageReqVO.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/pageReqVO.vm
new file mode 100644
index 00000000..003bac90
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/pageReqVO.vm
@@ -0,0 +1,47 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import ${PageParamClassName};
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperationCondition} && ${column.javaType} == "LocalDateTime")
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+#break
+#end
+#end
+## 字段模板
+#macro(columnTpl $prefix $prefixStr)
+ @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+ private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end;
+#end
+
+@Schema(description = "${sceneEnum.name} - ${table.classComment}分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ${sceneEnum.prefixClass}${table.className}PageReqVO extends PageParam {
+
+#foreach ($column in $columns)
+#if (${column.listOperation})##查询操作
+#if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候
+ @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end)
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private ${column.javaType}[] ${column.javaField};
+#else##情况二,非 Between 的时间
+ #columnTpl('', '')
+#end
+
+#end
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/respVO.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/respVO.vm
new file mode 100644
index 00000000..54c16671
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/respVO.vm
@@ -0,0 +1,54 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+## 处理 BigDecimal 字段的引入
+import java.util.*;
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if (${column.listOperationResult} && ${column.javaType} == "LocalDateTime")
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+#break
+#end
+#end
+## 处理 Excel 导出
+import com.alibaba.excel.annotation.*;
+#foreach ($column in $columns)
+#if ("$!column.dictType" != "")## 有设置数据字典
+import ${DictFormatClassName};
+import ${DictConvertClassName};
+#break
+#end
+#end
+
+@Schema(description = "${sceneEnum.name} - ${table.classComment} Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ${sceneEnum.prefixClass}${table.className}RespVO {
+
+## 逐个处理字段
+#foreach ($column in $columns)
+#if (${column.listOperationResult})
+## 1. 处理 Swagger 注解
+ @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
+## 2. 处理 Excel 导出
+#if ("$!column.dictType" != "")##处理枚举值
+ @ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class)
+ @DictFormat("${column.dictType}") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+#else
+ @ExcelProperty("${column.columnComment}")
+#end
+## 3. 处理字段定义
+ private ${column.javaType} ${column.javaField};
+
+#end
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm
new file mode 100644
index 00000000..89829cc9
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm
@@ -0,0 +1,65 @@
+package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.util.*;
+import javax.validation.constraints.*;
+## 处理 BigDecimal 字段的引入
+import java.util.*;
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#break
+#end
+#end
+## 处理 LocalDateTime 字段的引入
+#foreach ($column in $columns)
+#if ((${column.createOperation} || ${column.updateOperation}) && ${column.javaType} == "LocalDateTime")
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+#break
+#end
+#end
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
+
+@Schema(description = "${sceneEnum.name} - ${table.classComment}新增/修改 Request VO")
+@Data
+public class ${sceneEnum.prefixClass}${table.className}SaveReqVO {
+
+## 逐个处理字段
+#foreach ($column in $columns)
+#if (${column.createOperation} || ${column.updateOperation})
+## 1. 处理 Swagger 注解
+ @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end)
+## 2. 处理 Validator 参数校验
+#if (!${column.nullable} && !${column.primaryKey})
+#if (${column.javaType} == 'String')
+ @NotEmpty(message = "${column.columnComment}不能为空")
+#else
+ @NotNull(message = "${column.columnComment}不能为空")
+#end
+#end
+## 3. 处理字段定义
+ private ${column.javaType} ${column.javaField};
+
+#end
+#end
+## 特殊:主子表专属逻辑(非 ERP 模式)
+#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+ #if ( $subTable.subJoinMany)
+ @Schema(description = "${subTable.classComment}列表")
+ private List<${subTable.className}DO> ${subClassNameVars.get($index)}s;
+
+ #else
+ @Schema(description = "${subTable.classComment}")
+ private ${subTable.className}DO ${subClassNameVars.get($index)};
+
+ #end
+#end
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/do.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/do.vm
new file mode 100644
index 00000000..b019d6e1
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/do.vm
@@ -0,0 +1,52 @@
+package ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName};
+
+import lombok.*;
+import java.util.*;
+#foreach ($column in $columns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#end
+#if (${column.javaType} == "LocalDateTime")
+import java.time.LocalDateTime;
+#end
+#end
+import com.baomidou.mybatisplus.annotation.*;
+import ${BaseDOClassName};
+
+/**
+ * ${table.classComment} DO
+ *
+ * @author ${table.author}
+ */
+@TableName("${table.tableName.toLowerCase()}")
+@KeySequence("${table.tableName.toLowerCase()}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ${table.className}DO extends BaseDO {
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+ public static final Long ${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT = 0L;
+
+#end
+#foreach ($column in $columns)
+#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段
+ /**
+ * ${column.columnComment}
+ #if ("$!column.dictType" != "")##处理枚举值
+ *
+ * 枚举 {@link TODO ${column.dictType} 对应的类}
+ #end
+ */
+ #if (${column.primaryKey})##处理主键
+ @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end
+ #end
+ private ${column.javaType} ${column.javaField};
+#end
+#end
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/do_sub.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/do_sub.vm
new file mode 100644
index 00000000..16be55e8
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/do_sub.vm
@@ -0,0 +1,49 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+package ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName};
+
+import lombok.*;
+import java.util.*;
+#foreach ($column in $subColumns)
+#if (${column.javaType} == "BigDecimal")
+import java.math.BigDecimal;
+#end
+#if (${column.javaType} == "LocalDateTime")
+import java.time.LocalDateTime;
+#end
+#end
+import com.baomidou.mybatisplus.annotation.*;
+import ${BaseDOClassName};
+
+/**
+ * ${subTable.classComment} DO
+ *
+ * @author ${subTable.author}
+ */
+@TableName("${subTable.tableName.toLowerCase()}")
+@KeySequence("${subTable.tableName.toLowerCase()}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ${subTable.className}DO extends BaseDO {
+
+#foreach ($column in $subColumns)
+#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段
+ /**
+ * ${column.columnComment}
+ #if ("$!column.dictType" != "")##处理枚举值
+ *
+ * 枚举 {@link TODO ${column.dictType} 对应的类}
+ #end
+ */
+ #if (${column.primaryKey})##处理主键
+ @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end
+ #end
+ private ${column.javaType} ${column.javaField};
+#end
+#end
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper.vm
new file mode 100644
index 00000000..b98b471f
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper.vm
@@ -0,0 +1,82 @@
+package ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName};
+
+import java.util.*;
+
+import ${PageResultClassName};
+import ${QueryWrapperClassName};
+import ${BaseMapperClassName};
+import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import org.apache.ibatis.annotations.Mapper;
+import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
+
+## 字段模板
+#macro(listCondition)
+#foreach ($column in $columns)
+#if (${column.listOperation})
+#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+#if (${column.listOperationCondition} == "=")##情况一,= 的时候
+ .eqIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "!=")##情况二,!= 的时候
+ .neIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == ">")##情况三,> 的时候
+ .gtIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == ">=")##情况四,>= 的时候
+ .geIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "<")##情况五,< 的时候
+ .ltIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "<=")##情况五,<= 的时候
+ .leIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "LIKE")##情况七,Like 的时候
+ .likeIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#if (${column.listOperationCondition} == "BETWEEN")##情况八,Between 的时候
+ .betweenIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())
+#end
+#end
+#end
+#end
+/**
+ * ${table.classComment} Mapper
+ *
+ * @author ${table.author}
+ */
+@Mapper
+public interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> {
+
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+ default PageResult<${table.className}DO> selectPage(${sceneEnum.prefixClass}${table.className}PageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<${table.className}DO>()
+ #listCondition()
+ .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序
+
+ }
+#else
+ default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ListReqVO reqVO) {
+ return selectList(new LambdaQueryWrapperX<${table.className}DO>()
+ #listCondition()
+ .orderByDesc(${table.className}DO::getId));## 大多数情况下,id 倒序
+
+ }
+#end
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写
+ default ${table.className}DO selectBy${TreeParentJavaField}And${TreeNameJavaField}(Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) {
+ return selectOne(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField}, ${table.className}DO::get${TreeNameJavaField}, ${treeNameColumn.javaField});
+ }
+
+ default Long selectCountBy${TreeParentJavaField}(${treeParentColumn.javaType} ${treeParentColumn.javaField}) {
+ return selectCount(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField});
+ }
+
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper.xml.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper.xml.vm
new file mode 100644
index 00000000..290378d3
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper.xml.vm
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper_sub.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper_sub.vm
new file mode 100644
index 00000000..e5589e99
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/dal/mapper_sub.vm
@@ -0,0 +1,51 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subJoinColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+package ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName};
+
+import java.util.*;
+
+import ${PageResultClassName};
+import ${PageParamClassName};
+import ${QueryWrapperClassName};
+import ${BaseMapperClassName};
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * ${subTable.classComment} Mapper
+ *
+ * @author ${subTable.author}
+ */
+@Mapper
+public interface ${subTable.className}Mapper extends BaseMapperX<${subTable.className}DO> {
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+ default PageResult<${subTable.className}DO> selectPage(PageParam reqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<${subTable.className}DO>()
+ .eq(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField})
+ .orderByDesc(${subTable.className}DO::getId));## 大多数情况下,id 倒序
+
+ }
+
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+ #if ( $subTable.subJoinMany)
+ default List<${subTable.className}DO> selectListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return selectList(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+ }
+
+ #else
+ default ${subTable.className}DO selectBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return selectOne(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+ }
+
+ #end
+ #end
+ default int deleteBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return delete(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/enums/errorcode.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/enums/errorcode.vm
new file mode 100644
index 00000000..b7e21e63
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/enums/errorcode.vm
@@ -0,0 +1,22 @@
+// TODO 待办:请将下面的错误码复制到 yudao-module-${table.moduleName}-api 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
+// ========== ${table.classComment} TODO 补充编号 ==========
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${table.classComment}不存在");
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN = new ErrorCode(TODO 补充编号, "存在存在子${table.classComment},无法删除");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS = new ErrorCode(TODO 补充编号,"父级${table.classComment}不存在");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR = new ErrorCode(TODO 补充编号, "不能设置自己为父${table.classComment}");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE = new ErrorCode(TODO 补充编号, "已经存在该${treeNameColumn.columnComment}的${table.classComment}");
+ErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD = new ErrorCode(TODO 补充编号, "不能设置自己的子${table.className}为父${table.className}");
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 )## 特殊:ERP 情况
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index))
+ErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, "${subTable.classComment}不存在");
+#if ( !$subTable.subJoinMany )
+ErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS = new ErrorCode(TODO 补充编号, "${subTable.classComment}已存在");
+#end
+#end
+#end
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/service/service.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/service/service.vm
new file mode 100644
index 00000000..4085889d
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/service/service.vm
@@ -0,0 +1,147 @@
+package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
+
+import java.util.*;
+import javax.validation.*;
+import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
+import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
+import ${PageResultClassName};
+import ${PageParamClassName};
+
+/**
+ * ${table.classComment} Service 接口
+ *
+ * @author ${table.author}
+ */
+public interface ${table.className}Service {
+
+ /**
+ * 创建${table.classComment}
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ ${primaryColumn.javaType} create${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO);
+
+ /**
+ * 更新${table.classComment}
+ *
+ * @param updateReqVO 更新信息
+ */
+ void update${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO);
+
+ /**
+ * 删除${table.classComment}
+ *
+ * @param id 编号
+ */
+ void delete${simpleClassName}(${primaryColumn.javaType} id);
+
+ /**
+ * 获得${table.classComment}
+ *
+ * @param id 编号
+ * @return ${table.classComment}
+ */
+ ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);
+
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+ /**
+ * 获得${table.classComment}分页
+ *
+ * @param pageReqVO 分页查询
+ * @return ${table.classComment}分页
+ */
+ PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO);
+#else
+ /**
+ * 获得${table.classComment}列表
+ *
+ * @param listReqVO 查询条件
+ * @return ${table.classComment}列表
+ */
+ List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO);
+#end
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subClassNameVar = $subClassNameVars.get($index))
+ // ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+ /**
+ * 获得${subTable.classComment}分页
+ *
+ * @param pageReqVO 分页查询
+ * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+ * @return ${subTable.classComment}分页
+ */
+ PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+ #if ( $subTable.subJoinMany )
+ /**
+ * 获得${subTable.classComment}列表
+ *
+ * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+ * @return ${subTable.classComment}列表
+ */
+ List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+ #else
+ /**
+ * 获得${subTable.classComment}
+ *
+ * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}
+ * @return ${subTable.classComment}
+ */
+ ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});
+
+ #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+ /**
+ * 创建${subTable.classComment}
+ *
+ * @param ${subClassNameVar} 创建信息
+ * @return 编号
+ */
+ ${subPrimaryColumn.javaType} create${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar});
+
+ /**
+ * 更新${subTable.classComment}
+ *
+ * @param ${subClassNameVar} 更新信息
+ */
+ void update${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar});
+
+ /**
+ * 删除${subTable.classComment}
+ *
+ * @param id 编号
+ */
+ void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id);
+
+ /**
+ * 获得${subTable.classComment}
+ *
+ * @param id 编号
+ * @return ${subTable.classComment}
+ */
+ ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id);
+
+#end
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/service/serviceImpl.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/service/serviceImpl.vm
new file mode 100644
index 00000000..6aa2fb2e
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/service/serviceImpl.vm
@@ -0,0 +1,350 @@
+package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
+import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
+#end
+import ${PageResultClassName};
+import ${PageParamClassName};
+import ${BeanUtils};
+
+import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+import ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}.${subTable.className}Mapper;
+#end
+
+import static ${ServiceExceptionUtilClassName}.exception;
+import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;
+
+/**
+ * ${table.classComment} Service 实现类
+ *
+ * @author ${table.author}
+ */
+@Service
+@Validated
+public class ${table.className}ServiceImpl implements ${table.className}Service {
+
+ @Resource
+ private ${table.className}Mapper ${classNameVar}Mapper;
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+ @Resource
+ private ${subTable.className}Mapper ${subClassNameVars.get($index)}Mapper;
+#end
+
+ @Override
+## 特殊:主子表专属逻辑(非 ERP 模式)
+#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )
+ @Transactional(rollbackFor = Exception.class)
+#end
+ public ${primaryColumn.javaType} create${simpleClassName}(${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) {
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写
+ // 校验${treeParentColumn.columnComment}的有效性
+ validateParent${simpleClassName}(null, createReqVO.get${TreeParentJavaField}());
+ // 校验${treeNameColumn.columnComment}的唯一性
+ validate${simpleClassName}${TreeNameJavaField}Unique(null, createReqVO.get${TreeParentJavaField}(), createReqVO.get${TreeNameJavaField}());
+
+#end
+ // 插入
+ ${table.className}DO ${classNameVar} = BeanUtils.toBean(createReqVO, ${table.className}DO.class);
+ ${classNameVar}Mapper.insert(${classNameVar});
+## 特殊:主子表专属逻辑(非 ERP 模式)
+#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )
+
+ // 插入子表
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+ #if ( $subTable.subJoinMany)
+ create${subSimpleClassName}List(${classNameVar}.getId(), createReqVO.get${subSimpleClassNames.get($index)}s());
+ #else
+ create${subSimpleClassName}(${classNameVar}.getId(), createReqVO.get${subSimpleClassNames.get($index)}());
+ #end
+#end
+#end
+ // 返回
+ return ${classNameVar}.getId();
+ }
+
+ @Override
+## 特殊:主子表专属逻辑(非 ERP 模式)
+#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )
+ @Transactional(rollbackFor = Exception.class)
+#end
+ public void update${simpleClassName}(${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) {
+ // 校验存在
+ validate${simpleClassName}Exists(updateReqVO.getId());
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写
+ // 校验${treeParentColumn.columnComment}的有效性
+ validateParent${simpleClassName}(updateReqVO.getId(), updateReqVO.get${TreeParentJavaField}());
+ // 校验${treeNameColumn.columnComment}的唯一性
+ validate${simpleClassName}${TreeNameJavaField}Unique(updateReqVO.getId(), updateReqVO.get${TreeParentJavaField}(), updateReqVO.get${TreeNameJavaField}());
+
+#end
+ // 更新
+ ${table.className}DO updateObj = BeanUtils.toBean(updateReqVO, ${table.className}DO.class);
+ ${classNameVar}Mapper.updateById(updateObj);
+## 特殊:主子表专属逻辑(非 ERP 模式)
+#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11)
+
+ // 更新子表
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+ #if ( $subTable.subJoinMany)
+ update${subSimpleClassName}List(updateReqVO.getId(), updateReqVO.get${subSimpleClassNames.get($index)}s());
+ #else
+ update${subSimpleClassName}(updateReqVO.getId(), updateReqVO.get${subSimpleClassNames.get($index)}());
+ #end
+#end
+#end
+ }
+
+ @Override
+## 特殊:主子表专属逻辑
+#if ( $subTables && $subTables.size() > 0)
+ @Transactional(rollbackFor = Exception.class)
+#end
+ public void delete${simpleClassName}(${primaryColumn.javaType} id) {
+ // 校验存在
+ validate${simpleClassName}Exists(id);
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($ParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+ // 校验是否有子${table.classComment}
+ if (${classNameVar}Mapper.selectCountBy${ParentJavaField}(id) > 0) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN);
+ }
+#end
+ // 删除
+ ${classNameVar}Mapper.deleteById(id);
+## 特殊:主子表专属逻辑
+#if ( $subTables && $subTables.size() > 0)
+
+ // 删除子表
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+ delete${subSimpleClassName}By${SubJoinColumnName}(id);
+#end
+#end
+ }
+
+ private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) {
+ if (${classNameVar}Mapper.selectById(id) == null) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+ }
+ }
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写
+#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写
+ private void validateParent${simpleClassName}(Long id, Long ${treeParentColumn.javaField}) {
+ if (${treeParentColumn.javaField} == null || ${simpleClassName}DO.${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT.equals(${treeParentColumn.javaField})) {
+ return;
+ }
+ // 1. 不能设置自己为父${table.classComment}
+ if (Objects.equals(id, ${treeParentColumn.javaField})) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR);
+ }
+ // 2. 父${table.classComment}不存在
+ ${simpleClassName}DO parent${simpleClassName} = ${classNameVar}Mapper.selectById(${treeParentColumn.javaField});
+ if (parent${simpleClassName} == null) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS);
+ }
+ // 3. 递归校验父${table.classComment},如果父${table.classComment}是自己的子${table.classComment},则报错,避免形成环路
+ if (id == null) { // id 为空,说明新增,不需要考虑环路
+ return;
+ }
+ for (int i = 0; i < Short.MAX_VALUE; i++) {
+ // 3.1 校验环路
+ ${treeParentColumn.javaField} = parent${simpleClassName}.get${TreeParentJavaField}();
+ if (Objects.equals(id, ${treeParentColumn.javaField})) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD);
+ }
+ // 3.2 继续递归下一级父${table.classComment}
+ if (${treeParentColumn.javaField} == null || ${simpleClassName}DO.${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT.equals(${treeParentColumn.javaField})) {
+ break;
+ }
+ parent${simpleClassName} = ${classNameVar}Mapper.selectById(${treeParentColumn.javaField});
+ if (parent${simpleClassName} == null) {
+ break;
+ }
+ }
+ }
+
+ private void validate${simpleClassName}${TreeNameJavaField}Unique(Long id, Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) {
+ ${simpleClassName}DO ${classNameVar} = ${classNameVar}Mapper.selectBy${TreeParentJavaField}And${TreeNameJavaField}(${treeParentColumn.javaField}, ${treeNameColumn.javaField});
+ if (${classNameVar} == null) {
+ return;
+ }
+ // 如果 id 为空,说明不用比较是否为相同 id 的${table.classComment}
+ if (id == null) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE);
+ }
+ if (!Objects.equals(${classNameVar}.getId(), id)) {
+ throw exception(${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE);
+ }
+ }
+
+#end
+ @Override
+ public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) {
+ return ${classNameVar}Mapper.selectById(id);
+ }
+
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+ @Override
+ public PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {
+ return ${classNameVar}Mapper.selectPage(pageReqVO);
+ }
+#else
+ @Override
+ public List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {
+ return ${classNameVar}Mapper.selectList(listReqVO);
+ }
+#end
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subClassNameVar = $subClassNameVars.get($index))
+ // ==================== 子表($subTable.classComment) ====================
+
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+ @Override
+ public PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return ${subClassNameVars.get($index)}Mapper.selectPage(pageReqVO, ${subJoinColumn.javaField});
+ }
+
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+ #if ( $subTable.subJoinMany )
+ @Override
+ public List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return ${subClassNameVars.get($index)}Mapper.selectListBy${SubJoinColumnName}(${subJoinColumn.javaField});
+ }
+
+ #else
+ @Override
+ public ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {
+ return ${subClassNameVars.get($index)}Mapper.selectBy${SubJoinColumnName}(${subJoinColumn.javaField});
+ }
+
+ #end
+#end
+## 情况一:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+ @Override
+ public ${subPrimaryColumn.javaType} create${subSimpleClassName}(${subTable.className}DO ${subClassNameVar}) {
+## 特殊:一对一时,需要保证只有一条,不能重复插入
+#if ( !$subTable.subJoinMany)
+ // 校验是否已经存在
+ if (${subClassNameVars.get($index)}Mapper.selectBy${SubJoinColumnName}(${subClassNameVar}.get${SubJoinColumnName}()) != null) {
+ throw exception(${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS);
+ }
+ // 插入
+#end
+ ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar});
+ return ${subClassNameVar}.getId();
+ }
+
+ @Override
+ public void update${subSimpleClassName}(${subTable.className}DO ${subClassNameVar}) {
+ // 校验存在
+ validate${subSimpleClassName}Exists(${subClassNameVar}.getId());
+ // 更新
+ ${subClassNameVars.get($index)}Mapper.updateById(${subClassNameVar});
+ }
+
+ @Override
+ public void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id) {
+ // 校验存在
+ validate${subSimpleClassName}Exists(id);
+ // 删除
+ ${subClassNameVars.get($index)}Mapper.deleteById(id);
+ }
+
+ @Override
+ public ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id) {
+ return ${subClassNameVars.get($index)}Mapper.selectById(id);
+ }
+
+ private void validate${subSimpleClassName}Exists(${subPrimaryColumn.javaType} id) {
+ if (${subClassNameVar}Mapper.selectById(id) == null) {
+ throw exception(${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS);
+ }
+ }
+
+## 情况二:非 MASTER_ERP 时,支持批量的新增、修改操作
+#else
+ #if ( $subTable.subJoinMany)
+ private void create${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) {
+ list.forEach(o -> o.set$SubJoinColumnName(${subJoinColumn.javaField}));
+ ${subClassNameVars.get($index)}Mapper.insertBatch(list);
+ }
+
+ private void update${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) {
+ delete${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField});
+ list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+ create${subSimpleClassName}List(${subJoinColumn.javaField}, list);
+ }
+
+ #else
+ private void create${subSimpleClassName}(${primaryColumn.javaType} ${subJoinColumn.javaField}, ${subTable.className}DO ${subClassNameVar}) {
+ if (${subClassNameVar} == null) {
+ return;
+ }
+ ${subClassNameVar}.set$SubJoinColumnName(${subJoinColumn.javaField});
+ ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar});
+ }
+
+ private void update${subSimpleClassName}(${primaryColumn.javaType} ${subJoinColumn.javaField}, ${subTable.className}DO ${subClassNameVar}) {
+ if (${subClassNameVar} == null) {
+ return;
+ }
+ ${subClassNameVar}.set$SubJoinColumnName(${subJoinColumn.javaField});
+ ${subClassNameVar}.setUpdater(null).setUpdateTime(null); // 解决更新情况下:updateTime 不更新
+ ${subClassNameVars.get($index)}Mapper.insertOrUpdate(${subClassNameVar});
+ }
+
+ #end
+#end
+ private void delete${subSimpleClassName}By${SubJoinColumnName}(${primaryColumn.javaType} ${subJoinColumn.javaField}) {
+ ${subClassNameVars.get($index)}Mapper.deleteBy${SubJoinColumnName}(${subJoinColumn.javaField});
+ }
+
+#end
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/test/serviceTest.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/test/serviceTest.vm
new file mode 100644
index 00000000..eeac3ce6
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/java/test/serviceTest.vm
@@ -0,0 +1,168 @@
+package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+
+import ${baseFrameworkPackage}.test.core.ut.BaseDbUnitTest;
+
+import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
+import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
+import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
+import ${PageResultClassName};
+
+import javax.annotation.Resource;
+import org.springframework.context.annotation.Import;
+import java.util.*;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.util.RandomUtil.*;
+import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;
+import static ${baseFrameworkPackage}.test.core.util.AssertUtils.*;
+import static ${baseFrameworkPackage}.test.core.util.RandomUtils.*;
+import static ${LocalDateTimeUtilsClassName}.*;
+import static ${ObjectUtilsClassName}.*;
+import static ${DateUtilsClassName}.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+## 字段模板
+#macro(getPageCondition $VO)
+ // mock 数据
+ ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到
+ #foreach ($column in $columns)
+ #if (${column.listOperation})
+ #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+ o.set$JavaField(null);
+ #end
+ #end
+ });
+ ${classNameVar}Mapper.insert(db${simpleClassName});
+ #foreach ($column in $columns)
+ #if (${column.listOperation})
+ #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+ // 测试 ${column.javaField} 不匹配
+ ${classNameVar}Mapper.insert(cloneIgnoreId(db${simpleClassName}, o -> o.set$JavaField(null)));
+ #end
+ #end
+ // 准备参数
+ ${sceneEnum.prefixClass}${table.className}${VO} reqVO = new ${sceneEnum.prefixClass}${table.className}${VO}();
+ #foreach ($column in $columns)
+ #if (${column.listOperation})
+ #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
+ #if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况
+ reqVO.set${JavaField}(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+ #else
+ reqVO.set$JavaField(null);
+ #end
+ #end
+ #end
+#end
+/**
+ * {@link ${table.className}ServiceImpl} 的单元测试类
+ *
+ * @author ${table.author}
+ */
+@Import(${table.className}ServiceImpl.class)
+public class ${table.className}ServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private ${table.className}ServiceImpl ${classNameVar}Service;
+
+ @Resource
+ private ${table.className}Mapper ${classNameVar}Mapper;
+
+ @Test
+ public void testCreate${simpleClassName}_success() {
+ // 准备参数
+ ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class).setId(null);
+
+ // 调用
+ ${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(createReqVO);
+ // 断言
+ assertNotNull(${classNameVar}Id);
+ // 校验记录的属性是否正确
+ ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id);
+ assertPojoEquals(createReqVO, ${classNameVar}, "id");
+ }
+
+ @Test
+ public void testUpdate${simpleClassName}_success() {
+ // mock 数据
+ ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
+ ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class, o -> {
+ o.setId(db${simpleClassName}.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ ${classNameVar}Service.update${simpleClassName}(updateReqVO);
+ // 校验是否更新正确
+ ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(updateReqVO.getId()); // 获取最新的
+ assertPojoEquals(updateReqVO, ${classNameVar});
+ }
+
+ @Test
+ public void testUpdate${simpleClassName}_notExists() {
+ // 准备参数
+ ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(updateReqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDelete${simpleClassName}_success() {
+ // mock 数据
+ ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);
+ ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ ${primaryColumn.javaType} id = db${simpleClassName}.getId();
+
+ // 调用
+ ${classNameVar}Service.delete${simpleClassName}(id);
+ // 校验数据不存在了
+ assertNull(${classNameVar}Mapper.selectById(id));
+ }
+
+ @Test
+ public void testDelete${simpleClassName}_notExists() {
+ // 准备参数
+ ${primaryColumn.javaType} id = random${primaryColumn.javaType}Id();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);
+ }
+
+## 特殊:树表专属逻辑(树不需要分页接口)
+#if ( $table.templateType != 2 )
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGet${simpleClassName}Page() {
+ #getPageCondition("PageReqVO")
+
+ // 调用
+ PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0));
+ }
+#else
+ @Test
+ @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+ public void testGet${simpleClassName}List() {
+ #getPageCondition("ListReqVO")
+
+ // 调用
+ List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO);
+ // 断言
+ assertEquals(1, list.size());
+ assertPojoEquals(db${simpleClassName}, list.get(0));
+ }
+#end
+
+}
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/sql/h2.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/sql/h2.vm
new file mode 100644
index 00000000..b22389b0
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/sql/h2.vm
@@ -0,0 +1,37 @@
+-- 将该建表 SQL 语句,添加到 yudao-module-${table.moduleName}-biz 模块的 test/resources/sql/create_tables.sql 文件里
+CREATE TABLE IF NOT EXISTS "${table.tableName.toLowerCase()}" (
+#foreach ($column in $columns)
+#if (${column.javaType} == 'Long')
+ #set ($dataType='bigint')
+#elseif (${column.javaType} == 'Integer')
+ #set ($dataType='int')
+#elseif (${column.javaType} == 'Boolean')
+ #set ($dataType='bit')
+#elseif (${column.javaType} == 'Date')
+ #set ($dataType='datetime')
+#else
+ #set ($dataType='varchar')
+#end
+ #if (${column.primaryKey})##处理主键
+ "${column.javaField}"#if (${column.javaType} == 'String') ${dataType} NOT NULL#else ${dataType} NOT NULL GENERATED BY DEFAULT AS IDENTITY#end,
+ #else
+ #if (${column.columnName} == 'create_time')
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ #elseif (${column.columnName} == 'update_time')
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ #elseif (${column.columnName} == 'creator' || ${column.columnName} == 'updater')
+ "${column.columnName}" ${dataType} DEFAULT '',
+ #elseif (${column.columnName} == 'deleted')
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ #elseif (${column.columnName} == 'tenantId')
+ "tenant_id" bigint NOT NULL DEFAULT 0,
+ #else
+ "${column.columnName.toLowerCase()}" ${dataType}#if (${column.nullable} == false) NOT NULL#end,
+ #end
+ #end
+#end
+ PRIMARY KEY ("${primaryColumn.columnName.toLowerCase()}")
+) COMMENT '${table.tableComment}';
+
+-- 将该删表 SQL 语句,添加到 yudao-module-${table.moduleName}-biz 模块的 test/resources/sql/clean.sql 文件里
+DELETE FROM "${table.tableName}";
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/sql/sql.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/sql/sql.vm
new file mode 100644
index 00000000..41b107db
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/sql/sql.vm
@@ -0,0 +1,28 @@
+-- 菜单 SQL
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status, component_name
+)
+VALUES (
+ '${table.classComment}管理', '', 2, 0, ${table.parentMenuId},
+ '${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}'
+);
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
+#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
+#foreach ($functionName in $functionNames)
+#set ($index = $foreach.count - 1)
+INSERT INTO system_menu(
+ name, permission, type, sort, parent_id,
+ path, icon, component, status
+)
+VALUES (
+ '${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,
+ '', '', '', 0
+);
+#end
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/api/api.js.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/api/api.js.vm
new file mode 100644
index 00000000..bfe2dc00
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/api/api.js.vm
@@ -0,0 +1,147 @@
+import request from '@/utils/request'
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+// 创建${table.classComment}
+export function create${simpleClassName}(data) {
+ return request({
+ url: '${baseURL}/create',
+ method: 'post',
+ data: data
+ })
+}
+
+// 更新${table.classComment}
+export function update${simpleClassName}(data) {
+ return request({
+ url: '${baseURL}/update',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除${table.classComment}
+export function delete${simpleClassName}(id) {
+ return request({
+ url: '${baseURL}/delete?id=' + id,
+ method: 'delete'
+ })
+}
+
+// 获得${table.classComment}
+export function get${simpleClassName}(id) {
+ return request({
+ url: '${baseURL}/get?id=' + id,
+ method: 'get'
+ })
+}
+
+#if ( $table.templateType != 2 )
+// 获得${table.classComment}分页
+export function get${simpleClassName}Page(params) {
+ return request({
+ url: '${baseURL}/page',
+ method: 'get',
+ params
+ })
+}
+#else
+// 获得${table.classComment}列表
+export function get${simpleClassName}List(params) {
+ return request({
+ url: '${baseURL}/list',
+ method: 'get',
+ params
+ })
+}
+#end
+// 导出${table.classComment} Excel
+export function export${simpleClassName}Excel(params) {
+ return request({
+ url: '${baseURL}/export-excel',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
+## 特殊:主子表专属逻辑 TODO @puhui999:下面方法的【空格】不太对
+#foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+ #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+ #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+ #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+ ## 情况一:MASTER_ERP 时,需要分查询页子表
+ #if ( $table.templateType == 11 )
+
+ // 获得${subTable.classComment}分页
+ export function get${subSimpleClassName}Page(params) {
+ return request({
+ url: '${baseURL}/${subSimpleClassName_strikeCase}/page',
+ method: 'get',
+ params
+ })
+ }
+ ## 情况二:非 MASTER_ERP 时,需要列表查询子表
+ #else
+ #if ( $subTable.subJoinMany )
+
+ // 获得${subTable.classComment}列表
+ export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
+ method: 'get'
+ })
+ }
+ #else
+
+ // 获得${subTable.classComment}
+ export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
+ method: 'get'
+ })
+ }
+ #end
+ #end
+ ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+ #if ( $table.templateType == 11 )
+ // 新增${subTable.classComment}
+ export function create${subSimpleClassName}(data) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 修改${subTable.classComment}
+ export function update${subSimpleClassName}(data) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`,
+ method: 'post',
+ data
+ })
+ }
+
+ // 删除${subTable.classComment}
+ export function delete${subSimpleClassName}(id) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id,
+ method: 'delete'
+ })
+ }
+
+ // 获得${subTable.classComment}
+ export function get${subSimpleClassName}(id) {
+ return request({
+ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id,
+ method: 'get'
+ })
+ }
+ #end
+#end
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm
new file mode 100644
index 00000000..99aa91af
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm
@@ -0,0 +1,205 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+
+
+
+
+
+ #foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+ #end
+
+
+
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm
new file mode 100644
index 00000000..ca266be9
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm
@@ -0,0 +1,2 @@
+## 主表的 normal 和 inner 使用相同的 form 表单
+#parse("codegen/vue/views/components/form_sub_normal.vue.vm")
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm
new file mode 100644
index 00000000..48a404a3
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm
@@ -0,0 +1,347 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+
+
+ #if ( $subTable.subJoinMany )## 情况一:一对多,table + form
+
+
+
+ #foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+
+
+
+
+ #end
+ #end
+ #end
+
+
+ —
+
+
+
+
+
+ + 添加${subTable.classComment}
+
+ #else## 情况二:一对一,form
+
+ #foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+ #end
+
+ #end
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
new file mode 100644
index 00000000..589736b6
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm
@@ -0,0 +1,165 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+
+
+#if ($table.templateType == 11)
+
+
+
+ 新增
+
+
+#end
+ ## 列表
+
+ #foreach($column in $subColumns)
+ #if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.javaType == "LocalDateTime")## 时间类型
+
+
+ {{ parseTime(scope.row.${javaField}) }}
+
+
+ #elseif($column.dictType && "" != $column.dictType)## 数据字典
+
+
+
+
+
+ #else
+
+ #end
+ #end
+ #end
+
+
+ 修改
+ 删除
+
+
+
+#if ($table.templateType == 11)
+
+
+
+ <${subSimpleClassName}Form ref="formRef" @success="getList" />
+#end
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm
new file mode 100644
index 00000000..90b8e415
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm
@@ -0,0 +1,4 @@
+## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
+## 1)inner 使用 list 不分页,erp 使用 page 分页
+## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
+#parse("codegen/vue/views/components/list_sub_erp.vue.vm")
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/form.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/form.vue.vm
new file mode 100644
index 00000000..634d05d3
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/form.vue.vm
@@ -0,0 +1,320 @@
+
+
+
+
+
+ #foreach($column in $columns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
+
+
+
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ #set ($hasImageUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ #set ($hasFileUploadColumn = true)
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ #set ($hasEditorColumn = true)
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+ {{dict.label}}
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+ #end
+
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 10 || $table.templateType == 12 )
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
+
+ #end
+
+ #end
+
+
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/index.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/index.vue.vm
new file mode 100644
index 00000000..2328007a
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue/views/index.vue.vm
@@ -0,0 +1,340 @@
+
+
+
+
+#foreach($column in $columns)
+#if ($column.listOperation)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+#if ($column.htmlType == "input")
+
+
+
+#elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+
+
+ #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+
+ #else## 未设置 dictType 数据字典的情况
+
+ #end
+
+
+#elseif($column.htmlType == "datetime")
+ #if ($column.listOperationCondition != "BETWEEN")## 非范围
+
+
+
+ #else## 范围
+
+
+
+ #end
+#end
+#end
+#end
+
+ 搜索
+ 重置
+
+
+
+
+
+
+ 新增
+
+
+ 导出
+
+ ## 特殊:树表专属逻辑
+ #if ( $table.templateType == 2 )
+
+
+ 展开/折叠
+
+
+ #end
+
+
+
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+ ## 特殊:树表专属逻辑
+ #elseif ( $table.templateType == 2 )
+
+ #else
+
+ #end
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+
+
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
+
+ #end
+
+
+
+ #end
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+#if ($column.javaType == "LocalDateTime")## 时间类型
+
+
+ {{ parseTime(scope.row.${javaField}) }}
+
+
+#elseif("" != $column.dictType)## 数据字典
+
+
+
+
+
+#else
+
+#end
+#end
+#end
+
+
+ 修改
+ 删除
+
+
+
+## 特殊:树表专属逻辑(树不需要分页)
+#if ( $table.templateType != 2 )
+
+
+#end
+
+ <${simpleClassName}Form ref="formRef" @success="getList" />
+ ## 特殊:主子表专属逻辑
+ #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List v-if="currentRow.id" :${subJoinColumn_strikeCase}="currentRow.id" />
+
+ #end
+
+ #end
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/api/api.ts.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/api/api.ts.vm
new file mode 100644
index 00000000..c4b0b433
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/api/api.ts.vm
@@ -0,0 +1,111 @@
+import request from '@/config/axios'
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+export interface ${simpleClassName}VO {
+#foreach ($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+ ${column.javaField}: number
+#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
+ ${column.javaField}: Date
+#else
+ ${column.javaField}: ${column.javaType.toLowerCase()}
+#end
+#end
+#end
+}
+
+#if ( $table.templateType != 2 )
+// 查询${table.classComment}分页
+export const get${simpleClassName}Page = async (params) => {
+ return await request.get({ url: `${baseURL}/page`, params })
+}
+#else
+// 查询${table.classComment}列表
+export const get${simpleClassName}List = async (params) => {
+ return await request.get({ url: `${baseURL}/list`, params })
+}
+#end
+
+// 查询${table.classComment}详情
+export const get${simpleClassName} = async (id: number) => {
+ return await request.get({ url: `${baseURL}/get?id=` + id })
+}
+
+// 新增${table.classComment}
+export const create${simpleClassName} = async (data: ${simpleClassName}VO) => {
+ return await request.post({ url: `${baseURL}/create`, data })
+}
+
+// 修改${table.classComment}
+export const update${simpleClassName} = async (data: ${simpleClassName}VO) => {
+ return await request.put({ url: `${baseURL}/update`, data })
+}
+
+// 删除${table.classComment}
+export const delete${simpleClassName} = async (id: number) => {
+ return await request.delete({ url: `${baseURL}/delete?id=` + id })
+}
+
+// 导出${table.classComment} Excel
+export const export${simpleClassName} = async (params) => {
+ return await request.download({ url: `${baseURL}/export-excel`, params })
+}
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+#set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+
+// 获得${subTable.classComment}分页
+export const get${subSimpleClassName}Page = async (params) => {
+ return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/page`, params })
+}
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+ #if ( $subTable.subJoinMany )
+
+// 获得${subTable.classComment}列表
+export const get${subSimpleClassName}ListBy${SubJoinColumnName} = async (${subJoinColumn.javaField}) => {
+ return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} })
+}
+ #else
+
+// 获得${subTable.classComment}
+export const get${subSimpleClassName}By${SubJoinColumnName} = async (${subJoinColumn.javaField}) => {
+ return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} })
+}
+ #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+// 新增${subTable.classComment}
+export const create${subSimpleClassName} = async (data) => {
+ return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data })
+}
+
+// 修改${subTable.classComment}
+export const update${subSimpleClassName} = async (data) => {
+ return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data })
+}
+
+// 删除${subTable.classComment}
+export const delete${subSimpleClassName} = async (id: number) => {
+ return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id })
+}
+
+// 获得${subTable.classComment}
+export const get${subSimpleClassName} = async (id: number) => {
+ return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id })
+}
+#end
+#end
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm
new file mode 100644
index 00000000..ed318875
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm
@@ -0,0 +1,205 @@
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm
new file mode 100644
index 00000000..d8542c3d
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm
@@ -0,0 +1,2 @@
+## 主表的 normal 和 inner 使用相同的 form 表单
+#parse("codegen/vue3/views/components/form_sub_normal.vue.vm")
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm
new file mode 100644
index 00000000..90df7981
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm
@@ -0,0 +1,362 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+
+#if ( $subTable.subJoinMany )## 情况一:一对多,table + form
+
+
+
+#foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
+ #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+ #set ($dictMethod = "getIntDictOptions")
+ #elseif ($javaType == "String")
+ #set ($dictMethod = "getStrDictOptions")
+ #elseif ($javaType == "Boolean")
+ #set ($dictMethod = "getBoolDictOptions")
+ #end
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ {{ dict.label }}
+
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ {{ dict.label }}
+
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+
+
+
+
+ #end
+ #end
+#end
+
+
+ —
+
+
+
+
+
+ + 添加${subTable.classComment}
+
+#else## 情况二:一对一,form
+
+#foreach($column in $subColumns)
+ #if ($column.createOperation || $column.updateOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
+ #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+ #set ($dictMethod = "getIntDictOptions")
+ #elseif ($javaType == "String")
+ #set ($dictMethod = "getStrDictOptions")
+ #elseif ($javaType == "Boolean")
+ #set ($dictMethod = "getBoolDictOptions")
+ #end
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
+
+
+
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+
+
+
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+
+
+
+ #elseif($column.htmlType == "editor")## 文本编辑器
+
+
+
+ #elseif($column.htmlType == "select")## 下拉框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ #else##没数据字典
+
+ #end
+
+
+ #elseif($column.htmlType == "checkbox")## 多选框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ {{ dict.label }}
+
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "radio")## 单选框
+
+
+ #if ("" != $dictType)## 有数据字典
+
+ {{ dict.label }}
+
+ #else##没数据字典
+ 请选择字典生成
+ #end
+
+
+ #elseif($column.htmlType == "datetime")## 时间框
+
+
+
+ #elseif($column.htmlType == "textarea")## 文本框
+
+
+
+ #end
+ #end
+#end
+
+#end
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
new file mode 100644
index 00000000..5ad208b3
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
@@ -0,0 +1,181 @@
+#set ($subTable = $subTables.get($subIndex))##当前表
+#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
+#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+
+
+
+#if ($table.templateType == 11)
+
+ 新增
+
+#end
+
+ #foreach($column in $subColumns)
+ #if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+ #if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
+ #elseif ($column.javaType == "LocalDateTime")## 时间类型
+
+ #elseif($column.dictType && "" != $column.dictType)## 数据字典
+
+
+
+
+
+ #else
+
+ #end
+ #end
+ #end
+ #if ($table.templateType == 11)
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+ #end
+
+ #if ($table.templateType == 11)
+
+
+ #end
+
+#if ($table.templateType == 11)
+
+ <${subSimpleClassName}Form ref="formRef" @success="getList" />
+#end
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm
new file mode 100644
index 00000000..3fe64889
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm
@@ -0,0 +1,4 @@
+## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
+## 1)inner 使用 list 不分页,erp 使用 page 分页
+## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
+#parse("codegen/vue3/views/components/list_sub_erp.vue.vm")
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/form.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/form.vue.vm
new file mode 100644
index 00000000..1c155362
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/form.vue.vm
@@ -0,0 +1,298 @@
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/index.vue.vm
new file mode 100644
index 00000000..092b54ce
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3/views/index.vue.vm
@@ -0,0 +1,373 @@
+
+
+
+
+ #foreach($column in $columns)
+ #if ($column.listOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
+ #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+ #set ($dictMethod = "getIntDictOptions")
+ #elseif ($javaType == "String")
+ #set ($dictMethod = "getStrDictOptions")
+ #elseif ($javaType == "Boolean")
+ #set ($dictMethod = "getBoolDictOptions")
+ #end
+ #if ($column.htmlType == "input")
+
+
+
+ #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+
+
+ #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+
+ #else## 未设置 dictType 数据字典的情况
+
+ #end
+
+
+ #elseif($column.htmlType == "datetime")
+ #if ($column.listOperationCondition != "BETWEEN")## 非范围
+
+
+
+ #else## 范围
+
+
+
+ #end
+ #end
+ #end
+ #end
+
+ 搜索
+ 重置
+
+ 新增
+
+
+ 导出
+
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+
+ 展开/折叠
+
+#end
+
+
+
+
+
+
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+## 特殊:树表专属逻辑
+#elseif ( $table.templateType == 2 )
+
+#else
+
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+
+
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
+
+ #end
+
+
+
+#end
+ #foreach($column in $columns)
+ #if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+ #if ($column.javaType == "LocalDateTime")## 时间类型
+
+ #elseif($column.dictType && "" != $column.dictType)## 数据字典
+
+
+
+
+
+ #else
+
+ #end
+ #end
+ #end
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+ <${simpleClassName}Form ref="formRef" @success="getList" />
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" />
+
+ #end
+
+
+#end
+
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm
new file mode 100644
index 00000000..48cd5422
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm
@@ -0,0 +1,46 @@
+import request from '@/config/axios'
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+export interface ${simpleClassName}VO {
+ #foreach ($column in $columns)
+ #if ($column.createOperation || $column.updateOperation)
+ #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+ ${column.javaField}: number
+ #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
+ ${column.javaField}: Date
+ #else
+ ${column.javaField}: ${column.javaType.toLowerCase()}
+ #end
+ #end
+ #end
+}
+
+// 查询${table.classComment}列表
+export const get${simpleClassName}Page = async (params) => {
+ return await request.get({ url: '${baseURL}/page', params })
+}
+
+// 查询${table.classComment}详情
+export const get${simpleClassName} = async (id: number) => {
+ return await request.get({ url: '${baseURL}/get?id=' + id })
+}
+
+// 新增${table.classComment}
+export const create${simpleClassName} = async (data: ${simpleClassName}VO) => {
+ return await request.post({ url: '${baseURL}/create', data })
+}
+
+// 修改${table.classComment}
+export const update${simpleClassName} = async (data: ${simpleClassName}VO) => {
+ return await request.put({ url: '${baseURL}/update', data })
+}
+
+// 删除${table.classComment}
+export const delete${simpleClassName} = async (id: number) => {
+ return await request.delete({ url: '${baseURL}/delete?id=' + id })
+}
+
+// 导出${table.classComment} Excel
+export const export${simpleClassName}Api = async (params) => {
+ return await request.download({ url: '${baseURL}/export-excel', params })
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm
new file mode 100644
index 00000000..ff4fa810
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm
@@ -0,0 +1,124 @@
+import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
+import { dateFormatter } from '@/utils/formatTime'
+
+// 表单校验
+export const rules = reactive({
+#foreach ($column in $columns)
+#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+#set($comment=$column.columnComment)
+ $column.javaField: [required],
+#end
+#end
+})
+
+// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
+const crudSchemas = reactive([
+#foreach($column in $columns)
+#if ($column.listOperation || $column.listOperationResult || $column.createOperation || $column.updateOperation)
+#set ($dictType = $column.dictType)
+#set ($javaField = $column.javaField)
+#set ($javaType = $column.javaType)
+ {
+ label: '${column.columnComment}',
+ field: '${column.javaField}',
+## ========= 字典部分 =========
+ #if ("" != $dictType)## 有数据字典
+ dictType: DICT_TYPE.$dictType.toUpperCase(),
+ #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+ dictClass: 'number',
+ #elseif ($javaType == "String")
+ dictClass: 'string',
+ #elseif ($javaType == "Boolean")
+ dictClass: 'boolean',
+ #end
+ #end
+## ========= Table 表格部分 =========
+ #if (!$column.listOperationResult)
+ isTable: false,
+ #else
+ #if ($column.htmlType == "datetime")
+ formatter: dateFormatter,
+ #end
+ #end
+## ========= Search 表格部分 =========
+ #if ($column.listOperation)
+ isSearch: true,
+ #if ($column.htmlType == "datetime")
+ search: {
+ component: 'DatePicker',
+ componentProps: {
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
+ type: 'daterange',
+ defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
+ }
+ },
+ #end
+ #end
+## ========= Form 表单部分 =========
+ #if ((!$column.createOperation && !$column.updateOperation) || $column.primaryKey)
+ isForm: false,
+ #else
+ #if($column.htmlType == "imageUpload")## 图片上传
+ form: {
+ component: 'UploadImg'
+ },
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ form: {
+ component: 'UploadFile'
+ },
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ form: {
+ component: 'Editor',
+ componentProps: {
+ valueHtml: '',
+ height: 200
+ }
+ },
+ #elseif($column.htmlType == "select")## 下拉框
+ form: {
+ component: 'SelectV2'
+ },
+ #elseif($column.htmlType == "checkbox")## 多选框
+ form: {
+ component: 'Checkbox'
+ },
+ #elseif($column.htmlType == "radio")## 单选框
+ form: {
+ component: 'Radio'
+ },
+ #elseif($column.htmlType == "datetime")## 时间框
+ form: {
+ component: 'DatePicker',
+ componentProps: {
+ type: 'datetime',
+ valueFormat: 'x'
+ }
+ },
+ #elseif($column.htmlType == "textarea")## 文本框
+ form: {
+ component: 'Input',
+ componentProps: {
+ type: 'textarea',
+ rows: 4
+ },
+ colProps: {
+ span: 24
+ }
+ },
+ #elseif(${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer")## 文本框
+ form: {
+ component: 'InputNumber',
+ value: 0
+ },
+ #end
+ #end
+ },
+#end
+#end
+ {
+ label: '操作',
+ field: 'action',
+ isForm: false
+ }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm
new file mode 100644
index 00000000..52f20a2f
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm
new file mode 100644
index 00000000..6e8f1403
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+ 新增
+
+
+
+
+
+
+
+
+
+
+
+ <${simpleClassName}Form ref="formRef" @success="getList" />
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm
new file mode 100644
index 00000000..b7f26510
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm
@@ -0,0 +1,32 @@
+import { defHttp } from '@/utils/http/axios'
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+// 查询${table.classComment}列表
+export function get${simpleClassName}Page(params) {
+ return defHttp.get({ url: '${baseURL}/page', params })
+}
+
+// 查询${table.classComment}详情
+export function get${simpleClassName}(id: number) {
+ return defHttp.get({ url: `${baseURL}/get?id=${id}` })
+}
+
+// 新增${table.classComment}
+export function create${simpleClassName}(data) {
+ return defHttp.post({ url: '${baseURL}/create', data })
+}
+
+// 修改${table.classComment}
+export function update${simpleClassName}(data) {
+ return defHttp.put({ url: '${baseURL}/update', data })
+}
+
+// 删除${table.classComment}
+export function delete${simpleClassName}(id: number) {
+ return defHttp.delete({ url: `${baseURL}/delete?id=${id}` })
+}
+
+// 导出${table.classComment} Excel
+export function export${simpleClassName}(params) {
+ return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm
new file mode 100644
index 00000000..92d3b2d7
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm
@@ -0,0 +1,236 @@
+import type {BasicColumn, FormSchema} from '@/components/Table'
+import {useRender} from '@/components/Table'
+import {DICT_TYPE, getDictOptions} from '@/utils/dict'
+
+export const columns: BasicColumn[] = [
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+#if ($column.javaType == "LocalDateTime")## 时间类型
+ {
+ title: '${comment}',
+ dataIndex: '${javaField}',
+ width: 180,
+ customRender: ({ text }) => {
+ return useRender.renderDate(text)
+ },
+ },
+#elseif("" != $column.dictType)## 数据字典
+ {
+ title: '${comment}',
+ dataIndex: '${javaField}',
+ width: 180,
+ customRender: ({ text }) => {
+ return useRender.renderDict(text, DICT_TYPE.$dictType.toUpperCase())
+ },
+ },
+#else
+ {
+ title: '${comment}',
+ dataIndex: '${javaField}',
+ width: 160,
+ },
+#end
+#end
+#end
+]
+
+export const searchFormSchema: FormSchema[] = [
+#foreach($column in $columns)
+#if ($column.listOperation)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+ {
+ label: '${comment}',
+ field: '${javaField}',
+ #if ($column.htmlType == "input")
+ component: 'Input',
+ #elseif ($column.htmlType == "select")
+ component: 'Select',
+ componentProps: {
+ #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),
+ #else## 未设置 dictType 数据字典的情况
+ options: [],
+ #end
+ },
+ #elseif ($column.htmlType == "radio")
+ component: 'Radio',
+ componentProps: {
+ #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),
+ #else## 未设置 dictType 数据字典的情况
+ options: [],
+ #end
+ },
+ #elseif($column.htmlType == "datetime")
+ component: 'RangePicker',
+ #end
+ colProps: { span: 8 },
+ },
+#end
+#end
+]
+
+export const createFormSchema: FormSchema[] = [
+ {
+ label: '编号',
+ field: 'id',
+ show: false,
+ component: 'Input',
+ },
+#foreach($column in $columns)
+#if ($column.createOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+#if (!$column.primaryKey)## 忽略主键,不用在表单里
+ {
+ label: '${comment}',
+ field: '${javaField}',
+ #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+ required: true,
+ #end
+ #if ($column.htmlType == "input")
+ component: 'Input',
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ component: 'FileUpload',
+ componentProps: {
+ fileType: 'image',
+ maxCount: 1,
+ },
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ component: 'FileUpload',
+ componentProps: {
+ fileType: 'file',
+ maxCount: 1,
+ },
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ component: 'Editor',
+ #elseif($column.htmlType == "select")## 下拉框
+ component: 'Select',
+ componentProps: {
+ #if ("" != $dictType)## 有数据字典
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+ #else##没数据字典
+ options:[],
+ #end
+ },
+ #elseif($column.htmlType == "checkbox")## 多选框
+ component: 'Checkbox',
+ componentProps: {
+ #if ("" != $dictType)## 有数据字典
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+ #else##没数据字典
+ options:[],
+ #end
+ },
+ #elseif($column.htmlType == "radio")## 单选框
+ component: 'RadioButtonGroup',
+ componentProps: {
+ #if ("" != $dictType)## 有数据字典
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+ #else##没数据字典
+ options:[],
+ #end
+ },
+ #elseif($column.htmlType == "datetime")## 时间框
+ component: 'DatePicker',
+ componentProps: {
+ showTime: true,
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'x',
+ },
+ #elseif($column.htmlType == "textarea")## 文本域
+ component: 'InputTextArea',
+ #end
+ },
+#end
+#end
+#end
+]
+
+export const updateFormSchema: FormSchema[] = [
+ {
+ label: '编号',
+ field: 'id',
+ show: false,
+ component: 'Input',
+ },
+#foreach($column in $columns)
+#if ($column.updateOperation)
+#set ($dictType = $column.dictType)
+#set ($javaField = $column.javaField)
+#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+#set ($comment = $column.columnComment)
+ #if (!$column.primaryKey)## 忽略主键,不用在表单里
+ {
+ label: '${comment}',
+ field: '${javaField}',
+ #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+ required: true,
+ #end
+ #if ($column.htmlType == "input")
+ component: 'Input',
+ #elseif($column.htmlType == "imageUpload")## 图片上传
+ component: 'FileUpload',
+ componentProps: {
+ fileType: 'image',
+ maxCount: 1,
+ },
+ #elseif($column.htmlType == "fileUpload")## 文件上传
+ component: 'FileUpload',
+ componentProps: {
+ fileType: 'file',
+ maxCount: 1,
+ },
+ #elseif($column.htmlType == "editor")## 文本编辑器
+ component: 'Editor',
+ #elseif($column.htmlType == "select")## 下拉框
+ component: 'Select',
+ componentProps: {
+ #if ("" != $dictType)## 有数据字典
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+ #else##没数据字典
+ options:[],
+ #end
+ },
+ #elseif($column.htmlType == "checkbox")## 多选框
+ component: 'Checkbox',
+ componentProps: {
+ #if ("" != $dictType)## 有数据字典
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+ #else##没数据字典
+ options:[],
+ #end
+ },
+ #elseif($column.htmlType == "radio")## 单选框
+ component: 'RadioButtonGroup',
+ componentProps: {
+ #if ("" != $dictType)## 有数据字典
+ options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
+ #else##没数据字典
+ options:[],
+ #end
+ },
+ #elseif($column.htmlType == "datetime")## 时间框
+ component: 'DatePicker',
+ componentProps: {
+ showTime: true,
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'x',
+ },
+ #elseif($column.htmlType == "textarea")## 文本域
+ component: 'InputTextArea',
+ #end
+ },
+ #end
+#end
+#end
+]
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm
new file mode 100644
index 00000000..18043651
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm
@@ -0,0 +1,58 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm
new file mode 100644
index 00000000..84ec4bf5
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+ {{ t('action.create') }}
+
+
+ {{ t('action.export') }}
+
+
+
+
+
+
+
+
+ <${simpleClassName}Modal @register="registerModal" @success="reload()" />
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/file/erweima.jpg b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/file/erweima.jpg
new file mode 100644
index 00000000..1447283c
Binary files /dev/null and b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/file/erweima.jpg differ
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/main/resources/logback-spring.xml b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/logback-spring.xml
new file mode 100644
index 00000000..b1b9f3fa
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/main/resources/logback-spring.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ${PATTERN_DEFAULT}
+
+
+
+
+
+
+
+
+
+ ${PATTERN_DEFAULT}
+
+
+
+ ${LOG_FILE}
+
+
+ ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}
+
+ ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}
+
+ ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}
+
+ ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}
+
+ ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}
+
+
+
+
+
+ 0
+
+ 256
+
+
+
+
+
+
+
+ ${PATTERN_DEFAULT}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java
new file mode 100644
index 00000000..a6de8d9e
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/DefaultDatabaseQueryTest.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.infra.service;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.generator.query.DefaultQuery;
+import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
+import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+
+import java.util.List;
+
+public class DefaultDatabaseQueryTest {
+
+ public static void main(String[] args) {
+// DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:oracle:thin:@127.0.0.1:1521:xe",
+// "root", "123456").build();
+ DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro",
+ "root", "123456").build();
+// StrategyConfig strategyConfig = new StrategyConfig.Builder().build();
+
+ ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, null, null, null, null);
+
+ DefaultQuery query = new DefaultQuery(builder);
+
+ long time = System.currentTimeMillis();
+ List tableInfos = query.queryTables();
+ for (TableInfo tableInfo : tableInfos) {
+ if (StrUtil.startWithAny(tableInfo.getName().toLowerCase(), "act_", "flw_", "qrtz_")) {
+ continue;
+ }
+ System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 1;", tableInfo.getName()));
+// System.out.println(String.format("DELETE FROM %s WHERE deleted = '1';", tableInfo.getName()));
+ }
+ System.out.println(tableInfos.size());
+ System.out.println(System.currentTimeMillis() - time);
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java
new file mode 100644
index 00000000..5d1cedbc
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java
@@ -0,0 +1,558 @@
+package cn.iocoder.yudao.module.infra.service.codegen;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.wms.dal.mysql.codegen.CodegenColumnMapper;
+import cn.iocoder.yudao.module.wms.dal.mysql.codegen.CodegenTableMapper;
+import cn.iocoder.yudao.module.wms.enums.codegen.CodegenFrontTypeEnum;
+import cn.iocoder.yudao.module.wms.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.wms.enums.codegen.CodegenTemplateTypeEnum;
+import cn.iocoder.yudao.module.wms.framework.codegen.config.CodegenProperties;
+import cn.iocoder.yudao.module.wms.service.codegen.CodegenServiceImpl;
+import cn.iocoder.yudao.module.wms.service.codegen.inner.CodegenBuilder;
+import cn.iocoder.yudao.module.wms.service.codegen.inner.CodegenEngine;
+import cn.iocoder.yudao.module.wms.service.db.DatabaseTableService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link CodegenServiceImpl} 的单元测试类
+ *
+
+ */
+@Import(CodegenServiceImpl.class)
+public class CodegenServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private CodegenServiceImpl codegenService;
+
+ @Resource
+ private CodegenTableMapper codegenTableMapper;
+ @Resource
+ private CodegenColumnMapper codegenColumnMapper;
+
+ @MockBean
+ private DatabaseTableService databaseTableService;
+
+ @MockBean
+ private AdminUserApi userApi;
+
+ @MockBean
+ private CodegenBuilder codegenBuilder;
+ @MockBean
+ private CodegenEngine codegenEngine;
+
+ @MockBean
+ private CodegenProperties codegenProperties;
+
+ @Test
+ public void testCreateCodegenList() {
+ // 准备参数
+ Long userId = randomLongId();
+ CodegenCreateListReqVO reqVO = randomPojo(CodegenCreateListReqVO.class,
+ o -> o.setDataSourceConfigId(1L).setTableNames(Collections.singletonList("t_yunai")));
+ // mock 方法(TableInfo)
+ TableInfo tableInfo = mock(TableInfo.class);
+ when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+ .thenReturn(tableInfo);
+ when(tableInfo.getComment()).thenReturn("芋艿");
+ // mock 方法(TableInfo fields)
+ TableField field01 = mock(TableField.class);
+ when(field01.getComment()).thenReturn("主键");
+ TableField field02 = mock(TableField.class);
+ when(field02.getComment()).thenReturn("名字");
+ List fields = Arrays.asList(field01, field02);
+ when(tableInfo.getFields()).thenReturn(fields);
+ // mock 方法(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class);
+ when(codegenBuilder.buildTable(same(tableInfo))).thenReturn(table);
+ // mock 方法(AdminUserRespDTO)
+ AdminUserRespDTO user = randomPojo(AdminUserRespDTO.class, o -> o.setNickname("芋头"));
+ when(userApi.getUser(eq(userId))).thenReturn(success(user));
+ // mock 方法(CodegenColumnDO)
+ List columns = randomPojoList(CodegenColumnDO.class);
+ when(codegenBuilder.buildColumns(eq(table.getId()), same(fields)))
+ .thenReturn(columns);
+ // mock 方法(CodegenProperties)
+ when(codegenProperties.getFrontType()).thenReturn(CodegenFrontTypeEnum.VUE3.getType());
+
+ // 调用
+ List result = codegenService.createCodegenList(userId, reqVO);
+ // 断言
+ assertEquals(1, result.size());
+ // 断言(CodegenTableDO)
+ CodegenTableDO dbTable = codegenTableMapper.selectList().get(0);
+ assertPojoEquals(table, dbTable);
+ assertEquals(1L, dbTable.getDataSourceConfigId());
+ assertEquals(CodegenSceneEnum.ADMIN.getScene(), dbTable.getScene());
+ assertEquals(CodegenFrontTypeEnum.VUE3.getType(), dbTable.getFrontType());
+ assertEquals("芋头", dbTable.getAuthor());
+ // 断言(CodegenColumnDO)
+ List dbColumns = codegenColumnMapper.selectList();
+ assertEquals(columns.size(), dbColumns.size());
+ assertTrue(dbColumns.get(0).getPrimaryKey());
+ for (int i = 0; i < dbColumns.size(); i++) {
+ assertPojoEquals(columns.get(i), dbColumns.get(i));
+ }
+ }
+
+ @Test
+ public void testValidateTableInfo() {
+ // 情况一
+ assertServiceException(() -> codegenService.validateTableInfo(null),
+ CODEGEN_IMPORT_TABLE_NULL);
+ // 情况二
+ TableInfo tableInfo = mock(TableInfo.class);
+ assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+ CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
+ // 情况三
+ when(tableInfo.getComment()).thenReturn("芋艿");
+ assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+ CODEGEN_IMPORT_COLUMNS_NULL);
+ // 情况四
+ TableField field = mock(TableField.class);
+ when(field.getName()).thenReturn("name");
+ when(tableInfo.getFields()).thenReturn(Collections.singletonList(field));
+ assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+ CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
+ }
+
+ @Test
+ public void testUpdateCodegen_notExists() {
+ // 准备参数
+ CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class);
+ // mock 方法
+
+ // 调用,并断言
+ assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+ CODEGEN_TABLE_NOT_EXISTS);
+ }
+
+ @Test
+ public void testUpdateCodegen_sub_masterNotExists() {
+ // mock 数据
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+ .setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(table);
+ // 准备参数
+ CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+ o -> o.getTable().setId(table.getId())
+ .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()));
+
+ // 调用,并断言
+ assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+ CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId());
+ }
+
+ @Test
+ public void testUpdateCodegen_sub_columnNotExists() {
+ // mock 数据
+ CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+ o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+ .setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(subTable);
+ // mock 数据(master)
+ CodegenTableDO masterTable = randomPojo(CodegenTableDO.class,
+ o -> o.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType())
+ .setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(masterTable);
+ // 准备参数
+ CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+ o -> o.getTable().setId(subTable.getId())
+ .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+ .setMasterTableId(masterTable.getId()));
+
+ // 调用,并断言
+ assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+ CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId());
+ }
+
+ @Test
+ public void testUpdateCodegen_success() {
+ // mock 数据
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
+ .setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(table);
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column01);
+ CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column02);
+ // 准备参数
+ CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+ o -> o.getTable().setId(table.getId())
+ .setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
+ .setScene(CodegenSceneEnum.ADMIN.getScene()));
+ CodegenColumnSaveReqVO columnVO01 = randomPojo(CodegenColumnSaveReqVO.class,
+ o -> o.setId(column01.getId()).setTableId(table.getId()));
+ CodegenColumnSaveReqVO columnVO02 = randomPojo(CodegenColumnSaveReqVO.class,
+ o -> o.setId(column02.getId()).setTableId(table.getId()));
+ updateReqVO.setColumns(Arrays.asList(columnVO01, columnVO02));
+
+ // 调用
+ codegenService.updateCodegen(updateReqVO);
+ // 断言
+ CodegenTableDO dbTable = codegenTableMapper.selectById(table.getId());
+ assertPojoEquals(updateReqVO.getTable(), dbTable);
+ List dbColumns = codegenColumnMapper.selectList();
+ assertEquals(2, dbColumns.size());
+ assertPojoEquals(columnVO01, dbColumns.get(0));
+ assertPojoEquals(columnVO02, dbColumns.get(1));
+ }
+
+ @Test
+ public void testSyncCodegenFromDB() {
+ // mock 数据(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class, o -> o.setTableName("t_yunai")
+ .setDataSourceConfigId(1L).setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(table);
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
+ .setColumnName("id"));
+ codegenColumnMapper.insert(column01);
+ CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
+ .setColumnName("name"));
+ codegenColumnMapper.insert(column02);
+ // 准备参数
+ Long tableId = table.getId();
+ // mock 方法(TableInfo)
+ TableInfo tableInfo = mock(TableInfo.class);
+ when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+ .thenReturn(tableInfo);
+ when(tableInfo.getComment()).thenReturn("芋艿");
+ // mock 方法(TableInfo fields)
+ TableField field01 = mock(TableField.class);
+ when(field01.getComment()).thenReturn("主键");
+ TableField field03 = mock(TableField.class);
+ when(field03.getComment()).thenReturn("分类");
+ List fields = Arrays.asList(field01, field03);
+ when(tableInfo.getFields()).thenReturn(fields);
+ when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+ .thenReturn(tableInfo);
+ // mock 方法(CodegenTableDO)
+ List newColumns = randomPojoList(CodegenColumnDO.class);
+ when(codegenBuilder.buildColumns(eq(table.getId()), argThat(tableFields -> {
+ assertEquals(2, tableFields.size());
+ assertSame(tableInfo.getFields(), tableFields);
+ return true;
+ }))).thenReturn(newColumns);
+
+ // 调用
+ codegenService.syncCodegenFromDB(tableId);
+ // 断言
+ List dbColumns = codegenColumnMapper.selectList();
+ assertEquals(newColumns.size(), dbColumns.size());
+ assertPojoEquals(newColumns.get(0), dbColumns.get(0));
+ assertPojoEquals(newColumns.get(1), dbColumns.get(1));
+ }
+
+ @Test
+ public void testDeleteCodegen_notExists() {
+ assertServiceException(() -> codegenService.deleteCodegen(randomLongId()),
+ CODEGEN_TABLE_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteCodegen_success() {
+ // mock 数据
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(table);
+ CodegenColumnDO column = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column);
+ // 准备参数
+ Long tableId = table.getId();
+
+ // 调用
+ codegenService.deleteCodegen(tableId);
+ // 断言
+ assertNull(codegenTableMapper.selectById(tableId));
+ assertEquals(0, codegenColumnMapper.selectList().size());
+ }
+
+ @Test
+ public void testGetCodegenTableList() {
+ // mock 数据
+ CodegenTableDO table01 = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(table01);
+ CodegenTableDO table02 = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(table02);
+ // 准备参数
+ Long dataSourceConfigId = table01.getDataSourceConfigId();
+
+ // 调用
+ List result = codegenService.getCodegenTableList(dataSourceConfigId);
+ // 断言
+ assertEquals(1, result.size());
+ assertPojoEquals(table01, result.get(0));
+ }
+
+ @Test
+ public void testGetCodegenTablePage() {
+ // mock 数据
+ CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> {
+ o.setTableName("t_yunai");
+ o.setTableComment("芋艿");
+ o.setClassName("SystemYunai");
+ o.setCreateTime(buildTime(2021, 3, 10));
+ }).setScene(CodegenSceneEnum.ADMIN.getScene());
+ codegenTableMapper.insert(tableDO);
+ // 测试 tableName 不匹配
+ codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableName(randomString())));
+ // 测试 tableComment 不匹配
+ codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableComment(randomString())));
+ // 测试 className 不匹配
+ codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setClassName(randomString())));
+ // 测试 createTime 不匹配
+ codegenTableMapper.insert(cloneIgnoreId(tableDO, logDO -> logDO.setCreateTime(buildTime(2021, 4, 10))));
+ // 准备参数
+ CodegenTablePageReqVO reqVO = new CodegenTablePageReqVO();
+ reqVO.setTableName("yunai");
+ reqVO.setTableComment("芋");
+ reqVO.setClassName("Yunai");
+ reqVO.setCreateTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
+
+ // 调用
+ PageResult pageResult = codegenService.getCodegenTablePage(reqVO);
+ // 断言,只查到了一条符合条件的
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(tableDO, pageResult.getList().get(0));
+ }
+
+ @Test
+ public void testGetCodegenTable() {
+ // mock 数据
+ CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+ codegenTableMapper.insert(tableDO);
+ // 准备参数
+ Long id = tableDO.getId();
+
+ // 调用
+ CodegenTableDO result = codegenService.getCodegenTable(id);
+ // 断言
+ assertPojoEquals(tableDO, result);
+ }
+
+ @Test
+ public void testGetCodegenColumnListByTableId() {
+ // mock 数据
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class);
+ codegenColumnMapper.insert(column01);
+ CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class);
+ codegenColumnMapper.insert(column02);
+ // 准备参数
+ Long tableId = column01.getTableId();
+
+ // 调用
+ List result = codegenService.getCodegenColumnListByTableId(tableId);
+ // 断言
+ assertEquals(1, result.size());
+ assertPojoEquals(column01, result.get(0));
+ }
+
+ @Test
+ public void testGenerationCodes_tableNotExists() {
+ assertServiceException(() -> codegenService.generationCodes(randomLongId()),
+ CODEGEN_TABLE_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGenerationCodes_columnNotExists() {
+ // mock 数据(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+ codegenTableMapper.insert(table);
+ // 准备参数
+ Long tableId = table.getId();
+
+ // 调用,并断言
+ assertServiceException(() -> codegenService.generationCodes(tableId),
+ CODEGEN_COLUMN_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGenerationCodes_sub_tableNotExists() {
+ // mock 数据(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+ codegenTableMapper.insert(table);
+ // mock 数据(CodegenColumnDO)
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column01);
+ // 准备参数
+ Long tableId = table.getId();
+
+ // 调用,并断言
+ assertServiceException(() -> codegenService.generationCodes(tableId),
+ CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
+ }
+
+ @Test
+ public void testGenerationCodes_sub_columnNotExists() {
+ // mock 数据(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+ codegenTableMapper.insert(table);
+ // mock 数据(CodegenColumnDO)
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column01);
+ // mock 数据(sub CodegenTableDO)
+ CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+ .setMasterTableId(table.getId()));
+ codegenTableMapper.insert(subTable);
+ // 准备参数
+ Long tableId = table.getId();
+
+ // 调用,并断言
+ assertServiceException(() -> codegenService.generationCodes(tableId),
+ CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());
+ }
+
+ @Test
+ public void testGenerationCodes_one_success() {
+ // mock 数据(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.ONE.getType()));
+ codegenTableMapper.insert(table);
+ // mock 数据(CodegenColumnDO)
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column01);
+ CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column02);
+ // mock 执行生成
+ Map codes = MapUtil.of(randomString(), randomString());
+ when(codegenEngine.execute(eq(table), argThat(columns -> {
+ assertEquals(2, columns.size());
+ assertEquals(column01, columns.get(0));
+ assertEquals(column02, columns.get(1));
+ return true;
+ }), isNull(), isNull())).thenReturn(codes);
+ // 准备参数
+ Long tableId = table.getId();
+
+ // 调用
+ Map result = codegenService.generationCodes(tableId);
+ // 断言
+ assertSame(codes, result);
+ }
+
+ @Test
+ public void testGenerationCodes_master_success() {
+ // mock 数据(CodegenTableDO)
+ CodegenTableDO table = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+ codegenTableMapper.insert(table);
+ // mock 数据(CodegenColumnDO)
+ CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column01);
+ CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+ codegenColumnMapper.insert(column02);
+ // mock 数据(sub CodegenTableDO)
+ CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+ .setMasterTableId(table.getId())
+ .setSubJoinColumnId(1024L));
+ codegenTableMapper.insert(subTable);
+ // mock 数据(sub CodegenColumnDO)
+ CodegenColumnDO subColumn01 = randomPojo(CodegenColumnDO.class, o -> o.setId(1024L).setTableId(subTable.getId()));
+ codegenColumnMapper.insert(subColumn01);
+ // mock 执行生成
+ Map codes = MapUtil.of(randomString(), randomString());
+ when(codegenEngine.execute(eq(table), argThat(columns -> {
+ assertEquals(2, columns.size());
+ assertEquals(column01, columns.get(0));
+ assertEquals(column02, columns.get(1));
+ return true;
+ }), argThat(tables -> {
+ assertEquals(1, tables.size());
+ assertPojoEquals(subTable, tables.get(0));
+ return true;
+ }), argThat(columns -> {
+ assertEquals(1, columns.size());
+ assertPojoEquals(subColumn01, columns.size());
+ return true;
+ }))).thenReturn(codes);
+ // 准备参数
+ Long tableId = table.getId();
+
+ // 调用
+ Map result = codegenService.generationCodes(tableId);
+ // 断言
+ assertSame(codes, result);
+ }
+
+ @Test
+ public void testGetDatabaseTableList() {
+ // 准备参数
+ Long dataSourceConfigId = randomLongId();
+ String name = randomString();
+ String comment = randomString();
+ // mock 方法
+ TableInfo tableInfo01 = mock(TableInfo.class);
+ when(tableInfo01.getName()).thenReturn("t_yunai");
+ when(tableInfo01.getComment()).thenReturn("芋艿");
+ TableInfo tableInfo02 = mock(TableInfo.class);
+ when(tableInfo02.getName()).thenReturn("t_yunai_02");
+ when(tableInfo02.getComment()).thenReturn("芋艿_02");
+ when(databaseTableService.getTableList(eq(dataSourceConfigId), eq(name), eq(comment)))
+ .thenReturn(ListUtil.toList(tableInfo01, tableInfo02));
+ // mock 数据
+ CodegenTableDO tableDO = randomPojo(CodegenTableDO.class,
+ o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+ .setTableName("t_yunai_02")
+ .setDataSourceConfigId(dataSourceConfigId));
+ codegenTableMapper.insert(tableDO);
+
+ // 调用
+ List result = codegenService.getDatabaseTableList(dataSourceConfigId, name, comment);
+ // 断言
+ assertEquals(1, result.size());
+ assertEquals("t_yunai", result.get(0).getName());
+ assertEquals("芋艿", result.get(0).getComment());
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java
new file mode 100644
index 00000000..afe2beef
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.infra.service.codegen.inner;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.wms.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.wms.service.codegen.inner.CodegenBuilder;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
+import org.apache.ibatis.type.JdbcType;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CodegenBuilderTest extends BaseMockitoUnitTest {
+
+ @InjectMocks
+ private CodegenBuilder codegenBuilder;
+
+ @Test
+ public void testBuildTable() {
+ // 准备参数
+ TableInfo tableInfo = mock(TableInfo.class);
+ // mock 方法
+ when(tableInfo.getName()).thenReturn("system_user");
+ when(tableInfo.getComment()).thenReturn("用户");
+
+ // 调用
+ CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
+ // 断言
+ assertEquals("system_user", table.getTableName());
+ assertEquals("用户", table.getTableComment());
+ assertEquals("system", table.getModuleName());
+ assertEquals("user", table.getBusinessName());
+ assertEquals("User", table.getClassName());
+ assertEquals("用户", table.getClassComment());
+ }
+
+ @Test
+ public void testBuildColumns() {
+ // 准备参数
+ Long tableId = randomLongId();
+ TableField tableField = mock(TableField.class);
+ List tableFields = Collections.singletonList(tableField);
+ // mock 方法
+ TableField.MetaInfo metaInfo = mock(TableField.MetaInfo.class);
+ when(tableField.getMetaInfo()).thenReturn(metaInfo);
+ when(metaInfo.getJdbcType()).thenReturn(JdbcType.BIGINT);
+ when(tableField.getComment()).thenReturn("编号");
+ when(tableField.isKeyFlag()).thenReturn(true);
+ when(tableField.isKeyIdentityFlag()).thenReturn(true);
+ IColumnType columnType = mock(IColumnType.class);
+ when(tableField.getColumnType()).thenReturn(columnType);
+ when(columnType.getType()).thenReturn("Long");
+ when(tableField.getName()).thenReturn("id2");
+ when(tableField.getPropertyName()).thenReturn("id");
+
+ // 调用
+ List columns = codegenBuilder.buildColumns(tableId, tableFields);
+ // 断言
+ assertEquals(1, columns.size());
+ CodegenColumnDO column = columns.get(0);
+ assertEquals(tableId, column.getTableId());
+ assertEquals("id2", column.getColumnName());
+ assertEquals("BIGINT", column.getDataType());
+ assertEquals("编号", column.getColumnComment());
+ assertFalse(column.getNullable());
+ assertTrue(column.getPrimaryKey());
+ assertTrue(column.getAutoIncrement());
+ assertEquals(1, column.getOrdinalPosition());
+ assertEquals("Long", column.getJavaType());
+ assertEquals("id", column.getJavaField());
+ assertNull(column.getDictType());
+ assertNotNull(column.getExample());
+ assertFalse(column.getCreateOperation());
+ assertTrue(column.getUpdateOperation());
+ assertFalse(column.getListOperation());
+ assertEquals("=", column.getListOperationCondition());
+ assertTrue(column.getListOperationResult());
+ assertEquals("input", column.getHtmlType());
+ }
+
+}
diff --git a/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java
new file mode 100644
index 00000000..a268a73c
--- /dev/null
+++ b/yudao-module-wms/yudao-module-wms-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java
@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.module.infra.service.codegen.inner;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.ZipUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.wms.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.wms.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.wms.framework.codegen.config.CodegenProperties;
+import cn.iocoder.yudao.module.wms.service.codegen.inner.CodegenEngine;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CodegenEngine} 的单元测试抽象基类
+ *
+
+ */
+public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
+
+ @InjectMocks
+ protected CodegenEngine codegenEngine;
+
+ @Spy
+ protected CodegenProperties codegenProperties = new CodegenProperties()
+ .setBasePackage("cn.iocoder.yudao");
+
+ @BeforeEach
+ public void setUp() {
+ codegenEngine.initGlobalBindingMap();
+ }
+
+ protected static CodegenTableDO getTable(String name) {
+ String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
+ return JsonUtils.parseObject(content, "table", CodegenTableDO.class);
+ }
+
+ protected static List getColumnList(String name) {
+ String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
+ List list = JsonUtils.parseArray(content, "columns", CodegenColumnDO.class);
+ list.forEach(column -> {
+ if (column.getNullable() == null) {
+ column.setNullable(false);
+ }
+ if (column.getCreateOperation() == null) {
+ column.setCreateOperation(false);
+ }
+ if (column.getUpdateOperation() == null) {
+ column.setUpdateOperation(false);
+ }
+ if (column.getListOperation() == null) {
+ column.setListOperation(false);
+ }
+ if (column.getListOperationResult() == null) {
+ column.setListOperationResult(false);
+ }
+ });
+ return list;
+ }
+
+ @SuppressWarnings("rawtypes")
+ protected static void assertResult(Map result, String path) {
+ String assertContent = ResourceUtil.readUtf8Str(path + "/assert.json");
+ List asserts = JsonUtils.parseArray(assertContent, HashMap.class);
+ assertEquals(asserts.size(), result.size());
+ // 校验每个文件
+ asserts.forEach(assertMap -> {
+ String contentPath = (String) assertMap.get("contentPath");
+ String filePath = (String) assertMap.get("filePath");
+ String content = ResourceUtil.readUtf8Str(path + "/" + contentPath);
+ assertEquals(content, result.get(filePath), filePath + ":不匹配");
+ });
+ }
+
+ // ==================== 调试专用 ====================
+
+ /**
+ * 【调试使用】将生成的代码,写入到文件
+ *
+ * @param result 生成的代码
+ * @param path 写入文件的路径
+ */
+ protected void writeFile(Map result, String path) {
+ // 生成压缩包
+ String[] paths = result.keySet().toArray(new String[0]);
+ ByteArrayInputStream[] ins = result.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ZipUtil.zip(outputStream, paths, ins);
+ // 写入文件
+ FileUtil.writeBytes(outputStream.toByteArray(), path);
+ }
+
+ /**
+ * 【调试使用】将生成的结果,写入到文件
+ *
+ * @param result 生成的代码
+ * @param basePath 写入文件的路径(绝对路径)
+ */
+ protected void writeResult(Map result, String basePath) {
+ // 写入文件内容
+ List