Compare commits
19 Commits
27834cca39
...
masterlzm
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b2b91249a | |||
| bac721f5c4 | |||
| c496f00fd2 | |||
| 1d26048bc1 | |||
| e278ca59bd | |||
| e3e16f73cc | |||
| 29005c5ee8 | |||
| a319567f65 | |||
| 7a2260fbf4 | |||
| 2d6bbc1086 | |||
| 53a117f4bc | |||
| 719997218c | |||
| 4bf348b847 | |||
| ab3fde0a40 | |||
| 8319ac9516 | |||
| bb5937c5b7 | |||
| 454e444632 | |||
| 09d32a7796 | |||
| e6ffbb2197 |
177
README.md
177
README.md
@@ -0,0 +1,177 @@
|
||||
# tjt_czjs_backend
|
||||
|
||||
`tjt_czjs_backend` 是特建投设计产值统计平台的后端工程。当前底座基于 `Lyzsys` 后端框架,整体属于芋道源码体系上的二次开发版本。
|
||||
|
||||
这个项目负责提供管理后台 API、权限控制、数据访问、统计计算和通用基础能力。当前仓库中已经启用了 `system`、`infra`、`demo` 等模块,特建投业务建议后续独立沉淀为 `lyzsys-module-tjt`。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- JDK 8
|
||||
|
||||
- Spring Boot 2.7.18
|
||||
|
||||
- Spring Security
|
||||
|
||||
- MyBatis Plus 3.5.15
|
||||
|
||||
- MyBatis Plus Join
|
||||
|
||||
- Dynamic Datasource
|
||||
|
||||
- Redisson
|
||||
|
||||
- Knife4j / SpringDoc
|
||||
|
||||
- Maven 多模块单体架构
|
||||
|
||||
## 快速开始
|
||||
|
||||
环境要求:
|
||||
|
||||
- JDK `1.8`
|
||||
|
||||
- Maven `3.6+`
|
||||
|
||||
- 数据库
|
||||
|
||||
- Redis
|
||||
|
||||
本地启动方式一:直接运行启动类:
|
||||
|
||||
- `lyzsys-server/src/main/java/cn/iocoder/lyzsys/server/LyzsysServerApplication.java`
|
||||
|
||||
本地启动方式二:Maven 启动:
|
||||
|
||||
```bash
|
||||
mvn -pl lyzsys-server -am spring-boot:run
|
||||
```
|
||||
|
||||
打包:
|
||||
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
运行 Jar:
|
||||
|
||||
```bash
|
||||
java -jar lyzsys-server/target/lyzsys-server.jar --spring.profiles.active=local
|
||||
```
|
||||
|
||||
## 本地配置说明
|
||||
|
||||
默认激活配置见 `lyzsys-server/src/main/resources/application.yaml`。
|
||||
|
||||
当前默认 Profile 为 `local`,当前本地端口配置为 `48080`。
|
||||
|
||||
启动前请自行核对 `application-local.yaml` 中的数据源、Redis、消息队列和其他环境相关配置。
|
||||
|
||||
## 接口文档
|
||||
|
||||
项目已接入 SpringDoc 与 Knife4j。服务启动后可访问:
|
||||
|
||||
- Swagger UI:`/swagger-ui`
|
||||
|
||||
- OpenAPI:`/v3/api-docs`
|
||||
|
||||
例如本地地址通常为:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:48080/swagger-ui
|
||||
```
|
||||
|
||||
## 关键模块
|
||||
|
||||
- `lyzsys-server`
|
||||
启动容器与 API 聚合入口
|
||||
|
||||
- `lyzsys-framework`
|
||||
公共 starter 与基础能力封装
|
||||
|
||||
- `lyzsys-module-system`
|
||||
系统管理模块
|
||||
|
||||
- `lyzsys-module-infra`
|
||||
基础设施模块
|
||||
|
||||
- `lyzsys-module-demo`
|
||||
示例业务模块,可作为新增业务的参考模板
|
||||
|
||||
- `doc`
|
||||
后端详细文档目录
|
||||
|
||||
## 当前已启用模块
|
||||
|
||||
`lyzsys-server/pom.xml` 当前实际启用:
|
||||
|
||||
- `lyzsys-module-system`
|
||||
|
||||
- `lyzsys-module-infra`
|
||||
|
||||
- `lyzsys-module-demo`
|
||||
|
||||
说明:
|
||||
|
||||
框架层面虽然预留了 `bpm`、`ai`、`crm`、`erp`、`iot` 等模块入口,但当前服务并没有全部挂载启用。
|
||||
|
||||
## 特建投业务建议落位
|
||||
|
||||
建议后续新增独立业务模块:
|
||||
|
||||
- `lyzsys-module-tjt`
|
||||
|
||||
推荐按以下方向拆分:
|
||||
|
||||
- `controller/admin/project`
|
||||
|
||||
- `controller/admin/planning`
|
||||
|
||||
- `controller/admin/output`
|
||||
|
||||
- `controller/admin/profit`
|
||||
|
||||
- `service`
|
||||
|
||||
- `dal/dataobject`
|
||||
|
||||
- `dal/mysql`
|
||||
|
||||
- `convert`
|
||||
|
||||
建议接口前缀:
|
||||
|
||||
- `/tjt/project`
|
||||
|
||||
- `/tjt/planning`
|
||||
|
||||
- `/tjt/output`
|
||||
|
||||
- `/tjt/profit`
|
||||
|
||||
## 推荐参考代码
|
||||
|
||||
如果要按现有规范快速起一个业务模块,建议优先参考 `demo/project`:
|
||||
|
||||
- `lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/ProjectController.java`
|
||||
|
||||
- `lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/`
|
||||
|
||||
- `lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/`
|
||||
|
||||
这个示例已经包含新增、修改、删除、列表分页、批量删除、Excel 导出、`@PreAuthorize` 权限控制和 `CommonResult` 统一返回。
|
||||
|
||||
## 文档入口
|
||||
|
||||
后端详细文档:
|
||||
|
||||
- `doc/README.md`
|
||||
|
||||
- `doc/部署指南.md`
|
||||
|
||||
- `doc/架构设计.md`
|
||||
|
||||
- `doc/后端开发文档.md`
|
||||
|
||||
特建投业务分析与计划文档:
|
||||
|
||||
- [`../doc/特建投业务分析与计划.md`](../doc/特建投业务分析与计划.md)
|
||||
|
||||
248
doc/README.md
Normal file
248
doc/README.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Lyzsys 项目文档导航
|
||||
|
||||
欢迎使用 Lyzsys 企业级后台管理系统!本文档导航将帮助您快速找到所需的文档资源。
|
||||
|
||||
---
|
||||
|
||||
## 📚 文档目录
|
||||
|
||||
### 🚀 快速开始
|
||||
|
||||
| 文档 | 说明 | 适用人群 |
|
||||
|-----|------|---------|
|
||||
| [项目介绍](./项目介绍.md) | 项目概述、技术栈、核心特性 | 所有人 |
|
||||
| [部署指南](./部署指南.md) | 环境搭建、安装部署、配置说明 | 运维、开发 |
|
||||
| [常见问题](./常见问题.md) | FAQ、故障排查、解决方案 | 所有人 |
|
||||
|
||||
### 🏗️ 架构设计
|
||||
|
||||
| 文档 | 说明 | 适用人群 |
|
||||
|-----|------|---------|
|
||||
| [架构设计](./架构设计.md) | 系统架构、技术选型、设计思路 | 架构师、开发 |
|
||||
| [数据库设计](./数据库设计.md) | 数据库表结构、字段说明、ER图 | 开发、DBA |
|
||||
|
||||
### 💻 开发文档
|
||||
|
||||
| 文档 | 说明 | 适用人群 | 重要程度 |
|
||||
|-----|------|---------|---------|
|
||||
| [后端开发文档](./后端开发文档.md) | 后端开发指南、核心功能、代码示例 | 后端开发 | ⭐⭐⭐⭐⭐ |
|
||||
| [前端开发文档](./前端开发文档.md) | 前端开发指南、组件使用、最佳实践 | 前端开发 | ⭐⭐⭐⭐⭐ |
|
||||
| [编码规范](./编码规范.md) | 命名规范、代码规范、最佳实践 | 所有开发 | ⭐⭐⭐⭐⭐ |
|
||||
| [新模块开发指南](./新模块开发指南.md) | 新模块开发完整教程(含 Demo 示例) | 所有开发 | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 按场景查找文档
|
||||
|
||||
### 场景 1:我是新加入的开发人员
|
||||
|
||||
**推荐阅读顺序**:
|
||||
1. [项目介绍](./项目介绍.md) - 了解项目概况
|
||||
2. [部署指南](./部署指南.md) - 搭建开发环境
|
||||
3. [编码规范](./编码规范.md) - 熟悉编码规范 ⚠️ **必读**
|
||||
4. [后端开发文档](./后端开发文档.md) 或 [前端开发文档](./前端开发文档.md) - 根据岗位选择
|
||||
5. [新模块开发指南](./新模块开发指南.md) - 学习如何开发新功能
|
||||
|
||||
### 场景 2:我要开发一个新的业务模块
|
||||
|
||||
**推荐步骤**:
|
||||
1. **必读** → [编码规范](./编码规范.md) - 了解命名规范和菜单结构规范
|
||||
2. **必读** → [新模块开发指南](./新模块开发指南.md) - 按照步骤创建模块
|
||||
3. **参考** → Demo 模块代码(`lyzsys-module-demo`)- 作为实现参考
|
||||
4. **参考** → [后端开发文档](./后端开发文档.md) - 查阅具体技术实现
|
||||
5. **参考** → [前端开发文档](./前端开发文档.md) - 前端页面开发
|
||||
|
||||
### 场景 3:我遇到了问题
|
||||
|
||||
**查找方式**:
|
||||
1. [常见问题](./常见问题.md) - 先查看 FAQ
|
||||
2. [后端开发文档](./后端开发文档.md) - 查看附录的常见问题
|
||||
3. GitHub Issues - 搜索或提交问题
|
||||
|
||||
### 场景 4:我要了解系统架构
|
||||
|
||||
**推荐阅读**:
|
||||
1. [项目介绍](./项目介绍.md) - 快速了解
|
||||
2. [架构设计](./架构设计.md) - 详细架构说明
|
||||
3. [数据库设计](./数据库设计.md) - 数据模型
|
||||
4. [后端开发文档](./后端开发文档.md) - 核心功能实现
|
||||
|
||||
---
|
||||
|
||||
## 📖 重点文档说明
|
||||
|
||||
### 🔥 编码规范(必读)
|
||||
|
||||
**文件**:[编码规范.md](./编码规范.md)
|
||||
|
||||
**核心内容**:
|
||||
- ⚠️ **驼峰命名规范**:第一个单词不能只有一个字母(重要!)
|
||||
- 📋 **菜单结构规范**:业务菜单优先,系统管理放最后
|
||||
- 🏗️ **代码结构规范**:Controller-Service-DAL 三层架构
|
||||
- 💾 **数据库设计规范**:表名、字段名、索引规范
|
||||
- 🔌 **API 接口规范**:RESTful 风格、统一响应格式
|
||||
- 🎨 **前端开发规范**:组件规范、权限控制
|
||||
|
||||
**为什么重要**:
|
||||
- 避免 MyBatis Plus 字段映射问题
|
||||
- 提高代码可读性和可维护性
|
||||
- 统一团队开发风格
|
||||
|
||||
### 🚀 新模块开发指南(必读)
|
||||
|
||||
**文件**:[新模块开发指南.md](./新模块开发指南.md)
|
||||
|
||||
**核心内容**:
|
||||
- ✅ 完整的开发流程(从数据库设计到前端页面)
|
||||
- 📝 逐步操作指南(包含完整代码示例)
|
||||
- 🎯 以 **Demo 模块**为参考案例
|
||||
- ✔️ 开发检查清单
|
||||
|
||||
**包含内容**:
|
||||
1. 创建后端模块(Maven 配置、实体类、Service、Controller)
|
||||
2. 创建前端页面(API 接口、列表页、表单组件)
|
||||
3. 配置菜单权限(一级菜单、二级菜单、按钮权限)
|
||||
4. 常见问题解决
|
||||
|
||||
### 📦 Demo 示例模块
|
||||
|
||||
**模块位置**:
|
||||
- 后端:`lyzsys_backend/lyzsys-module-demo`
|
||||
- 前端:`lyzsys-ui-admin/src/views/demo/project`
|
||||
- SQL:`lyzsys_backend/sql/mysql/demo_*.sql`
|
||||
|
||||
**功能展示**:
|
||||
- ✅ 项目管理的完整 CRUD 功能
|
||||
- ✅ 分页查询、批量删除、Excel 导出
|
||||
- ✅ 遵循所有编码规范的标准实现
|
||||
- ✅ 前后端完整联调示例
|
||||
|
||||
**如何使用**:
|
||||
1. 查看 Demo 模块的代码结构
|
||||
2. 参考其命名方式和代码风格
|
||||
3. 复制并修改为自己的业务模块
|
||||
4. 详细步骤见 [新模块开发指南](./新模块开发指南.md)
|
||||
|
||||
---
|
||||
|
||||
## 📂 文档结构
|
||||
|
||||
```
|
||||
doc/
|
||||
├── README.md # 📖 本文档(文档导航)
|
||||
├── 编码规范.md # ⭐⭐⭐⭐⭐ 命名规范、开发规范
|
||||
├── 新模块开发指南.md # ⭐⭐⭐⭐⭐ 新模块开发教程
|
||||
├── 后端开发文档.md # 后端开发指南
|
||||
├── 前端开发文档.md # 前端开发指南
|
||||
├── 项目介绍.md # 项目概述
|
||||
├── 架构设计.md # 架构设计文档
|
||||
├── 数据库设计.md # 数据库设计文档
|
||||
├── 部署指南.md # 部署运维文档
|
||||
└── 常见问题.md # FAQ 文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 关键规范速查
|
||||
|
||||
### 驼峰命名规范
|
||||
|
||||
⚠️ **核心原则**:第一个单词不能只有一个字母
|
||||
|
||||
```java
|
||||
// ✅ 正确
|
||||
projectId, projectName, establishDate
|
||||
|
||||
// ❌ 错误
|
||||
pId, pName, eDate
|
||||
```
|
||||
|
||||
**详细说明**:[编码规范 - 命名规范](./编码规范.md#1-命名规范)
|
||||
|
||||
### 菜单结构规范
|
||||
|
||||
```
|
||||
1-90. 业务菜单(优先展示)
|
||||
98. 系统管理
|
||||
99. 基础设施
|
||||
```
|
||||
|
||||
**详细说明**:[编码规范 - 菜单结构规范](./编码规范.md#2-菜单结构规范)
|
||||
|
||||
### 权限标识格式
|
||||
|
||||
```
|
||||
{模块名}:{业务}:{操作}
|
||||
|
||||
示例:
|
||||
demo:project:query
|
||||
demo:project:create
|
||||
demo:project:update
|
||||
```
|
||||
|
||||
**详细说明**:[编码规范 - 菜单配置规范](./编码规范.md#23-权限标识规范)
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 开发工具推荐
|
||||
|
||||
### 后端开发
|
||||
- **IDE**:IntelliJ IDEA(推荐)
|
||||
- **JDK**:1.8+
|
||||
- **Maven**:3.6+
|
||||
- **数据库工具**:Navicat、DataGrip
|
||||
|
||||
### 前端开发
|
||||
- **IDE**:VSCode、WebStorm
|
||||
- **Node.js**:16+
|
||||
- **包管理器**:npm、pnpm
|
||||
|
||||
### 版本管理
|
||||
- **Git 客户端**:Git、SourceTree、GitKraken
|
||||
- **代码托管**:GitHub、GitLab
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
### 文档问题
|
||||
- 查看 [常见问题](./常见问题.md)
|
||||
- 搜索 GitHub Issues
|
||||
|
||||
### 技术支持
|
||||
- 提交 GitHub Issue
|
||||
- 联系开发团队
|
||||
|
||||
### 参与贡献
|
||||
- Fork 项目
|
||||
- 提交 Pull Request
|
||||
- 完善文档
|
||||
|
||||
---
|
||||
|
||||
## 📝 文档更新记录
|
||||
|
||||
| 日期 | 版本 | 更新内容 | 作者 |
|
||||
|-----|------|---------|------|
|
||||
| 2024-01 | v1.0 | 创建文档导航,新增编码规范和新模块开发指南 | 开发团队 |
|
||||
| 2024-01 | v1.0 | 添加 Demo 示例模块说明 | 开发团队 |
|
||||
|
||||
---
|
||||
|
||||
## 🎉 开始使用
|
||||
|
||||
如果您是第一次使用 Lyzsys,建议按以下顺序阅读文档:
|
||||
|
||||
1. ✅ [项目介绍](./项目介绍.md) - 5分钟了解项目
|
||||
2. ✅ [部署指南](./部署指南.md) - 搭建开发环境
|
||||
3. ✅ [编码规范](./编码规范.md) - **必读**,熟悉编码规范
|
||||
4. ✅ [新模块开发指南](./新模块开发指南.md) - 开发第一个模块
|
||||
5. ✅ [后端开发文档](./后端开发文档.md) / [前端开发文档](./前端开发文档.md) - 深入学习
|
||||
|
||||
祝您使用愉快!🚀
|
||||
|
||||
---
|
||||
|
||||
**文档维护**:Lyzsys 开发团队
|
||||
**最后更新**:2024年
|
||||
**文档版本**:v1.0
|
||||
43
doc/demo_menu.sql
Normal file
43
doc/demo_menu.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- ----------------------------
|
||||
-- 菜单配置 SQL - 项目管理模块
|
||||
-- ----------------------------
|
||||
|
||||
-- 一级菜单:项目管理(放在最前面)
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3000, '项目管理', '', 1, 1, 0, '/demo', 'ep:files', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- 二级菜单:项目列表
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3001, '项目列表', '', 2, 1, 3000, 'project', 'ep:document', 'demo/project/index', 'DemoProject', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- 按钮权限:查询
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3002, '项目查询', 'demo:project:query', 3, 1, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- 按钮权限:新增
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3003, '项目新增', 'demo:project:create', 3, 2, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- 按钮权限:修改
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3004, '项目修改', 'demo:project:update', 3, 3, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- 按钮权限:删除
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3005, '项目删除', 'demo:project:delete', 3, 4, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- 按钮权限:导出
|
||||
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
VALUES (3006, '项目导出', 'demo:project:export', 3, 5, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
|
||||
|
||||
-- ----------------------------
|
||||
-- 调整系统管理和基础设施菜单的排序(放到最后)
|
||||
-- 注意:这里假设系统管理菜单 id=1,基础设施菜单 id=2
|
||||
-- 如果 id 不同,请根据实际情况修改
|
||||
-- ----------------------------
|
||||
|
||||
-- 将系统管理菜单 sort 调整为 98
|
||||
UPDATE `system_menu` SET `sort` = 98, `updater` = '1', `update_time` = NOW() WHERE `id` = 1 AND `deleted` = b'0';
|
||||
|
||||
-- 将基础设施菜单 sort 调整为 99
|
||||
UPDATE `system_menu` SET `sort` = 99, `updater` = '1', `update_time` = NOW() WHERE `id` = 2 AND `deleted` = b'0';
|
||||
25
doc/demo_project.sql
Normal file
25
doc/demo_project.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- ----------------------------
|
||||
-- Table structure for demo_project
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `demo_project`;
|
||||
CREATE TABLE `demo_project` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '项目ID',
|
||||
`project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目名称',
|
||||
`project_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目编号',
|
||||
`establish_date` datetime NULL DEFAULT NULL COMMENT '立项时间',
|
||||
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '项目管理表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of demo_project (示例数据)
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `demo_project` (`id`, `project_name`, `project_code`, `establish_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
|
||||
VALUES (1, '示例项目一', 'PROJ-2024-001', '2024-01-01 00:00:00', '1', '2024-01-01 10:00:00', '1', '2024-01-01 10:00:00', b'0', 1);
|
||||
COMMIT;
|
||||
259
doc/demo_部署说明.md
Normal file
259
doc/demo_部署说明.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# 项目管理模块部署说明
|
||||
|
||||
## 一、概述
|
||||
|
||||
本文档说明如何部署 lyzsys-module-demo 项目管理模块。
|
||||
|
||||
### 模块信息
|
||||
- **模块名称**: lyzsys-module-demo
|
||||
- **功能**: 项目管理(增删改查导出)
|
||||
- **数据库表**: demo_project
|
||||
- **菜单结构**:
|
||||
- 一级菜单:项目管理
|
||||
- 二级菜单:项目列表
|
||||
|
||||
### 命名规范遵循
|
||||
✅ 所有字段名遵循驼峰命名规范,第一个单词不能只有一个字母
|
||||
- `projectName`(而非 `pName`)
|
||||
- `projectCode`(而非 `pCode`)
|
||||
- `establishDate`(而非 `eDate`)
|
||||
|
||||
## 二、部署步骤
|
||||
|
||||
### 1. 数据库部署
|
||||
|
||||
按顺序执行以下 SQL 脚本:
|
||||
|
||||
```bash
|
||||
# 1. 创建项目管理表
|
||||
mysql> source e:/workspace/lyzsys/lyzsys_backend/sql/mysql/demo_project.sql
|
||||
|
||||
# 2. 创建菜单配置
|
||||
mysql> source e:/workspace/lyzsys/lyzsys_backend/sql/mysql/demo_menu.sql
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `demo_project.sql` - 创建项目管理表,包含示例数据
|
||||
- `demo_menu.sql` - 创建菜单配置,包含一级菜单、二级菜单和按钮权限
|
||||
|
||||
### 2. 后端部署
|
||||
|
||||
后端代码已自动集成到项目中,无需额外配置。
|
||||
|
||||
**后端文件清单**:
|
||||
|
||||
```
|
||||
lyzsys_backend/lyzsys-module-demo/
|
||||
├── pom.xml # Maven 配置
|
||||
└── src/main/java/cn/iocoder/lyzsys/module/demo/
|
||||
├── controller/admin/project/
|
||||
│ ├── ProjectController.java # 控制器
|
||||
│ └── vo/
|
||||
│ ├── ProjectPageReqVO.java # 分页查询请求
|
||||
│ ├── ProjectRespVO.java # 响应 VO
|
||||
│ └── ProjectSaveReqVO.java # 保存请求 VO
|
||||
├── service/project/
|
||||
│ ├── ProjectService.java # 服务接口
|
||||
│ └── ProjectServiceImpl.java # 服务实现
|
||||
├── dal/
|
||||
│ ├── dataobject/project/
|
||||
│ │ └── ProjectDO.java # 数据对象
|
||||
│ └── mysql/project/
|
||||
│ └── ProjectMapper.java # Mapper 接口
|
||||
└── enums/
|
||||
└── ErrorCodeConstants.java # 错误码常量
|
||||
```
|
||||
|
||||
**验证后端部署**:
|
||||
1. Maven 重新导入项目依赖
|
||||
2. 确认 `lyzsys-server/pom.xml` 包含 `lyzsys-module-demo` 依赖
|
||||
3. 启动后端项目,查看日志确认模块加载成功
|
||||
|
||||
### 3. 前端部署
|
||||
|
||||
前端代码已生成,无需额外配置。
|
||||
|
||||
**前端文件清单**:
|
||||
|
||||
```
|
||||
lyzsys-ui-admin/src/
|
||||
├── api/demo/project/
|
||||
│ └── index.ts # API 接口定义
|
||||
└── views/demo/project/
|
||||
├── index.vue # 项目列表页面
|
||||
└── ProjectForm.vue # 项目表单组件
|
||||
```
|
||||
|
||||
**验证前端部署**:
|
||||
1. 重新启动前端项目(如果正在运行)
|
||||
2. 使用管理员账号登录系统
|
||||
3. 在左侧菜单栏查看"项目管理"菜单
|
||||
|
||||
## 三、功能验证
|
||||
|
||||
### 1. 菜单验证
|
||||
|
||||
登录后台管理系统,应该看到以下菜单结构:
|
||||
|
||||
```
|
||||
项目管理(一级菜单)
|
||||
└── 项目列表(二级菜单)
|
||||
系统管理(已调整到最后)
|
||||
基础设施(已调整到最后)
|
||||
```
|
||||
|
||||
### 2. 权限验证
|
||||
|
||||
项目列表页面应包含以下操作按钮:
|
||||
- ✅ 搜索
|
||||
- ✅ 重置
|
||||
- ✅ 新增
|
||||
- ✅ 导出
|
||||
- ✅ 批量删除
|
||||
- ✅ 修改(每行)
|
||||
- ✅ 删除(每行)
|
||||
|
||||
### 3. 功能验证
|
||||
|
||||
测试以下功能:
|
||||
|
||||
#### 查询功能
|
||||
- [ ] 按项目名称模糊查询
|
||||
- [ ] 按项目编号模糊查询
|
||||
- [ ] 按立项时间范围查询
|
||||
- [ ] 分页查询
|
||||
|
||||
#### 新增功能
|
||||
- [ ] 点击"新增"按钮打开表单
|
||||
- [ ] 必填字段验证(项目名称、项目编号)
|
||||
- [ ] 项目编号唯一性校验
|
||||
- [ ] 保存成功后刷新列表
|
||||
|
||||
#### 修改功能
|
||||
- [ ] 点击"修改"按钮打开表单
|
||||
- [ ] 回显原有数据
|
||||
- [ ] 修改后保存成功
|
||||
|
||||
#### 删除功能
|
||||
- [ ] 单个删除确认提示
|
||||
- [ ] 删除成功后刷新列表
|
||||
- [ ] 批量删除功能
|
||||
|
||||
#### 导出功能
|
||||
- [ ] 点击"导出"按钮
|
||||
- [ ] 下载 Excel 文件
|
||||
- [ ] 验证导出数据正确
|
||||
|
||||
## 四、接口文档
|
||||
|
||||
### API 端点
|
||||
|
||||
**Base URL**: `/demo/project`
|
||||
|
||||
| 接口 | 方法 | 说明 | 权限 |
|
||||
|-----|------|------|------|
|
||||
| `/create` | POST | 创建项目 | demo:project:create |
|
||||
| `/update` | PUT | 修改项目 | demo:project:update |
|
||||
| `/delete` | DELETE | 删除项目 | demo:project:delete |
|
||||
| `/delete-list` | DELETE | 批量删除 | demo:project:delete |
|
||||
| `/page` | GET | 分页查询 | demo:project:query |
|
||||
| `/get` | GET | 查询详情 | demo:project:query |
|
||||
| `/export-excel` | GET | 导出Excel | demo:project:export |
|
||||
|
||||
### 请求示例
|
||||
|
||||
**创建项目**:
|
||||
```json
|
||||
POST /demo/project/create
|
||||
{
|
||||
"projectName": "示例项目",
|
||||
"projectCode": "PROJ-2024-001",
|
||||
"establishDate": "2024-01-01 00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**查询列表**:
|
||||
```
|
||||
GET /demo/project/page?pageNo=1&pageSize=10&projectName=示例
|
||||
```
|
||||
|
||||
## 五、常见问题
|
||||
|
||||
### 1. 菜单不显示
|
||||
|
||||
**原因**:可能是权限问题
|
||||
**解决**:
|
||||
1. 确认已执行 `demo_menu.sql`
|
||||
2. 检查当前登录用户的角色权限
|
||||
3. 在"系统管理 > 菜单管理"中确认菜单已创建
|
||||
4. 在"系统管理 > 角色管理"中给当前角色分配菜单权限
|
||||
|
||||
### 2. 后端接口 404
|
||||
|
||||
**原因**:模块未启动或路由配置错误
|
||||
**解决**:
|
||||
1. 检查 `lyzsys-server/pom.xml` 是否包含 demo 模块依赖
|
||||
2. 重新编译启动项目
|
||||
3. 查看启动日志确认模块加载
|
||||
|
||||
### 3. 前端页面报错
|
||||
|
||||
**原因**:API 接口路径不匹配
|
||||
**解决**:
|
||||
1. 检查 `src/api/demo/project/index.ts` 中的接口路径
|
||||
2. 确认后端 Controller 的 `@RequestMapping` 路径
|
||||
3. 查看浏览器控制台错误信息
|
||||
|
||||
### 4. 项目编号重复错误
|
||||
|
||||
**原因**:唯一性校验生效
|
||||
**解决**:这是正常的业务校验,请使用不同的项目编号
|
||||
|
||||
## 六、扩展开发
|
||||
|
||||
### 添加新字段
|
||||
|
||||
1. **修改数据库表**:
|
||||
```sql
|
||||
ALTER TABLE demo_project ADD COLUMN new_field VARCHAR(100) COMMENT '新字段';
|
||||
```
|
||||
|
||||
2. **修改后端代码**:
|
||||
- 在 `ProjectDO.java` 中添加字段
|
||||
- 在 `ProjectRespVO.java` 和 `ProjectSaveReqVO.java` 中添加字段
|
||||
- 根据需要修改 `ProjectMapper.java` 查询条件
|
||||
|
||||
3. **修改前端代码**:
|
||||
- 在 `api/demo/project/index.ts` 中添加字段定义
|
||||
- 在 `ProjectForm.vue` 中添加表单项
|
||||
- 在 `index.vue` 中添加表格列
|
||||
|
||||
### 添加新功能
|
||||
|
||||
参考现有的增删改查实现,遵循以下开发流程:
|
||||
1. 在 Service 层添加业务方法
|
||||
2. 在 Controller 层添加接口
|
||||
3. 在前端 API 文件中添加接口调用
|
||||
4. 在前端页面中实现 UI 交互
|
||||
|
||||
## 七、技术栈
|
||||
|
||||
### 后端
|
||||
- Spring Boot 2.7.18
|
||||
- MyBatis Plus 3.5.15
|
||||
- MySQL 8.0+
|
||||
|
||||
### 前端
|
||||
- Vue 3.5.12
|
||||
- Element Plus 2.11.1
|
||||
- TypeScript 5.3.3
|
||||
- Vite 5.1.4
|
||||
|
||||
## 八、联系方式
|
||||
|
||||
如有问题,请联系开发团队或查阅项目文档。
|
||||
|
||||
---
|
||||
|
||||
**生成时间**: 2024年
|
||||
**版本**: v1.0
|
||||
216
doc/后端开发文档.md
216
doc/后端开发文档.md
@@ -828,16 +828,224 @@ lyzsys:
|
||||
|
||||
---
|
||||
|
||||
## Demo 示例模块
|
||||
|
||||
### 模块简介
|
||||
|
||||
**lyzsys-module-demo** 是一个完整的业务模块示例,展示了如何在 Lyzsys 系统中快速创建一个新的业务模块。该模块实现了项目管理的完整 CRUD 功能,包括增删改查和 Excel 导出。
|
||||
|
||||
### 功能特性
|
||||
|
||||
- ✅ 完整的 CRUD 接口(增删改查)
|
||||
- ✅ 分页查询支持
|
||||
- ✅ 批量删除功能
|
||||
- ✅ Excel 导出功能
|
||||
- ✅ 业务字段唯一性校验
|
||||
- ✅ 权限控制
|
||||
- ✅ 前后端完整实现
|
||||
|
||||
### 模块结构
|
||||
|
||||
```
|
||||
lyzsys-module-demo/
|
||||
├── pom.xml # Maven 配置
|
||||
└── src/main/java/cn/iocoder/lyzsys/module/demo/
|
||||
├── controller/admin/project/ # 控制器层
|
||||
│ ├── ProjectController.java # 项目控制器
|
||||
│ └── vo/ # VO 对象
|
||||
│ ├── ProjectPageReqVO.java # 分页查询请求
|
||||
│ ├── ProjectRespVO.java # 响应 VO
|
||||
│ └── ProjectSaveReqVO.java # 保存请求 VO
|
||||
├── service/project/ # 服务层
|
||||
│ ├── ProjectService.java # 服务接口
|
||||
│ └── ProjectServiceImpl.java # 服务实现
|
||||
├── dal/ # 数据访问层
|
||||
│ ├── dataobject/project/ # 数据对象
|
||||
│ │ └── ProjectDO.java # 项目实体类
|
||||
│ └── mysql/project/ # Mapper 接口
|
||||
│ └── ProjectMapper.java # 项目 Mapper
|
||||
└── enums/ # 枚举类
|
||||
└── ErrorCodeConstants.java # 错误码常量
|
||||
```
|
||||
|
||||
### 核心代码示例
|
||||
|
||||
#### 实体类(ProjectDO)
|
||||
|
||||
```java
|
||||
@TableName("demo_project")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectDO extends BaseDO {
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
// 遵循命名规范:第一个单词不能只有一个字母
|
||||
private String projectName; // ✅ 正确(而非 pName)
|
||||
private String projectCode; // ✅ 正确(而非 pCode)
|
||||
private LocalDateTime establishDate; // ✅ 正确(而非 eDate)
|
||||
}
|
||||
```
|
||||
|
||||
#### Controller 示例
|
||||
|
||||
```java
|
||||
@Tag(name = "管理后台 - 项目管理")
|
||||
@RestController
|
||||
@RequestMapping("/demo/project")
|
||||
public class ProjectController {
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建项目")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:create')")
|
||||
public CommonResult<Long> createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
|
||||
return success(projectService.createProject(createReqVO));
|
||||
}
|
||||
|
||||
// 其他 CRUD 方法...
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库设计
|
||||
|
||||
```sql
|
||||
CREATE TABLE `demo_project` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '项目ID',
|
||||
`project_name` varchar(100) NOT NULL COMMENT '项目名称',
|
||||
`project_code` varchar(50) NOT NULL COMMENT '项目编号',
|
||||
`establish_date` datetime NULL COMMENT '立项时间',
|
||||
-- 标准字段
|
||||
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB COMMENT='项目管理表';
|
||||
```
|
||||
|
||||
### 菜单配置
|
||||
|
||||
Demo 模块的菜单结构:
|
||||
|
||||
```
|
||||
项目管理(一级菜单,sort: 1)
|
||||
└── 项目列表(二级菜单,sort: 1)
|
||||
├── 项目查询(demo:project:query)
|
||||
├── 项目新增(demo:project:create)
|
||||
├── 项目修改(demo:project:update)
|
||||
├── 项目删除(demo:project:delete)
|
||||
└── 项目导出(demo:project:export)
|
||||
```
|
||||
|
||||
### 前端实现
|
||||
|
||||
前端代码位置:
|
||||
- API 接口:`src/api/demo/project/index.ts`
|
||||
- 列表页面:`src/views/demo/project/index.vue`
|
||||
- 表单组件:`src/views/demo/project/ProjectForm.vue`
|
||||
|
||||
### 作为参考模板
|
||||
|
||||
Demo 模块可以作为新业务模块开发的参考模板,包含了:
|
||||
|
||||
1. **标准的代码结构** - 遵循项目分层架构
|
||||
2. **完整的业务逻辑** - 包括校验、异常处理等
|
||||
3. **规范的命名方式** - 遵循驼峰命名规范
|
||||
4. **权限控制示例** - 展示如何配置权限
|
||||
5. **前后端联调** - 完整的前后端交互流程
|
||||
|
||||
**详细开发指南请参考**:
|
||||
- [新模块开发指南](./新模块开发指南.md) - 基于 demo 模块的完整开发教程
|
||||
- [编码规范](./编码规范.md) - 命名规范、菜单结构规范等
|
||||
|
||||
---
|
||||
|
||||
## 开发规范
|
||||
|
||||
为确保代码质量和团队协作效率,Lyzsys 项目制定了一系列开发规范。
|
||||
|
||||
### 核心规范文档
|
||||
|
||||
1. **[编码规范](./编码规范.md)** - 必读 ⭐⭐⭐⭐⭐
|
||||
- 驼峰命名规范(重要:第一个单词不能只有一个字母)
|
||||
- 菜单结构规范
|
||||
- 代码结构规范
|
||||
- 数据库设计规范
|
||||
- API 接口规范
|
||||
- 前端开发规范
|
||||
|
||||
2. **[新模块开发指南](./新模块开发指南.md)** - 实战教程 ⭐⭐⭐⭐⭐
|
||||
- 快速开始
|
||||
- 创建后端模块(完整步骤)
|
||||
- 创建前端页面
|
||||
- 配置菜单权限
|
||||
- 以 Demo 模块为参考示例
|
||||
|
||||
### 关键规范要点
|
||||
|
||||
#### 命名规范核心原则
|
||||
|
||||
⚠️ **重要**:所有字段名、类名的驼峰命名中,**第一个单词不能只有一个字母**
|
||||
|
||||
```java
|
||||
// ✅ 正确示例
|
||||
private String projectId; // 而非 pId
|
||||
private String projectName; // 而非 pName
|
||||
private String establishDate; // 而非 eDate
|
||||
|
||||
// ❌ 错误示例
|
||||
private String pId; // 单字母开头,可能导致 MyBatis Plus 映射问题
|
||||
private String mType; // 单字母开头
|
||||
```
|
||||
|
||||
#### 菜单结构规范
|
||||
|
||||
业务功能菜单优先展示,系统管理功能放在最后:
|
||||
|
||||
```
|
||||
1-90. 业务菜单(项目管理、客户管理等)
|
||||
98. 系统管理
|
||||
99. 基础设施
|
||||
```
|
||||
|
||||
#### 权限标识格式
|
||||
|
||||
```
|
||||
格式:{模块名}:{业务}:{操作}
|
||||
|
||||
示例:
|
||||
demo:project:query # 查询
|
||||
demo:project:create # 新增
|
||||
demo:project:update # 修改
|
||||
demo:project:delete # 删除
|
||||
demo:project:export # 导出
|
||||
```
|
||||
|
||||
### 开发流程建议
|
||||
|
||||
1. **阅读规范文档** → 了解编码规范和开发流程
|
||||
2. **参考 Demo 模块** → 理解标准的实现方式
|
||||
3. **按照开发指南** → 逐步创建新模块
|
||||
4. **代码评审** → 确保符合规范要求
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### Q1: 如何添加新的业务模块?
|
||||
|
||||
1. 在 `lyzsys-module-xxx` 目录下创建新模块
|
||||
2. 按照标准模块结构创建包和类
|
||||
3. 在 `lyzsys-server/pom.xml` 中添加模块依赖
|
||||
4. 在 `application.yaml` 中配置模块扫描路径
|
||||
**推荐方式**:参考 [新模块开发指南](./新模块开发指南.md),以 Demo 模块为模板创建新模块。
|
||||
|
||||
**快速步骤**:
|
||||
1. 创建 Maven 模块和目录结构
|
||||
2. 编写数据库表(遵循命名规范)
|
||||
3. 按照 Demo 模块的结构创建后端代码
|
||||
4. 创建前端页面和 API 接口
|
||||
5. 配置菜单和权限
|
||||
|
||||
#### Q2: 如何自定义异常?
|
||||
|
||||
|
||||
931
doc/新模块开发指南.md
Normal file
931
doc/新模块开发指南.md
Normal file
@@ -0,0 +1,931 @@
|
||||
# Lyzsys 新模块开发指南
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 快速开始](#1-快速开始)
|
||||
- [2. 创建后端模块](#2-创建后端模块)
|
||||
- [3. 创建前端页面](#3-创建前端页面)
|
||||
- [4. 配置菜单权限](#4-配置菜单权限)
|
||||
- [5. 完整示例](#5-完整示例)
|
||||
- [6. 常见问题](#6-常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 1. 快速开始
|
||||
|
||||
本指南以 **lyzsys-module-demo** 项目管理模块为参考,演示如何快速创建一个新的业务模块。
|
||||
|
||||
### 1.1 开发流程
|
||||
|
||||
```
|
||||
1. 设计数据库表
|
||||
↓
|
||||
2. 创建后端模块
|
||||
├── 创建 Maven 模块
|
||||
├── 编写实体类(DO)
|
||||
├── 编写 Mapper 接口
|
||||
├── 编写 VO 类
|
||||
├── 编写 Service 层
|
||||
└── 编写 Controller 层
|
||||
↓
|
||||
3. 创建前端页面
|
||||
├── 编写 API 接口
|
||||
├── 编写列表页面
|
||||
└── 编写表单组件
|
||||
↓
|
||||
4. 配置菜单权限
|
||||
├── 创建一级菜单
|
||||
├── 创建二级菜单
|
||||
└── 创建按钮权限
|
||||
↓
|
||||
5. 测试验证
|
||||
```
|
||||
|
||||
### 1.2 前置准备
|
||||
|
||||
- [ ] 确认数据库表设计
|
||||
- [ ] 确认菜单结构(一级、二级菜单名称)
|
||||
- [ ] 确认业务字段和验证规则
|
||||
- [ ] 准备好模块名称和权限标识
|
||||
|
||||
---
|
||||
|
||||
## 2. 创建后端模块
|
||||
|
||||
### 2.1 创建 Maven 模块
|
||||
|
||||
#### 步骤 1:创建模块目录
|
||||
|
||||
在 `lyzsys_backend` 下创建新模块:
|
||||
|
||||
```
|
||||
lyzsys_backend/
|
||||
└── lyzsys-module-{模块名}/
|
||||
├── pom.xml
|
||||
└── src/main/java/cn/iocoder/lyzsys/module/{模块名}/
|
||||
```
|
||||
|
||||
#### 步骤 2:编写 pom.xml
|
||||
|
||||
参考 `lyzsys-module-demo/pom.xml`:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>lyzsys-module-{模块名}</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
{模块说明}
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 依赖 infra 模块 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-module-infra</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
#### 步骤 3:添加模块到父 pom.xml
|
||||
|
||||
在 `lyzsys_backend/pom.xml` 中添加:
|
||||
|
||||
```xml
|
||||
<modules>
|
||||
<!-- 已有模块 -->
|
||||
<module>lyzsys-module-system</module>
|
||||
<module>lyzsys-module-infra</module>
|
||||
<!-- 新增模块 -->
|
||||
<module>lyzsys-module-{模块名}</module>
|
||||
</modules>
|
||||
```
|
||||
|
||||
#### 步骤 4:添加模块到 server
|
||||
|
||||
在 `lyzsys-server/pom.xml` 中添加依赖:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-module-{模块名}</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 2.2 创建数据库表
|
||||
|
||||
参考 `sql/mysql/demo_project.sql`:
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS `{表名}`;
|
||||
CREATE TABLE `{表名}` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
-- 业务字段(注意:遵循驼峰命名规范,第一个单词不能只有一个字母)
|
||||
`business_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务名称',
|
||||
`business_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务编号',
|
||||
-- 标准字段(所有表必备)
|
||||
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '业务表';
|
||||
```
|
||||
|
||||
### 2.3 创建实体类(DO)
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/dal/dataobject/{业务}/{业务}DO.java`
|
||||
|
||||
参考 `ProjectDO.java`:
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务};
|
||||
|
||||
import cn.iocoder.lyzsys.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* {业务名称} DO
|
||||
*
|
||||
* @author {作者}
|
||||
*/
|
||||
@TableName("{表名}")
|
||||
@KeySequence("{表名}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class {业务}DO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 业务字段(注意驼峰命名)
|
||||
*/
|
||||
private String businessName;
|
||||
private String businessCode;
|
||||
private LocalDateTime businessDate;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 创建 Mapper 接口
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/dal/mysql/{业务}/{业务}Mapper.java`
|
||||
|
||||
参考 `ProjectMapper.java`:
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.dal.mysql.{业务};
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* {业务名称} Mapper
|
||||
*
|
||||
* @author {作者}
|
||||
*/
|
||||
@Mapper
|
||||
public interface {业务}Mapper extends BaseMapperX<{业务}DO> {
|
||||
|
||||
default PageResult<{业务}DO> selectPage({业务}PageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<{业务}DO>()
|
||||
.likeIfPresent({业务}DO::getBusinessName, reqVO.getBusinessName())
|
||||
.likeIfPresent({业务}DO::getBusinessCode, reqVO.getBusinessCode())
|
||||
.betweenIfPresent({业务}DO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc({业务}DO::getId));
|
||||
}
|
||||
|
||||
default {业务}DO selectByBusinessCode(String businessCode) {
|
||||
return selectOne({业务}DO::getBusinessCode, businessCode);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 创建 VO 类
|
||||
|
||||
#### 分页查询请求 VO
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/vo/{业务}PageReqVO.java`
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - {业务名称}分页列表 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class {业务}PageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "业务名称,模糊匹配", example = "示例")
|
||||
private String businessName;
|
||||
|
||||
@Schema(description = "业务编号,模糊匹配", example = "CODE-001")
|
||||
private String businessCode;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应 VO
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/vo/{业务}RespVO.java`
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - {业务名称}信息 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class {业务}RespVO {
|
||||
|
||||
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "业务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例")
|
||||
@ExcelProperty("业务名称")
|
||||
private String businessName;
|
||||
|
||||
@Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CODE-001")
|
||||
@ExcelProperty("业务编号")
|
||||
private String businessCode;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 保存请求 VO
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/vo/{业务}SaveReqVO.java`
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - {业务名称}创建/修改 Request VO")
|
||||
@Data
|
||||
public class {业务}SaveReqVO {
|
||||
|
||||
@Schema(description = "主键ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "业务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例")
|
||||
@NotBlank(message = "业务名称不能为空")
|
||||
@Size(max = 100, message = "业务名称长度不能超过100个字符")
|
||||
private String businessName;
|
||||
|
||||
@Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CODE-001")
|
||||
@NotBlank(message = "业务编号不能为空")
|
||||
@Size(max = 50, message = "业务编号长度不能超过50个字符")
|
||||
private String businessCode;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 创建 Service 层
|
||||
|
||||
#### Service 接口
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/service/{业务}/{业务}Service.java`
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.service.{业务};
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}SaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {业务名称} Service 接口
|
||||
*
|
||||
* @author {作者}
|
||||
*/
|
||||
public interface {业务}Service {
|
||||
|
||||
/**
|
||||
* 创建{业务}
|
||||
*/
|
||||
Long create{业务}({业务}SaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新{业务}
|
||||
*/
|
||||
void update{业务}({业务}SaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除{业务}
|
||||
*/
|
||||
void delete{业务}(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除{业务}
|
||||
*/
|
||||
void delete{业务}List(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得{业务}分页列表
|
||||
*/
|
||||
PageResult<{业务}DO> get{业务}Page({业务}PageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得{业务}详情
|
||||
*/
|
||||
{业务}DO get{业务}(Long id);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### Service 实现类
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/service/{业务}/{业务}ServiceImpl.java`
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.service.{业务};
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}SaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.dal.mysql.{业务}.{业务}Mapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.lyzsys.module.{模块名}.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* {业务名称} Service 实现类
|
||||
*
|
||||
* @author {作者}
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class {业务}ServiceImpl implements {业务}Service {
|
||||
|
||||
@Resource
|
||||
private {业务}Mapper {业务}Mapper;
|
||||
|
||||
@Override
|
||||
public Long create{业务}({业务}SaveReqVO createReqVO) {
|
||||
// 校验业务编号的唯一性
|
||||
validateBusinessCodeUnique(null, createReqVO.getBusinessCode());
|
||||
|
||||
// 插入
|
||||
{业务}DO {业务} = BeanUtils.toBean(createReqVO, {业务}DO.class);
|
||||
{业务}Mapper.insert({业务});
|
||||
return {业务}.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update{业务}({业务}SaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validate{业务}Exists(updateReqVO.getId());
|
||||
// 校验业务编号的唯一性
|
||||
validateBusinessCodeUnique(updateReqVO.getId(), updateReqVO.getBusinessCode());
|
||||
|
||||
// 更新
|
||||
{业务}DO updateObj = BeanUtils.toBean(updateReqVO, {业务}DO.class);
|
||||
{业务}Mapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete{业务}(Long id) {
|
||||
// 校验存在
|
||||
validate{业务}Exists(id);
|
||||
// 删除
|
||||
{业务}Mapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete{业务}List(List<Long> ids) {
|
||||
// 校验存在
|
||||
ids.forEach(this::validate{业务}Exists);
|
||||
// 批量删除
|
||||
{业务}Mapper.deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<{业务}DO> get{业务}Page({业务}PageReqVO pageReqVO) {
|
||||
return {业务}Mapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public {业务}DO get{业务}(Long id) {
|
||||
return {业务}Mapper.selectById(id);
|
||||
}
|
||||
|
||||
// ==================== 校验方法 ====================
|
||||
|
||||
private void validate{业务}Exists(Long id) {
|
||||
if ({业务}Mapper.selectById(id) == null) {
|
||||
throw exception({业务大写}_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBusinessCodeUnique(Long id, String businessCode) {
|
||||
{业务}DO {业务} = {业务}Mapper.selectByBusinessCode(businessCode);
|
||||
if ({业务} == null) {
|
||||
return;
|
||||
}
|
||||
if (id == null) {
|
||||
throw exception({业务大写}_CODE_DUPLICATE);
|
||||
}
|
||||
if (!{业务}.getId().equals(id)) {
|
||||
throw exception({业务大写}_CODE_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 2.7 创建 Controller 层
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/{业务}Controller.java`
|
||||
|
||||
参考 `ProjectController.java`:
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务};
|
||||
|
||||
import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}RespVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}SaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
|
||||
import cn.iocoder.lyzsys.module.{模块名}.service.{业务}.{业务}Service;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - {业务名称}管理")
|
||||
@RestController
|
||||
@RequestMapping("/{模块名}/{业务}")
|
||||
@Validated
|
||||
public class {业务}Controller {
|
||||
|
||||
@Resource
|
||||
private {业务}Service {业务}Service;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建{业务}")
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:create')")
|
||||
public CommonResult<Long> create{业务}(@Valid @RequestBody {业务}SaveReqVO createReqVO) {
|
||||
Long id = {业务}Service.create{业务}(createReqVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改{业务}")
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:update')")
|
||||
public CommonResult<Boolean> update{业务}(@Valid @RequestBody {业务}SaveReqVO updateReqVO) {
|
||||
{业务}Service.update{业务}(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除{业务}")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:delete')")
|
||||
public CommonResult<Boolean> delete{业务}(@RequestParam("id") Long id) {
|
||||
{业务}Service.delete{业务}(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Operation(summary = "批量删除{业务}")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:delete')")
|
||||
public CommonResult<Boolean> delete{业务}List(@RequestParam("ids") List<Long> ids) {
|
||||
{业务}Service.delete{业务}List(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得{业务}分页列表")
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:query')")
|
||||
public CommonResult<PageResult<{业务}RespVO>> get{业务}Page(@Valid {业务}PageReqVO pageReqVO) {
|
||||
PageResult<{业务}DO> pageResult = {业务}Service.get{业务}Page(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, {业务}RespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得{业务}详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:query')")
|
||||
public CommonResult<{业务}RespVO> get{业务}(@RequestParam("id") Long id) {
|
||||
{业务}DO {业务} = {业务}Service.get{业务}(id);
|
||||
return success(BeanUtils.toBean({业务}, {业务}RespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出{业务}")
|
||||
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void export{业务}(HttpServletResponse response, @Valid {业务}PageReqVO exportReqVO) throws IOException {
|
||||
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<{业务}DO> list = {业务}Service.get{业务}Page(exportReqVO).getList();
|
||||
ExcelUtils.write(response, "{业务名称}.xls", "数据", {业务}RespVO.class,
|
||||
BeanUtils.toBean(list, {业务}RespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 2.8 创建错误码常量
|
||||
|
||||
文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/enums/ErrorCodeConstants.java`
|
||||
|
||||
```java
|
||||
package cn.iocoder.lyzsys.module.{模块名}.enums;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* {模块名称} 错误码枚举类
|
||||
*
|
||||
* {模块名} 模块,使用 1-0XX-000-000 段
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== {业务名称}模块 1-0XX-001-000 ==========
|
||||
ErrorCode {业务大写}_NOT_EXISTS = new ErrorCode(1_0XX_001_000, "{业务}不存在");
|
||||
ErrorCode {业务大写}_CODE_DUPLICATE = new ErrorCode(1_0XX_001_001, "已经存在该{业务}编号");
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 创建前端页面
|
||||
|
||||
### 3.1 创建 API 接口
|
||||
|
||||
文件位置:`src/api/{模块名}/{业务}/index.ts`
|
||||
|
||||
参考 `src/api/demo/project/index.ts`:
|
||||
|
||||
```typescript
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface {业务}VO {
|
||||
id: number
|
||||
businessName: string
|
||||
businessCode: string
|
||||
createTime: Date
|
||||
}
|
||||
|
||||
export interface {业务}PageReqVO extends PageParam {
|
||||
businessName?: string
|
||||
businessCode?: string
|
||||
createTime?: Date[]
|
||||
}
|
||||
|
||||
// 查询分页
|
||||
export const get{业务}Page = (params: {业务}PageReqVO) => {
|
||||
return request.get({ url: '/{模块名}/{业务}/page', params })
|
||||
}
|
||||
|
||||
// 查询详情
|
||||
export const get{业务} = (id: number) => {
|
||||
return request.get({ url: '/{模块名}/{业务}/get', params: { id } })
|
||||
}
|
||||
|
||||
// 新增
|
||||
export const create{业务} = (data: {业务}VO) => {
|
||||
return request.post({ url: '/{模块名}/{业务}/create', data })
|
||||
}
|
||||
|
||||
// 修改
|
||||
export const update{业务} = (data: {业务}VO) => {
|
||||
return request.put({ url: '/{模块名}/{业务}/update', data })
|
||||
}
|
||||
|
||||
// 删除
|
||||
export const delete{业务} = (id: number) => {
|
||||
return request.delete({ url: '/{模块名}/{业务}/delete', params: { id } })
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
export const delete{业务}List = (ids: number[]) => {
|
||||
return request.delete({ url: '/{模块名}/{业务}/delete-list', params: { ids } })
|
||||
}
|
||||
|
||||
// 导出
|
||||
export const export{业务} = (params) => {
|
||||
return request.download({ url: '/{模块名}/{业务}/export-excel', params })
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 创建列表页面
|
||||
|
||||
文件位置:`src/views/{模块名}/{业务}/index.vue`
|
||||
|
||||
参考 `src/views/demo/project/index.vue`(详细代码见 demo 模块)
|
||||
|
||||
**关键点**:
|
||||
- 使用 `defineOptions({ name: '{模块}{业务}' })`
|
||||
- 搜索表单 + 列表表格 + 分页组件
|
||||
- 权限控制:`v-hasPermi="['{模块名}:{业务}:操作']"`
|
||||
|
||||
### 3.3 创建表单组件
|
||||
|
||||
文件位置:`src/views/{模块名}/{业务}/{业务}Form.vue`
|
||||
|
||||
参考 `src/views/demo/project/ProjectForm.vue`(详细代码见 demo 模块)
|
||||
|
||||
**关键点**:
|
||||
- 使用 `defineOptions({ name: '{模块}{业务}Form' })`
|
||||
- 使用 `defineExpose({ open })` 暴露打开方法
|
||||
- 表单验证规则
|
||||
- 提交成功后 `emit('success')`
|
||||
|
||||
---
|
||||
|
||||
## 4. 配置菜单权限
|
||||
|
||||
### 4.1 创建菜单 SQL
|
||||
|
||||
文件位置:`sql/mysql/{模块名}_menu.sql`
|
||||
|
||||
参考 `sql/mysql/demo_menu.sql`:
|
||||
|
||||
```sql
|
||||
-- 一级菜单
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
{ID}, '{一级菜单名}', '', 1, {排序}, 0,
|
||||
'/{模块名}', '{图标}', NULL, NULL,
|
||||
0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
|
||||
-- 二级菜单
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
{ID+1}, '{二级菜单名}', '', 2, 1, {一级菜单ID},
|
||||
'{业务}', '{图标}', '{模块名}/{业务}/index', '{模块}{业务}',
|
||||
0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
|
||||
-- 按钮权限:查询、新增、修改、删除、导出
|
||||
INSERT INTO `system_menu` (...) VALUES
|
||||
({ID+2}, '{业务}查询', '{模块名}:{业务}:query', 3, 1, {二级菜单ID}, ...), ({ID+3}, '{业务}新增', '{模块名}:{业务}:create', 3, 2, {二级菜单ID}, ...),
|
||||
({ID+4}, '{业务}修改', '{模块名}:{业务}:update', 3, 3, {二级菜单ID}, ...),
|
||||
({ID+5}, '{业务}删除', '{模块名}:{业务}:delete', 3, 4, {二级菜单ID}, ...),
|
||||
({ID+6}, '{业务}导出', '{模块名}:{业务}:export', 3, 5, {二级菜单ID}, ...);
|
||||
|
||||
-- 调整系统管理和基础设施菜单排序
|
||||
UPDATE `system_menu` SET `sort` = 98, `updater` = '1', `update_time` = NOW() WHERE `id` = 1 AND `deleted` = b'0';
|
||||
UPDATE `system_menu` SET `sort` = 99, `updater` = '1', `update_time` = NOW() WHERE `id` = 2 AND `deleted` = b'0';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 完整示例
|
||||
|
||||
### 5.1 Demo 模块文件清单
|
||||
|
||||
以下是 `lyzsys-module-demo` 的完整文件结构,可作为新模块开发的参考:
|
||||
|
||||
```
|
||||
lyzsys-module-demo/
|
||||
├── pom.xml
|
||||
└── src/main/java/cn/iocoder/lyzsys/module/demo/
|
||||
├── controller/admin/project/
|
||||
│ ├── ProjectController.java
|
||||
│ └── vo/
|
||||
│ ├── ProjectPageReqVO.java
|
||||
│ ├── ProjectRespVO.java
|
||||
│ └── ProjectSaveReqVO.java
|
||||
├── service/project/
|
||||
│ ├── ProjectService.java
|
||||
│ └── ProjectServiceImpl.java
|
||||
├── dal/
|
||||
│ ├── dataobject/project/
|
||||
│ │ └── ProjectDO.java
|
||||
│ └── mysql/project/
|
||||
│ └── ProjectMapper.java
|
||||
└── enums/
|
||||
└── ErrorCodeConstants.java
|
||||
```
|
||||
|
||||
### 5.2 前端文件清单
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/demo/project/
|
||||
│ └── index.ts
|
||||
└── views/demo/project/
|
||||
├── index.vue
|
||||
└── ProjectForm.vue
|
||||
```
|
||||
|
||||
### 5.3 SQL 文件清单
|
||||
|
||||
```
|
||||
sql/mysql/
|
||||
├── demo_project.sql # 数据库表
|
||||
└── demo_menu.sql # 菜单配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见问题
|
||||
|
||||
### 6.1 Maven 编译错误
|
||||
|
||||
**问题**:找不到父 POM
|
||||
**解决**:检查 parent 的 groupId、artifactId、version 是否正确
|
||||
|
||||
### 6.2 菜单不显示
|
||||
|
||||
**问题**:前端菜单不显示
|
||||
**解决**:
|
||||
1. 确认已执行菜单 SQL
|
||||
2. 检查当前用户角色权限
|
||||
3. 清除浏览器缓存
|
||||
|
||||
### 6.3 接口 404
|
||||
|
||||
**问题**:前端调用接口返回 404
|
||||
**解决**:
|
||||
1. 检查 Controller 的 @RequestMapping 路径
|
||||
2. 确认模块已添加到 lyzsys-server/pom.xml
|
||||
3. 重启后端服务
|
||||
|
||||
### 6.4 MyBatis Plus 字段映射错误
|
||||
|
||||
**问题**:字段映射错误,查询结果为 null
|
||||
**解决**:检查字段命名是否遵循驼峰规范,确保第一个单词不是单字母
|
||||
|
||||
### 6.5 权限验证失败
|
||||
|
||||
**问题**:操作提示无权限
|
||||
**解决**:
|
||||
1. 检查 @PreAuthorize 权限标识是否正确
|
||||
2. 确认菜单 SQL 中的权限标识与代码一致
|
||||
3. 给当前角色分配权限
|
||||
|
||||
---
|
||||
|
||||
## 7. 检查清单
|
||||
|
||||
开发完成后,请检查:
|
||||
|
||||
**后端**
|
||||
- [ ] Maven 模块配置正确
|
||||
- [ ] 数据库表创建成功
|
||||
- [ ] 实体类字段遵循命名规范
|
||||
- [ ] Mapper 接口方法正确
|
||||
- [ ] VO 类字段完整
|
||||
- [ ] Service 业务逻辑正确
|
||||
- [ ] Controller 权限配置正确
|
||||
- [ ] 错误码定义完整
|
||||
|
||||
**前端**
|
||||
- [ ] API 接口定义正确
|
||||
- [ ] 列表页面功能完整
|
||||
- [ ] 表单组件验证规则正确
|
||||
- [ ] 权限控制配置正确
|
||||
|
||||
**菜单**
|
||||
- [ ] 一级菜单创建成功
|
||||
- [ ] 二级菜单创建成功
|
||||
- [ ] 按钮权限创建成功
|
||||
- [ ] 菜单排序正确
|
||||
|
||||
**测试**
|
||||
- [ ] 列表查询正常
|
||||
- [ ] 新增功能正常
|
||||
- [ ] 修改功能正常
|
||||
- [ ] 删除功能正常
|
||||
- [ ] 导出功能正常
|
||||
- [ ] 权限控制正常
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**参考模块**:lyzsys-module-demo
|
||||
**最后更新**:2024年
|
||||
**维护者**:开发团队
|
||||
647
doc/编码规范.md
Normal file
647
doc/编码规范.md
Normal file
@@ -0,0 +1,647 @@
|
||||
# Lyzsys 项目编码规范
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 命名规范](#1-命名规范)
|
||||
- [2. 菜单结构规范](#2-菜单结构规范)
|
||||
- [3. 代码结构规范](#3-代码结构规范)
|
||||
- [4. 数据库设计规范](#4-数据库设计规范)
|
||||
- [5. API 接口规范](#5-api-接口规范)
|
||||
- [6. 前端开发规范](#6-前端开发规范)
|
||||
|
||||
---
|
||||
|
||||
## 1. 命名规范
|
||||
|
||||
### 1.1 驼峰命名规范
|
||||
|
||||
**核心原则**:所有字段名、类名、文件名、变量名的驼峰命名中,**第一个单词不能只有一个字母**。
|
||||
|
||||
#### 为什么要遵循这个规范?
|
||||
|
||||
1. **MyBatis Plus 兼容性**:MyBatis Plus 在映射单字母开头的驼峰字段时可能出现问题
|
||||
2. **代码可读性**:完整单词更具可读性,便于理解业务含义
|
||||
3. **避免错误**:减少字段读取错误和调试困难
|
||||
|
||||
#### 正确示例 ✅
|
||||
|
||||
```java
|
||||
// 字段命名
|
||||
private String projectId; // 而不是 pId
|
||||
private String projectName; // 而不是 pName
|
||||
private String projectCode; // 而不是 pCode
|
||||
private LocalDateTime establishDate; // 而不是 eDate
|
||||
|
||||
// 特殊情况:如果首字母缩写必须使用,请用完整单词
|
||||
private String majorType; // 而不是 mType
|
||||
private BigDecimal leaderRatio; // 而不是 lRatio
|
||||
private Double kOneCoefficient; // 而不是 k1Coefficient(k1 是业务术语)
|
||||
```
|
||||
|
||||
#### 错误示例 ❌
|
||||
|
||||
```java
|
||||
// 错误:单字母开头
|
||||
private String pId; // ❌ 应该是 projectId
|
||||
private String mType; // ❌ 应该是 majorType
|
||||
private String lRatio; // ❌ 应该是 leaderRatio
|
||||
private String k1Coefficient; // ❌ 应该是 kOneCoefficient
|
||||
```
|
||||
|
||||
### 1.2 Java 类命名规范
|
||||
|
||||
#### 实体类(DO)
|
||||
|
||||
```java
|
||||
// 格式:{业务名称}DO
|
||||
ProjectDO.java // 项目实体
|
||||
UserDO.java // 用户实体
|
||||
OrderDO.java // 订单实体
|
||||
```
|
||||
|
||||
#### VO 类
|
||||
|
||||
```java
|
||||
// 分页查询请求
|
||||
{业务名称}PageReqVO.java
|
||||
ProjectPageReqVO.java
|
||||
|
||||
// 响应 VO
|
||||
{业务名称}RespVO.java
|
||||
ProjectRespVO.java
|
||||
|
||||
// 保存请求 VO(新增和修改共用)
|
||||
{业务名称}SaveReqVO.java
|
||||
ProjectSaveReqVO.java
|
||||
|
||||
// 简单响应 VO(下拉选项等)
|
||||
{业务名称}SimpleRespVO.java
|
||||
ProjectSimpleRespVO.java
|
||||
```
|
||||
|
||||
#### Mapper 接口
|
||||
|
||||
```java
|
||||
// 格式:{业务名称}Mapper
|
||||
ProjectMapper.java
|
||||
```
|
||||
|
||||
#### Service 层
|
||||
|
||||
```java
|
||||
// 接口:{业务名称}Service
|
||||
ProjectService.java
|
||||
|
||||
// 实现:{业务名称}ServiceImpl
|
||||
ProjectServiceImpl.java
|
||||
```
|
||||
|
||||
#### Controller 层
|
||||
|
||||
```java
|
||||
// 格式:{业务名称}Controller
|
||||
ProjectController.java
|
||||
```
|
||||
|
||||
### 1.3 数据库命名规范
|
||||
|
||||
#### 表名
|
||||
|
||||
```sql
|
||||
-- 格式:{模块前缀}_{业务名称}(全小写,下划线分隔)
|
||||
demo_project -- demo 模块的项目表
|
||||
system_user -- system 模块的用户表
|
||||
infra_config -- infra 模块的配置表
|
||||
```
|
||||
|
||||
#### 字段名
|
||||
|
||||
```sql
|
||||
-- 使用下划线分隔的小写单词
|
||||
project_name -- 项目名称(对应 Java 中的 projectName)
|
||||
project_code -- 项目编号
|
||||
establish_date -- 立项时间
|
||||
|
||||
-- 标准字段(所有表必备)
|
||||
id -- 主键
|
||||
creator -- 创建者
|
||||
create_time -- 创建时间
|
||||
updater -- 更新者
|
||||
update_time -- 更新时间
|
||||
deleted -- 删除标记
|
||||
tenant_id -- 租户ID(多租户表必备)
|
||||
```
|
||||
|
||||
### 1.4 包名规范
|
||||
|
||||
```
|
||||
cn.iocoder.lyzsys.module.{模块名}.{分层}
|
||||
|
||||
示例:
|
||||
cn.iocoder.lyzsys.module.demo.controller.admin.project
|
||||
cn.iocoder.lyzsys.module.demo.service.project
|
||||
cn.iocoder.lyzsys.module.demo.dal.dataobject.project
|
||||
cn.iocoder.lyzsys.module.demo.dal.mysql.project
|
||||
```
|
||||
|
||||
### 1.5 前端命名规范
|
||||
|
||||
#### Vue 组件名
|
||||
|
||||
```javascript
|
||||
// 页面组件(defineOptions name)
|
||||
DemoProject // 项目列表页
|
||||
DemoProjectForm // 项目表单组件
|
||||
|
||||
// 文件名(kebab-case)
|
||||
index.vue // 列表页
|
||||
ProjectForm.vue // 表单组件
|
||||
```
|
||||
|
||||
#### API 文件
|
||||
|
||||
```typescript
|
||||
// 文件路径
|
||||
src/api/demo/project/index.ts
|
||||
|
||||
// 接口命名(驼峰)
|
||||
export const getProjectPage = () => {}
|
||||
export const createProject = () => {}
|
||||
export const updateProject = () => {}
|
||||
export const deleteProject = () => {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 菜单结构规范
|
||||
|
||||
### 2.1 菜单层级规范
|
||||
|
||||
**原则**:业务功能菜单优先展示,系统管理功能放在最后。
|
||||
|
||||
#### 推荐的菜单顺序
|
||||
|
||||
```
|
||||
1. 业务菜单(sort: 1-90)
|
||||
├── 项目管理(一级菜单,sort: 1)
|
||||
│ └── 项目列表(二级菜单,sort: 1)
|
||||
├── 客户管理(一级菜单,sort: 2)
|
||||
│ └── 客户列表(二级菜单,sort: 1)
|
||||
└── ...
|
||||
|
||||
98. 系统管理(sort: 98)
|
||||
├── 用户管理
|
||||
├── 角色管理
|
||||
├── 菜单管理
|
||||
└── ...
|
||||
|
||||
99. 基础设施(sort: 99)
|
||||
├── 代码生成
|
||||
├── 文件管理
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 2.2 菜单配置规范
|
||||
|
||||
#### 一级菜单(目录)
|
||||
|
||||
```sql
|
||||
INSERT INTO `system_menu` (
|
||||
`id`, `name`, `permission`, `type`, `sort`, `parent_id`,
|
||||
`path`, `icon`, `component`, `component_name`,
|
||||
`status`, `visible`, `keep_alive`, `always_show`,
|
||||
`creator`, `create_time`, `updater`, `update_time`, `deleted`
|
||||
) VALUES (
|
||||
3000, -- ID(建议按模块分配段,如 demo: 3000-3999)
|
||||
'项目管理', -- 名称
|
||||
'', -- 权限标识(一级菜单为空)
|
||||
1, -- 类型:1=目录
|
||||
1, -- 排序(业务菜单建议 1-90)
|
||||
0, -- 父级ID(0表示顶级)
|
||||
'/demo', -- 路由路径
|
||||
'ep:files', -- 图标
|
||||
NULL, -- 组件路径(目录为NULL)
|
||||
NULL, -- 组件名称(目录为NULL)
|
||||
0, -- 状态:0=正常
|
||||
b'1', -- 是否可见
|
||||
b'1', -- 是否缓存
|
||||
b'1', -- 是否总是显示
|
||||
'1', -- 创建者
|
||||
NOW(), -- 创建时间
|
||||
'1', -- 更新者
|
||||
NOW(), -- 更新时间
|
||||
b'0' -- 是否删除
|
||||
);
|
||||
```
|
||||
|
||||
#### 二级菜单(菜单页面)
|
||||
|
||||
```sql
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
3001, -- ID
|
||||
'项目列表', -- 名称
|
||||
'', -- 权限标识(菜单页面为空)
|
||||
2, -- 类型:2=菜单
|
||||
1, -- 排序
|
||||
3000, -- 父级ID(对应一级菜单ID)
|
||||
'project', -- 路由路径
|
||||
'ep:document', -- 图标
|
||||
'demo/project/index', -- 组件路径
|
||||
'DemoProject', -- 组件名称(对应 Vue defineOptions name)
|
||||
0, b'1', b'1', b'1',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
```
|
||||
|
||||
#### 三级菜单(按钮权限)
|
||||
|
||||
```sql
|
||||
-- 查询权限
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
3002, '项目查询', 'demo:project:query', 3, 1, 3001,
|
||||
'', '', '', NULL, 0, b'1', b'1', b'1',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
|
||||
-- 新增权限
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
3003, '项目新增', 'demo:project:create', 3, 2, 3001,
|
||||
'', '', '', NULL, 0, b'1', b'1', b'1',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
|
||||
-- 修改权限
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
3004, '项目修改', 'demo:project:update', 3, 3, 3001,
|
||||
'', '', '', NULL, 0, b'1', b'1', b'1',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
|
||||
-- 删除权限
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
3005, '项目删除', 'demo:project:delete', 3, 4, 3001,
|
||||
'', '', '', NULL, 0, b'1', b'1', b'1',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
|
||||
-- 导出权限
|
||||
INSERT INTO `system_menu` (...) VALUES (
|
||||
3006, '项目导出', 'demo:project:export', 3, 5, 3001,
|
||||
'', '', '', NULL, 0, b'1', b'1', b'1',
|
||||
'1', NOW(), '1', NOW(), b'0'
|
||||
);
|
||||
```
|
||||
|
||||
### 2.3 权限标识规范
|
||||
|
||||
```
|
||||
格式:{模块名}:{业务}:{操作}
|
||||
|
||||
示例:
|
||||
demo:project:query -- demo模块,项目业务,查询操作
|
||||
demo:project:create -- demo模块,项目业务,新增操作
|
||||
demo:project:update -- demo模块,项目业务,修改操作
|
||||
demo:project:delete -- demo模块,项目业务,删除操作
|
||||
demo:project:export -- demo模块,项目业务,导出操作
|
||||
```
|
||||
|
||||
### 2.4 菜单 ID 分配规范
|
||||
|
||||
为避免 ID 冲突,建议按模块分配 ID 段:
|
||||
|
||||
| 模块 | ID 范围 | 说明 |
|
||||
|-----|---------|------|
|
||||
| system | 1-999 | 系统核心模块 |
|
||||
| infra | 1000-1999 | 基础设施模块 |
|
||||
| member | 2000-2999 | 会员模块 |
|
||||
| demo | 3000-3999 | Demo 示例模块 |
|
||||
| bpm | 4000-4999 | 工作流模块 |
|
||||
| mall | 5000-5999 | 商城模块 |
|
||||
| ... | ... | 其他业务模块 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码结构规范
|
||||
|
||||
### 3.1 模块目录结构
|
||||
|
||||
```
|
||||
lyzsys-module-{模块名}/
|
||||
├── pom.xml # Maven 配置
|
||||
└── src/main/java/cn/iocoder/lyzsys/module/{模块名}/
|
||||
├── controller/ # 控制器层
|
||||
│ └── admin/ # 管理后台接口
|
||||
│ └── {业务}/ # 业务模块
|
||||
│ ├── {业务}Controller.java # 控制器
|
||||
│ └── vo/ # VO 对象
|
||||
│ ├── {业务}PageReqVO.java # 分页请求
|
||||
│ ├── {业务}RespVO.java # 响应 VO
|
||||
│ └── {业务}SaveReqVO.java # 保存请求
|
||||
├── service/ # 服务层
|
||||
│ └── {业务}/
|
||||
│ ├── {业务}Service.java # 服务接口
|
||||
│ └── {业务}ServiceImpl.java # 服务实现
|
||||
├── dal/ # 数据访问层
|
||||
│ ├── dataobject/ # 数据对象
|
||||
│ │ └── {业务}/
|
||||
│ │ └── {业务}DO.java # 实体类
|
||||
│ └── mysql/ # Mapper 接口
|
||||
│ └── {业务}/
|
||||
│ └── {业务}Mapper.java # Mapper
|
||||
├── convert/ # 对象转换(可选)
|
||||
│ └── {业务}Convert.java
|
||||
├── enums/ # 枚举类
|
||||
│ └── ErrorCodeConstants.java # 错误码
|
||||
└── api/ # 对外 API(可选)
|
||||
└── {业务}Api.java
|
||||
```
|
||||
|
||||
### 3.2 分层职责
|
||||
|
||||
#### Controller 层
|
||||
|
||||
- 接收 HTTP 请求
|
||||
- 参数校验(使用 @Valid)
|
||||
- 权限控制(使用 @PreAuthorize)
|
||||
- 调用 Service 层
|
||||
- 返回统一响应
|
||||
|
||||
#### Service 层
|
||||
|
||||
- 业务逻辑处理
|
||||
- 数据校验
|
||||
- 事务控制
|
||||
- 调用 Mapper 层
|
||||
|
||||
#### DAL 层
|
||||
|
||||
- 数据访问
|
||||
- SQL 操作
|
||||
- 不包含业务逻辑
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据库设计规范
|
||||
|
||||
### 4.1 表设计规范
|
||||
|
||||
#### 标准字段(所有表必备)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `{表名}` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
-- 业务字段
|
||||
...
|
||||
-- 标准字段
|
||||
`creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='表注释';
|
||||
```
|
||||
|
||||
#### 多租户表额外字段
|
||||
|
||||
```sql
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
```
|
||||
|
||||
### 4.2 字段类型规范
|
||||
|
||||
| Java 类型 | MySQL 类型 | 说明 |
|
||||
|-----------|-----------|------|
|
||||
| Long | bigint | ID、数量等 |
|
||||
| String | varchar | 字符串 |
|
||||
| Integer | int | 状态、类型等 |
|
||||
| LocalDateTime | datetime | 日期时间 |
|
||||
| LocalDate | date | 日期 |
|
||||
| BigDecimal | decimal | 金额、精确数值 |
|
||||
| Boolean | bit(1) | 布尔值 |
|
||||
|
||||
### 4.3 索引规范
|
||||
|
||||
```sql
|
||||
-- 主键索引
|
||||
PRIMARY KEY (`id`)
|
||||
|
||||
-- 唯一索引(业务唯一字段)
|
||||
UNIQUE KEY `uk_project_code` (`project_code`)
|
||||
|
||||
-- 普通索引(常用查询字段)
|
||||
KEY `idx_project_name` (`project_name`)
|
||||
|
||||
-- 组合索引(多字段联合查询)
|
||||
KEY `idx_tenant_status` (`tenant_id`, `status`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 接口规范
|
||||
|
||||
### 5.1 RESTful 风格
|
||||
|
||||
| 操作 | HTTP 方法 | URL | 说明 |
|
||||
|-----|----------|-----|------|
|
||||
| 查询列表 | GET | /{模块}/{业务}/page | 分页查询 |
|
||||
| 查询详情 | GET | /{模块}/{业务}/get | 根据ID查询 |
|
||||
| 新增 | POST | /{模块}/{业务}/create | 创建 |
|
||||
| 修改 | PUT | /{模块}/{业务}/update | 更新 |
|
||||
| 删除 | DELETE | /{模块}/{业务}/delete | 删除 |
|
||||
| 批量删除 | DELETE | /{模块}/{业务}/delete-list | 批量删除 |
|
||||
| 导出 | GET | /{模块}/{业务}/export-excel | 导出Excel |
|
||||
|
||||
### 5.2 统一响应格式
|
||||
|
||||
```java
|
||||
// 成功响应
|
||||
{
|
||||
"code": 0,
|
||||
"data": {...},
|
||||
"msg": ""
|
||||
}
|
||||
|
||||
// 失败响应
|
||||
{
|
||||
"code": 1001,
|
||||
"data": null,
|
||||
"msg": "错误信息"
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [...],
|
||||
"total": 100
|
||||
},
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 接口注解规范
|
||||
|
||||
```java
|
||||
@Tag(name = "管理后台 - 项目管理")
|
||||
@RestController
|
||||
@RequestMapping("/demo/project")
|
||||
@Validated
|
||||
public class ProjectController {
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建项目")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:create')")
|
||||
public CommonResult<Long> createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 前端开发规范
|
||||
|
||||
### 6.1 目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ # API 接口
|
||||
│ └── {模块}/
|
||||
│ └── {业务}/
|
||||
│ └── index.ts # API 定义
|
||||
└── views/ # 页面
|
||||
└── {模块}/
|
||||
└── {业务}/
|
||||
├── index.vue # 列表页
|
||||
└── {业务}Form.vue # 表单组件
|
||||
```
|
||||
|
||||
### 6.2 组件规范
|
||||
|
||||
#### 列表页面
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form>...</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table>...</el-table>
|
||||
<Pagination />
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗 -->
|
||||
<{业务}Form ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: '{模块}{业务}' })
|
||||
// ...
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 表单组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form>...</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: '{模块}{业务}Form' })
|
||||
defineExpose({ open })
|
||||
// ...
|
||||
</script>
|
||||
```
|
||||
|
||||
### 6.3 权限控制
|
||||
|
||||
```vue
|
||||
<!-- 按钮权限 -->
|
||||
<el-button
|
||||
v-hasPermi="['demo:project:create']"
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
|
||||
<!-- 路由权限在菜单配置中控制 -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 最佳实践
|
||||
|
||||
### 7.1 代码注释
|
||||
|
||||
```java
|
||||
/**
|
||||
* 项目管理 Service 接口
|
||||
*
|
||||
* @author lyzsys
|
||||
*/
|
||||
public interface ProjectService {
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
*
|
||||
* @param createReqVO 项目信息
|
||||
* @return 项目编号
|
||||
*/
|
||||
Long createProject(ProjectSaveReqVO createReqVO);
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 异常处理
|
||||
|
||||
```java
|
||||
// 使用统一的业务异常
|
||||
throw exception(PROJECT_NOT_EXISTS);
|
||||
throw exception(PROJECT_CODE_DUPLICATE);
|
||||
```
|
||||
|
||||
### 7.3 日志规范
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
public class ProjectServiceImpl implements ProjectService {
|
||||
|
||||
@Override
|
||||
public Long createProject(ProjectSaveReqVO createReqVO) {
|
||||
log.info("[createProject] 创建项目,项目编号:{}", createReqVO.getProjectCode());
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 检查清单
|
||||
|
||||
在提交代码前,请检查:
|
||||
|
||||
- [ ] 所有字段命名遵循驼峰规范,无单字母开头
|
||||
- [ ] 菜单配置完整(一级、二级、按钮权限)
|
||||
- [ ] 业务菜单排序在系统管理之前
|
||||
- [ ] 权限标识格式正确({模块}:{业务}:{操作})
|
||||
- [ ] 数据库表包含标准字段
|
||||
- [ ] API 接口遵循 RESTful 风格
|
||||
- [ ] 代码注释完整
|
||||
- [ ] 前端权限控制正确
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**最后更新**:2024年
|
||||
**维护者**:开发团队
|
||||
@@ -35,7 +35,6 @@
|
||||
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
||||
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
|
||||
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
|
||||
<sqlserver.jdbc.version>12.8.1.jre8</sqlserver.jdbc.version>
|
||||
<taos.version>3.7.9</taos.version>
|
||||
<!-- 消息队列 -->
|
||||
<rocketmq-spring.version>2.3.5</rocketmq-spring.version>
|
||||
@@ -297,11 +296,6 @@
|
||||
<version>${kingbase.jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>${sqlserver.jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
|
||||
@@ -112,6 +112,7 @@ public class LyzsysWebAutoConfiguration {
|
||||
config.addAllowedOriginPattern("*"); // 设置访问源地址
|
||||
config.addAllowedHeader("*"); // 设置访问源请求头
|
||||
config.addAllowedMethod("*"); // 设置访问源请求方法
|
||||
config.addExposedHeader("Content-Disposition"); // 允许前端读取下载文件名
|
||||
// 创建 UrlBasedCorsConfigurationSource 对象
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config); // 对接口配置跨域设置
|
||||
|
||||
70
lyzsys-module-demo/pom.xml
Normal file
70
lyzsys-module-demo/pom.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>lyzsys-module-demo</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
demo 模块,演示业务功能。
|
||||
例如:项目管理等
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-module-infra</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,102 @@
|
||||
package cn.iocoder.lyzsys.module.demo.controller.admin.project;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectRespVO;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
|
||||
import cn.iocoder.lyzsys.module.demo.service.project.ProjectService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 项目管理")
|
||||
@RestController
|
||||
@RequestMapping("/demo/project")
|
||||
@Validated
|
||||
public class ProjectController {
|
||||
|
||||
@Resource
|
||||
private ProjectService projectService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建项目")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:create')")
|
||||
public CommonResult<Long> createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
|
||||
Long projectId = projectService.createProject(createReqVO);
|
||||
return success(projectId);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改项目")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:update')")
|
||||
public CommonResult<Boolean> updateProject(@Valid @RequestBody ProjectSaveReqVO updateReqVO) {
|
||||
projectService.updateProject(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除项目")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:delete')")
|
||||
public CommonResult<Boolean> deleteProject(@RequestParam("id") Long id) {
|
||||
projectService.deleteProject(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Operation(summary = "批量删除项目")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:delete')")
|
||||
public CommonResult<Boolean> deleteProjectList(@RequestParam("ids") List<Long> ids) {
|
||||
projectService.deleteProjectList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得项目分页列表")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:query')")
|
||||
public CommonResult<PageResult<ProjectRespVO>> getProjectPage(@Valid ProjectPageReqVO pageReqVO) {
|
||||
PageResult<ProjectDO> pageResult = projectService.getProjectPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, ProjectRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得项目详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:query')")
|
||||
public CommonResult<ProjectRespVO> getProject(@RequestParam("id") Long id) {
|
||||
ProjectDO project = projectService.getProject(id);
|
||||
return success(BeanUtils.toBean(project, ProjectRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出项目")
|
||||
@PreAuthorize("@ss.hasPermission('demo:project:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportProject(HttpServletResponse response, @Valid ProjectPageReqVO exportReqVO) throws IOException {
|
||||
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<ProjectDO> list = projectService.getProjectPage(exportReqVO).getList();
|
||||
// 导出
|
||||
ExcelUtils.write(response, "项目管理.xls", "数据", ProjectRespVO.class,
|
||||
BeanUtils.toBean(list, ProjectRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.lyzsys.module.demo.controller.admin.project.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 项目管理分页列表 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "项目名称,模糊匹配", example = "示例项目")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目编号,模糊匹配", example = "PROJ-2024-001")
|
||||
private String projectCode;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Schema(description = "立项时间")
|
||||
private LocalDateTime[] establishDate;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.lyzsys.module.demo.controller.admin.project.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 项目管理信息 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class ProjectRespVO {
|
||||
|
||||
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("项目ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例项目")
|
||||
@ExcelProperty("项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "PROJ-2024-001")
|
||||
@ExcelProperty("项目编号")
|
||||
private String projectCode;
|
||||
|
||||
@Schema(description = "立项时间", example = "2024-01-01 00:00:00")
|
||||
@ExcelProperty("立项时间")
|
||||
private LocalDateTime establishDate;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.lyzsys.module.demo.controller.admin.project.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 项目管理创建/修改 Request VO")
|
||||
@Data
|
||||
public class ProjectSaveReqVO {
|
||||
|
||||
@Schema(description = "项目ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例项目")
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Size(max = 100, message = "项目名称长度不能超过100个字符")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "PROJ-2024-001")
|
||||
@NotBlank(message = "项目编号不能为空")
|
||||
@Size(max = 50, message = "项目编号长度不能超过50个字符")
|
||||
private String projectCode;
|
||||
|
||||
@Schema(description = "立项时间", example = "2024-01-01 00:00:00")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime establishDate;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.lyzsys.module.demo.dal.dataobject.project;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 项目管理 DO
|
||||
*
|
||||
* @author lyzsys
|
||||
*/
|
||||
@TableName("demo_project")
|
||||
@KeySequence("demo_project_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 项目编号
|
||||
*/
|
||||
private String projectCode;
|
||||
|
||||
/**
|
||||
* 立项时间
|
||||
*/
|
||||
private LocalDateTime establishDate;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.lyzsys.module.demo.dal.mysql.project;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目管理 Mapper
|
||||
*
|
||||
* @author lyzsys
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectMapper extends BaseMapperX<ProjectDO> {
|
||||
|
||||
default PageResult<ProjectDO> selectPage(ProjectPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ProjectDO>()
|
||||
.likeIfPresent(ProjectDO::getProjectName, reqVO.getProjectName())
|
||||
.likeIfPresent(ProjectDO::getProjectCode, reqVO.getProjectCode())
|
||||
.betweenIfPresent(ProjectDO::getEstablishDate, reqVO.getEstablishDate())
|
||||
.orderByDesc(ProjectDO::getId));
|
||||
}
|
||||
|
||||
default ProjectDO selectByProjectCode(String projectCode) {
|
||||
return selectOne(ProjectDO::getProjectCode, projectCode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.lyzsys.module.demo.enums;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* Demo 错误码枚举类
|
||||
*
|
||||
* demo 模块,使用 1-010-000-000 段
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 项目管理模块 1-010-001-000 ==========
|
||||
ErrorCode PROJECT_NOT_EXISTS = new ErrorCode(1_010_001_000, "项目不存在");
|
||||
ErrorCode PROJECT_CODE_DUPLICATE = new ErrorCode(1_010_001_001, "已经存在该项目编号");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.iocoder.lyzsys.module.demo.service.project;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目管理 Service 接口
|
||||
*
|
||||
* @author lyzsys
|
||||
*/
|
||||
public interface ProjectService {
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
*
|
||||
* @param createReqVO 项目信息
|
||||
* @return 项目编号
|
||||
*/
|
||||
Long createProject(ProjectSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
*
|
||||
* @param updateReqVO 项目信息
|
||||
*/
|
||||
void updateProject(ProjectSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*
|
||||
* @param id 项目编号
|
||||
*/
|
||||
void deleteProject(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除项目
|
||||
*
|
||||
* @param ids 项目编号列表
|
||||
*/
|
||||
void deleteProjectList(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得项目分页列表
|
||||
*
|
||||
* @param pageReqVO 分页请求
|
||||
* @return 项目分页列表
|
||||
*/
|
||||
PageResult<ProjectDO> getProjectPage(ProjectPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得项目详情
|
||||
*
|
||||
* @param id 项目编号
|
||||
* @return 项目详情
|
||||
*/
|
||||
ProjectDO getProject(Long id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package cn.iocoder.lyzsys.module.demo.service.project;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
|
||||
import cn.iocoder.lyzsys.module.demo.dal.mysql.project.ProjectMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.lyzsys.module.demo.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 项目管理 Service 实现类
|
||||
*
|
||||
* @author lyzsys
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class ProjectServiceImpl implements ProjectService {
|
||||
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
|
||||
@Override
|
||||
public Long createProject(ProjectSaveReqVO createReqVO) {
|
||||
// 校验项目编号的唯一性
|
||||
validateProjectCodeUnique(null, createReqVO.getProjectCode());
|
||||
|
||||
// 插入项目
|
||||
ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class);
|
||||
projectMapper.insert(project);
|
||||
return project.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProject(ProjectSaveReqVO updateReqVO) {
|
||||
// 校验项目存在
|
||||
validateProjectExists(updateReqVO.getId());
|
||||
// 校验项目编号的唯一性
|
||||
validateProjectCodeUnique(updateReqVO.getId(), updateReqVO.getProjectCode());
|
||||
|
||||
// 更新项目
|
||||
ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class);
|
||||
projectMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProject(Long id) {
|
||||
// 校验项目存在
|
||||
validateProjectExists(id);
|
||||
// 删除项目
|
||||
projectMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteProjectList(List<Long> ids) {
|
||||
// 校验项目存在
|
||||
ids.forEach(this::validateProjectExists);
|
||||
// 批量删除
|
||||
projectMapper.deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ProjectDO> getProjectPage(ProjectPageReqVO pageReqVO) {
|
||||
return projectMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectDO getProject(Long id) {
|
||||
return projectMapper.selectById(id);
|
||||
}
|
||||
|
||||
// ==================== 校验方法 ====================
|
||||
|
||||
private void validateProjectExists(Long id) {
|
||||
if (projectMapper.selectById(id) == null) {
|
||||
throw exception(PROJECT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProjectCodeUnique(Long id, String projectCode) {
|
||||
ProjectDO project = projectMapper.selectByProjectCode(projectCode);
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
// 如果 id 为空,说明是新增,项目编号已存在则报错
|
||||
if (id == null) {
|
||||
throw exception(PROJECT_CODE_DUPLICATE);
|
||||
}
|
||||
// 如果有 id,说明是修改,判断是否是自己
|
||||
if (!project.getId().equals(id)) {
|
||||
throw exception(PROJECT_CODE_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public interface TenantMapper extends BaseMapperX<TenantDO> {
|
||||
|
||||
default List<TenantDO> selectListByWebsite(String website) {
|
||||
return selectList(new LambdaQueryWrapperX<TenantDO>()
|
||||
.apply(MyBatisUtils.findInSet("websites", website)));
|
||||
.apply("PATINDEX('%,'+{0}+',%', ','+websites+',') > 0", website));
|
||||
}
|
||||
|
||||
default Long selectCountByPackageId(Long packageId) {
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
package cn.iocoder.lyzsys.module.system.service.dict;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.lyzsys.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.mysql.dict.DictDataMapper;
|
||||
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.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.lyzsys.module.system.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Import(DictDataServiceImpl.class)
|
||||
public class DictDataServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private DictDataServiceImpl dictDataService;
|
||||
|
||||
@Resource
|
||||
private DictDataMapper dictDataMapper;
|
||||
@MockBean
|
||||
private DictTypeService dictTypeService;
|
||||
|
||||
@Test
|
||||
public void testGetDictDataList() {
|
||||
// mock 数据
|
||||
DictDataDO dictDataDO01 = randomDictDataDO().setDictType("yunai").setSort(2)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
dictDataMapper.insert(dictDataDO01);
|
||||
DictDataDO dictDataDO02 = randomDictDataDO().setDictType("yunai").setSort(1)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
dictDataMapper.insert(dictDataDO02);
|
||||
DictDataDO dictDataDO03 = randomDictDataDO().setDictType("yunai").setSort(3)
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
dictDataMapper.insert(dictDataDO03);
|
||||
DictDataDO dictDataDO04 = randomDictDataDO().setDictType("yunai2").setSort(3)
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
dictDataMapper.insert(dictDataDO04);
|
||||
// 准备参数
|
||||
Integer status = CommonStatusEnum.ENABLE.getStatus();
|
||||
String dictType = "yunai";
|
||||
|
||||
// 调用
|
||||
List<DictDataDO> dictDataDOList = dictDataService.getDictDataList(status, dictType);
|
||||
// 断言
|
||||
assertEquals(2, dictDataDOList.size());
|
||||
assertPojoEquals(dictDataDO02, dictDataDOList.get(0));
|
||||
assertPojoEquals(dictDataDO01, dictDataDOList.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictDataPage() {
|
||||
// mock 数据
|
||||
DictDataDO dbDictData = randomPojo(DictDataDO.class, o -> { // 等会查询到
|
||||
o.setLabel("芋艿");
|
||||
o.setDictType("yunai");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
dictDataMapper.insert(dbDictData);
|
||||
// 测试 label 不匹配
|
||||
dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setLabel("艿")));
|
||||
// 测试 dictType 不匹配
|
||||
dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setDictType("nai")));
|
||||
// 测试 status 不匹配
|
||||
dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 准备参数
|
||||
DictDataPageReqVO reqVO = new DictDataPageReqVO();
|
||||
reqVO.setLabel("芋");
|
||||
reqVO.setDictType("yunai");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
PageResult<DictDataDO> pageResult = dictDataService.getDictDataPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbDictData, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictData() {
|
||||
// mock 数据
|
||||
DictDataDO dbDictData = randomDictDataDO();
|
||||
dictDataMapper.insert(dbDictData);
|
||||
// 准备参数
|
||||
Long id = dbDictData.getId();
|
||||
|
||||
// 调用
|
||||
DictDataDO dictData = dictDataService.getDictData(id);
|
||||
// 断言
|
||||
assertPojoEquals(dbDictData, dictData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDictData_success() {
|
||||
// 准备参数
|
||||
DictDataSaveReqVO reqVO = randomPojo(DictDataSaveReqVO.class,
|
||||
o -> o.setStatus(randomCommonStatus()))
|
||||
.setId(null); // 防止 id 被赋值
|
||||
// mock 方法
|
||||
when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
|
||||
|
||||
// 调用
|
||||
Long dictDataId = dictDataService.createDictData(reqVO);
|
||||
// 断言
|
||||
assertNotNull(dictDataId);
|
||||
// 校验记录的属性是否正确
|
||||
DictDataDO dictData = dictDataMapper.selectById(dictDataId);
|
||||
assertPojoEquals(reqVO, dictData, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDictData_success() {
|
||||
// mock 数据
|
||||
DictDataDO dbDictData = randomDictDataDO();
|
||||
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
DictDataSaveReqVO reqVO = randomPojo(DictDataSaveReqVO.class, o -> {
|
||||
o.setId(dbDictData.getId()); // 设置更新的 ID
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
// mock 方法,字典类型
|
||||
when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));
|
||||
|
||||
// 调用
|
||||
dictDataService.updateDictData(reqVO);
|
||||
// 校验是否更新正确
|
||||
DictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, dictData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDictData_success() {
|
||||
// mock 数据
|
||||
DictDataDO dbDictData = randomDictDataDO();
|
||||
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbDictData.getId();
|
||||
|
||||
// 调用
|
||||
dictDataService.deleteDictData(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(dictDataMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataExists_success() {
|
||||
// mock 数据
|
||||
DictDataDO dbDictData = randomDictDataDO();
|
||||
dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据
|
||||
|
||||
// 调用成功
|
||||
dictDataService.validateDictDataExists(dbDictData.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataExists_notExists() {
|
||||
assertServiceException(() -> dictDataService.validateDictDataExists(randomLongId()), DICT_DATA_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeExists_success() {
|
||||
// mock 方法,数据类型被禁用
|
||||
String type = randomString();
|
||||
when(dictTypeService.getDictType(eq(type))).thenReturn(randomDictTypeDO(type));
|
||||
|
||||
// 调用, 成功
|
||||
dictDataService.validateDictTypeExists(type);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeExists_notExists() {
|
||||
assertServiceException(() -> dictDataService.validateDictTypeExists(randomString()), DICT_TYPE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeExists_notEnable() {
|
||||
// mock 方法,数据类型被禁用
|
||||
String dictType = randomString();
|
||||
when(dictTypeService.getDictType(eq(dictType))).thenReturn(
|
||||
randomPojo(DictTypeDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> dictDataService.validateDictTypeExists(dictType), DICT_TYPE_NOT_ENABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataValueUnique_success() {
|
||||
// 调用,成功
|
||||
dictDataService.validateDictDataValueUnique(randomLongId(), randomString(), randomString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataValueUnique_valueDuplicateForCreate() {
|
||||
// 准备参数
|
||||
String dictType = randomString();
|
||||
String value = randomString();
|
||||
// mock 数据
|
||||
dictDataMapper.insert(randomDictDataDO(o -> {
|
||||
o.setDictType(dictType);
|
||||
o.setValue(value);
|
||||
}));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> dictDataService.validateDictDataValueUnique(null, dictType, value),
|
||||
DICT_DATA_VALUE_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataValueUnique_valueDuplicateForUpdate() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
String dictType = randomString();
|
||||
String value = randomString();
|
||||
// mock 数据
|
||||
dictDataMapper.insert(randomDictDataDO(o -> {
|
||||
o.setDictType(dictType);
|
||||
o.setValue(value);
|
||||
}));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> dictDataService.validateDictDataValueUnique(id, dictType, value),
|
||||
DICT_DATA_VALUE_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictDataCountByDictType() {
|
||||
// mock 数据
|
||||
dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("yunai")));
|
||||
dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("tudou")));
|
||||
dictDataMapper.insert(randomDictDataDO(o -> o.setDictType("yunai")));
|
||||
// 准备参数
|
||||
String dictType = "yunai";
|
||||
|
||||
// 调用
|
||||
long count = dictDataService.getDictDataCountByDictType(dictType);
|
||||
// 校验
|
||||
assertEquals(2L, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataList_success() {
|
||||
// mock 数据
|
||||
DictDataDO dictDataDO = randomDictDataDO().setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
dictDataMapper.insert(dictDataDO);
|
||||
// 准备参数
|
||||
String dictType = dictDataDO.getDictType();
|
||||
List<String> values = singletonList(dictDataDO.getValue());
|
||||
|
||||
// 调用,无需断言
|
||||
dictDataService.validateDictDataList(dictType, values);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataList_notFound() {
|
||||
// 准备参数
|
||||
String dictType = randomString();
|
||||
List<String> values = singletonList(randomString());
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> dictDataService.validateDictDataList(dictType, values), DICT_DATA_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataList_notEnable() {
|
||||
// mock 数据
|
||||
DictDataDO dictDataDO = randomDictDataDO().setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
dictDataMapper.insert(dictDataDO);
|
||||
// 准备参数
|
||||
String dictType = dictDataDO.getDictType();
|
||||
List<String> values = singletonList(dictDataDO.getValue());
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> dictDataService.validateDictDataList(dictType, values),
|
||||
DICT_DATA_NOT_ENABLE, dictDataDO.getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictData_dictType() {
|
||||
// mock 数据
|
||||
DictDataDO dictDataDO = randomDictDataDO().setDictType("yunai").setValue("1");
|
||||
dictDataMapper.insert(dictDataDO);
|
||||
DictDataDO dictDataDO02 = randomDictDataDO().setDictType("yunai").setValue("2");
|
||||
dictDataMapper.insert(dictDataDO02);
|
||||
// 准备参数
|
||||
String dictType = "yunai";
|
||||
String value = "1";
|
||||
|
||||
// 调用
|
||||
DictDataDO dbDictData = dictDataService.getDictData(dictType, value);
|
||||
// 断言
|
||||
assertEquals(dictDataDO, dbDictData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDictData() {
|
||||
// mock 数据
|
||||
DictDataDO dictDataDO = randomDictDataDO().setDictType("yunai").setLabel("1");
|
||||
dictDataMapper.insert(dictDataDO);
|
||||
DictDataDO dictDataDO02 = randomDictDataDO().setDictType("yunai").setLabel("2");
|
||||
dictDataMapper.insert(dictDataDO02);
|
||||
// 准备参数
|
||||
String dictType = "yunai";
|
||||
String label = "1";
|
||||
|
||||
// 调用
|
||||
DictDataDO dbDictData = dictDataService.parseDictData(dictType, label);
|
||||
// 断言
|
||||
assertEquals(dictDataDO, dbDictData);
|
||||
}
|
||||
|
||||
// ========== 随机对象 ==========
|
||||
|
||||
@SafeVarargs
|
||||
private static DictDataDO randomDictDataDO(Consumer<DictDataDO>... consumers) {
|
||||
Consumer<DictDataDO> consumer = (o) -> {
|
||||
o.setStatus(randomCommonStatus()); // 保证 status 的范围
|
||||
};
|
||||
return randomPojo(DictDataDO.class, ArrayUtils.append(consumer, consumers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个有效的字典类型
|
||||
*
|
||||
* @param type 字典类型
|
||||
* @return DictTypeDO 对象
|
||||
*/
|
||||
private static DictTypeDO randomDictTypeDO(String type) {
|
||||
return randomPojo(DictTypeDO.class, o -> {
|
||||
o.setType(type);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 是开启
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
package cn.iocoder.lyzsys.module.system.service.dict;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.lyzsys.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.mysql.dict.DictTypeMapper;
|
||||
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.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.lyzsys.module.system.enums.ErrorCodeConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Import(DictTypeServiceImpl.class)
|
||||
public class DictTypeServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private DictTypeServiceImpl dictTypeService;
|
||||
|
||||
@Resource
|
||||
private DictTypeMapper dictTypeMapper;
|
||||
@MockBean
|
||||
private DictDataService dictDataService;
|
||||
|
||||
@Test
|
||||
public void testGetDictTypePage() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomPojo(DictTypeDO.class, o -> { // 等会查询到
|
||||
o.setName("yunai");
|
||||
o.setType("芋艿");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2021, 1, 15));
|
||||
});
|
||||
dictTypeMapper.insert(dbDictType);
|
||||
// 测试 name 不匹配
|
||||
dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setName("tudou")));
|
||||
// 测试 type 不匹配
|
||||
dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setType("土豆")));
|
||||
// 测试 status 不匹配
|
||||
dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setCreateTime(buildTime(2021, 1, 1))));
|
||||
// 准备参数
|
||||
DictTypePageReqVO reqVO = new DictTypePageReqVO();
|
||||
reqVO.setName("nai");
|
||||
reqVO.setType("艿");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2021, 1, 10, 2021, 1, 20));
|
||||
|
||||
// 调用
|
||||
PageResult<DictTypeDO> pageResult = dictTypeService.getDictTypePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbDictType, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictType_id() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dbDictType);
|
||||
// 准备参数
|
||||
Long id = dbDictType.getId();
|
||||
|
||||
// 调用
|
||||
DictTypeDO dictType = dictTypeService.getDictType(id);
|
||||
// 断言
|
||||
assertNotNull(dictType);
|
||||
assertPojoEquals(dbDictType, dictType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictType_type() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dbDictType);
|
||||
// 准备参数
|
||||
String type = dbDictType.getType();
|
||||
|
||||
// 调用
|
||||
DictTypeDO dictType = dictTypeService.getDictType(type);
|
||||
// 断言
|
||||
assertNotNull(dictType);
|
||||
assertPojoEquals(dbDictType, dictType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDictType_success() {
|
||||
// 准备参数
|
||||
DictTypeSaveReqVO reqVO = randomPojo(DictTypeSaveReqVO.class,
|
||||
o -> o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()))
|
||||
.setId(null); // 避免 id 被赋值
|
||||
|
||||
// 调用
|
||||
Long dictTypeId = dictTypeService.createDictType(reqVO);
|
||||
// 断言
|
||||
assertNotNull(dictTypeId);
|
||||
// 校验记录的属性是否正确
|
||||
DictTypeDO dictType = dictTypeMapper.selectById(dictTypeId);
|
||||
assertPojoEquals(reqVO, dictType, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDictType_success() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
DictTypeSaveReqVO reqVO = randomPojo(DictTypeSaveReqVO.class, o -> {
|
||||
o.setId(dbDictType.getId()); // 设置更新的 ID
|
||||
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus());
|
||||
});
|
||||
|
||||
// 调用
|
||||
dictTypeService.updateDictType(reqVO);
|
||||
// 校验是否更新正确
|
||||
DictTypeDO dictType = dictTypeMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, dictType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDictType_success() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbDictType.getId();
|
||||
|
||||
// 调用
|
||||
dictTypeService.deleteDictType(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(dictTypeMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDictType_hasChildren() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbDictType.getId();
|
||||
// mock 方法
|
||||
when(dictDataService.getDictDataCountByDictType(eq(dbDictType.getType()))).thenReturn(1L);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_HAS_CHILDREN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDictTypeList() {
|
||||
// 准备参数
|
||||
DictTypeDO dictTypeDO01 = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dictTypeDO01);
|
||||
DictTypeDO dictTypeDO02 = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dictTypeDO02);
|
||||
// mock 方法
|
||||
|
||||
// 调用
|
||||
List<DictTypeDO> dictTypeDOList = dictTypeService.getDictTypeList();
|
||||
// 断言
|
||||
assertEquals(2, dictTypeDOList.size());
|
||||
assertPojoEquals(dictTypeDO01, dictTypeDOList.get(0));
|
||||
assertPojoEquals(dictTypeDO02, dictTypeDOList.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataExists_success() {
|
||||
// mock 数据
|
||||
DictTypeDO dbDictType = randomDictTypeDO();
|
||||
dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据
|
||||
|
||||
// 调用成功
|
||||
dictTypeService.validateDictTypeExists(dbDictType.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictDataExists_notExists() {
|
||||
assertServiceException(() -> dictTypeService.validateDictTypeExists(randomLongId()), DICT_TYPE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeUnique_success() {
|
||||
// 调用,成功
|
||||
dictTypeService.validateDictTypeUnique(randomLongId(), randomString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeUnique_valueDuplicateForCreate() {
|
||||
// 准备参数
|
||||
String type = randomString();
|
||||
// mock 数据
|
||||
dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> dictTypeService.validateDictTypeUnique(null, type),
|
||||
DICT_TYPE_TYPE_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeUnique_valueDuplicateForUpdate() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
String type = randomString();
|
||||
// mock 数据
|
||||
dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> dictTypeService.validateDictTypeUnique(id, type),
|
||||
DICT_TYPE_TYPE_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypNameUnique_success() {
|
||||
// 调用,成功
|
||||
dictTypeService.validateDictTypeNameUnique(randomLongId(), randomString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeNameUnique_nameDuplicateForCreate() {
|
||||
// 准备参数
|
||||
String name = randomString();
|
||||
// mock 数据
|
||||
dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> dictTypeService.validateDictTypeNameUnique(null, name),
|
||||
DICT_TYPE_NAME_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDictTypeNameUnique_nameDuplicateForUpdate() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
String name = randomString();
|
||||
// mock 数据
|
||||
dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));
|
||||
|
||||
// 调用,校验异常
|
||||
assertServiceException(() -> dictTypeService.validateDictTypeNameUnique(id, name),
|
||||
DICT_TYPE_NAME_DUPLICATE);
|
||||
}
|
||||
|
||||
// ========== 随机对象 ==========
|
||||
|
||||
@SafeVarargs
|
||||
private static DictTypeDO randomDictTypeDO(Consumer<DictTypeDO>... consumers) {
|
||||
Consumer<DictTypeDO> consumer = (o) -> {
|
||||
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
|
||||
};
|
||||
return randomPojo(DictTypeDO.class, ArrayUtils.append(consumer, consumers));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
package cn.iocoder.lyzsys.module.system.service.permission;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.lyzsys.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.mysql.permission.MenuMapper;
|
||||
import cn.iocoder.lyzsys.module.system.enums.permission.MenuTypeEnum;
|
||||
import cn.iocoder.lyzsys.module.system.service.tenant.TenantService;
|
||||
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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.collection.SetUtils.asSet;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.lyzsys.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
|
||||
import static cn.iocoder.lyzsys.module.system.enums.ErrorCodeConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@Import(MenuServiceImpl.class)
|
||||
public class MenuServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MenuServiceImpl menuService;
|
||||
|
||||
@Resource
|
||||
private MenuMapper menuMapper;
|
||||
|
||||
@MockBean
|
||||
private PermissionService permissionService;
|
||||
@MockBean
|
||||
private TenantService tenantService;
|
||||
|
||||
@Test
|
||||
public void testCreateMenu_success() {
|
||||
// mock 数据(构造父菜单)
|
||||
MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU,
|
||||
"parent", 0L);
|
||||
menuMapper.insert(menuDO);
|
||||
Long parentId = menuDO.getId();
|
||||
// 准备参数
|
||||
MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> {
|
||||
o.setParentId(parentId);
|
||||
o.setName("testSonName");
|
||||
o.setType(MenuTypeEnum.MENU.getType());
|
||||
}).setId(null); // 防止 id 被赋值
|
||||
Long menuId = menuService.createMenu(reqVO);
|
||||
|
||||
// 校验记录的属性是否正确
|
||||
MenuDO dbMenu = menuMapper.selectById(menuId);
|
||||
assertPojoEquals(reqVO, dbMenu, "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMenu_success() {
|
||||
// mock 数据(构造父子菜单)
|
||||
MenuDO sonMenuDO = createParentAndSonMenu();
|
||||
Long sonId = sonMenuDO.getId();
|
||||
// 准备参数
|
||||
MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> {
|
||||
o.setId(sonId);
|
||||
o.setName("testSonName"); // 修改名字
|
||||
o.setParentId(sonMenuDO.getParentId());
|
||||
o.setType(MenuTypeEnum.MENU.getType());
|
||||
});
|
||||
|
||||
// 调用
|
||||
menuService.updateMenu(reqVO);
|
||||
// 校验记录的属性是否正确
|
||||
MenuDO dbMenu = menuMapper.selectById(sonId);
|
||||
assertPojoEquals(reqVO, dbMenu);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMenu_sonIdNotExist() {
|
||||
// 准备参数
|
||||
MenuSaveVO reqVO = randomPojo(MenuSaveVO.class);
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> menuService.updateMenu(reqVO), MENU_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMenu_success() {
|
||||
// mock 数据
|
||||
MenuDO menuDO = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menuDO);
|
||||
// 准备参数
|
||||
Long id = menuDO.getId();
|
||||
|
||||
// 调用
|
||||
menuService.deleteMenu(id);
|
||||
// 断言
|
||||
MenuDO dbMenuDO = menuMapper.selectById(id);
|
||||
assertNull(dbMenuDO);
|
||||
verify(permissionService).processMenuDeleted(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMenu_menuNotExist() {
|
||||
assertServiceException(() -> menuService.deleteMenu(randomLongId()),
|
||||
MENU_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMenu_existChildren() {
|
||||
// mock 数据(构造父子菜单)
|
||||
MenuDO sonMenu = createParentAndSonMenu();
|
||||
// 准备参数
|
||||
Long parentId = sonMenu.getParentId();
|
||||
|
||||
// 调用并断言异常
|
||||
assertServiceException(() -> menuService.deleteMenu(parentId), MENU_EXISTS_CHILDREN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuList_all() {
|
||||
// mock 数据
|
||||
MenuDO menu100 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu100);
|
||||
MenuDO menu101 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu101);
|
||||
// 准备参数
|
||||
|
||||
// 调用
|
||||
List<MenuDO> list = menuService.getMenuList();
|
||||
// 断言
|
||||
assertEquals(2, list.size());
|
||||
assertPojoEquals(menu100, list.get(0));
|
||||
assertPojoEquals(menu101, list.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuList() {
|
||||
// mock 数据
|
||||
MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setName("芋艿").setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
menuMapper.insert(menuDO);
|
||||
// 测试 status 不匹配
|
||||
menuMapper.insert(cloneIgnoreId(menuDO, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 name 不匹配
|
||||
menuMapper.insert(cloneIgnoreId(menuDO, o -> o.setName("艿")));
|
||||
// 准备参数
|
||||
MenuListReqVO reqVO = new MenuListReqVO().setName("芋").setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
List<MenuDO> result = menuService.getMenuList(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(menuDO, result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuListByTenant() {
|
||||
// mock 数据
|
||||
MenuDO menu100 = randomPojo(MenuDO.class, o -> o.setId(100L).setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
menuMapper.insert(menu100);
|
||||
MenuDO menu101 = randomPojo(MenuDO.class, o -> o.setId(101L).setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
menuMapper.insert(menu101);
|
||||
MenuDO menu102 = randomPojo(MenuDO.class, o -> o.setId(102L).setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
menuMapper.insert(menu102);
|
||||
// mock 过滤菜单
|
||||
Set<Long> menuIds = asSet(100L, 101L);
|
||||
doNothing().when(tenantService).handleTenantMenu(argThat(handler -> {
|
||||
handler.handle(menuIds);
|
||||
return true;
|
||||
}));
|
||||
// 准备参数
|
||||
MenuListReqVO reqVO = new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
List<MenuDO> result = menuService.getMenuListByTenant(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(menu100, result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuIdListByPermissionFromCache() {
|
||||
// mock 数据
|
||||
MenuDO menu100 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu100);
|
||||
MenuDO menu101 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu101);
|
||||
// 准备参数
|
||||
String permission = menu100.getPermission();
|
||||
|
||||
// 调用
|
||||
List<Long> ids = menuService.getMenuIdListByPermissionFromCache(permission);
|
||||
// 断言
|
||||
assertEquals(1, ids.size());
|
||||
assertEquals(menu100.getId(), ids.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuList_ids() {
|
||||
// mock 数据
|
||||
MenuDO menu100 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu100);
|
||||
MenuDO menu101 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu101);
|
||||
// 准备参数
|
||||
Collection<Long> ids = Collections.singleton(menu100.getId());
|
||||
|
||||
// 调用
|
||||
List<MenuDO> list = menuService.getMenuList(ids);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(menu100, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenu() {
|
||||
// mock 数据
|
||||
MenuDO menu = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menu);
|
||||
// 准备参数
|
||||
Long id = menu.getId();
|
||||
|
||||
// 调用
|
||||
MenuDO dbMenu = menuService.getMenu(id);
|
||||
// 断言
|
||||
assertPojoEquals(menu, dbMenu);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateParentMenu_success() {
|
||||
// mock 数据
|
||||
MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", 0L);
|
||||
menuMapper.insert(menuDO);
|
||||
// 准备参数
|
||||
Long parentId = menuDO.getId();
|
||||
|
||||
// 调用,无需断言
|
||||
menuService.validateParentMenu(parentId, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateParentMenu_canNotSetSelfToBeParent() {
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> menuService.validateParentMenu(1L, 1L),
|
||||
MENU_PARENT_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateParentMenu_parentNotExist() {
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> menuService.validateParentMenu(randomLongId(), null),
|
||||
MENU_PARENT_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateParentMenu_parentTypeError() {
|
||||
// mock 数据
|
||||
MenuDO menuDO = buildMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
|
||||
menuMapper.insert(menuDO);
|
||||
// 准备参数
|
||||
Long parentId = menuDO.getId();
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> menuService.validateParentMenu(parentId, null),
|
||||
MENU_PARENT_NOT_DIR_OR_MENU);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMenu_Name_success() {
|
||||
// mock 父子菜单
|
||||
MenuDO sonMenu = createParentAndSonMenu();
|
||||
// 准备参数
|
||||
Long parentId = sonMenu.getParentId();
|
||||
Long otherSonMenuId = randomLongId();
|
||||
String otherSonMenuName = randomString();
|
||||
|
||||
// 调用,无需断言
|
||||
menuService.validateMenuName(parentId, otherSonMenuName, otherSonMenuId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMenu_sonMenuNameNameDuplicate() {
|
||||
// mock 父子菜单
|
||||
MenuDO sonMenu = createParentAndSonMenu();
|
||||
// 准备参数
|
||||
Long parentId = sonMenu.getParentId();
|
||||
Long otherSonMenuId = randomLongId();
|
||||
String otherSonMenuName = sonMenu.getName(); //相同名称
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> menuService.validateMenuName(parentId, otherSonMenuName, otherSonMenuId),
|
||||
MENU_NAME_DUPLICATE);
|
||||
}
|
||||
|
||||
// ====================== 初始化方法 ======================
|
||||
|
||||
/**
|
||||
* 插入父子菜单,返回子菜单
|
||||
*
|
||||
* @return 子菜单
|
||||
*/
|
||||
private MenuDO createParentAndSonMenu() {
|
||||
// 构造父子菜单
|
||||
MenuDO parentMenuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
|
||||
menuMapper.insert(parentMenuDO);
|
||||
// 构建子菜单
|
||||
MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName",
|
||||
parentMenuDO.getParentId());
|
||||
menuMapper.insert(sonMenuDO);
|
||||
return sonMenuDO;
|
||||
}
|
||||
|
||||
private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId) {
|
||||
return buildMenuDO(type, name, parentId, randomCommonStatus());
|
||||
}
|
||||
|
||||
private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
|
||||
return randomPojo(MenuDO.class, o -> o.setId(null).setName(name).setParentId(parentId)
|
||||
.setType(type.getType()).setStatus(status));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
package cn.iocoder.lyzsys.module.system.service.permission;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.lyzsys.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
|
||||
import cn.iocoder.lyzsys.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.lyzsys.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.dept.DeptDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.permission.UserRoleDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||
import cn.iocoder.lyzsys.module.system.dal.mysql.permission.UserRoleMapper;
|
||||
import cn.iocoder.lyzsys.module.system.enums.permission.DataScopeEnum;
|
||||
import cn.iocoder.lyzsys.module.system.service.dept.DeptService;
|
||||
import cn.iocoder.lyzsys.module.system.service.user.AdminUserService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.collection.ListUtil.toList;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.collection.SetUtils.asSet;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@Import({PermissionServiceImpl.class})
|
||||
public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private PermissionServiceImpl permissionService;
|
||||
|
||||
@Resource
|
||||
private RoleMenuMapper roleMenuMapper;
|
||||
@Resource
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
@MockBean
|
||||
private RoleService roleService;
|
||||
@MockBean
|
||||
private MenuService menuService;
|
||||
@MockBean
|
||||
private DeptService deptService;
|
||||
@MockBean
|
||||
private AdminUserService userService;
|
||||
|
||||
@Test
|
||||
public void testHasAnyPermissions_superAdmin() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"system:user:query", "system:user:create"};
|
||||
// mock 用户登录的角色
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
|
||||
// mock 其它方法
|
||||
when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
|
||||
|
||||
// 调用,并断言
|
||||
assertTrue(permissionService.hasAnyPermissions(userId, roles));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyPermissions_normal() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"system:user:query", "system:user:create"};
|
||||
// mock 用户登录的角色
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
|
||||
// mock 菜单
|
||||
Long menuId = 1000L;
|
||||
when(menuService.getMenuIdListByPermissionFromCache(
|
||||
eq("system:user:create"))).thenReturn(singletonList(menuId));
|
||||
roleMenuMapper.insert(randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1000L));
|
||||
|
||||
// 调用,并断言
|
||||
assertTrue(permissionService.hasAnyPermissions(userId, roles));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyRoles() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"yunai", "tudou"};
|
||||
// mock 用户与角色的缓存
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("tudou")
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
|
||||
|
||||
// 调用,并断言
|
||||
assertTrue(permissionService.hasAnyRoles(userId, roles));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 角色-菜单的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testAssignRoleMenu() {
|
||||
// 准备参数
|
||||
Long roleId = 1L;
|
||||
Set<Long> menuIds = asSet(200L, 300L);
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);
|
||||
roleMenuMapper.insert(roleMenu01);
|
||||
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);
|
||||
roleMenuMapper.insert(roleMenu02);
|
||||
|
||||
// 调用
|
||||
permissionService.assignRoleMenu(roleId, menuIds);
|
||||
// 断言
|
||||
List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();
|
||||
assertEquals(2, roleMenuList.size());
|
||||
assertEquals(1L, roleMenuList.get(0).getRoleId());
|
||||
assertEquals(200L, roleMenuList.get(0).getMenuId());
|
||||
assertEquals(1L, roleMenuList.get(1).getRoleId());
|
||||
assertEquals(300L, roleMenuList.get(1).getMenuId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessRoleDeleted() {
|
||||
// 准备参数
|
||||
Long roleId = randomLongId();
|
||||
// mock 数据 UserRole
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setRoleId(roleId)); // 被删除
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
|
||||
userRoleMapper.insert(userRoleDO02);
|
||||
// mock 数据 RoleMenu
|
||||
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(roleId)); // 被删除
|
||||
roleMenuMapper.insert(roleMenuDO01);
|
||||
RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
|
||||
roleMenuMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
permissionService.processRoleDeleted(roleId);
|
||||
// 断言数据 RoleMenuDO
|
||||
List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
|
||||
assertEquals(1, dbRoleMenus.size());
|
||||
assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
|
||||
// 断言数据 UserRoleDO
|
||||
List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
|
||||
assertEquals(1, dbUserRoles.size());
|
||||
assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessMenuDeleted() {
|
||||
// 准备参数
|
||||
Long menuId = randomLongId();
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setMenuId(menuId)); // 被删除
|
||||
roleMenuMapper.insert(roleMenuDO01);
|
||||
RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除
|
||||
roleMenuMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
permissionService.processMenuDeleted(menuId);
|
||||
// 断言数据
|
||||
List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
|
||||
assertEquals(1, dbRoleMenus.size());
|
||||
assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuIds_superAdmin() {
|
||||
// 准备参数
|
||||
Long roleId = 100L;
|
||||
// mock 方法
|
||||
when(roleService.hasAnySuperAdmin(eq(singleton(100L)))).thenReturn(true);
|
||||
List<MenuDO> menuList = singletonList(randomPojo(MenuDO.class).setId(1L));
|
||||
when(menuService.getMenuList()).thenReturn(menuList);
|
||||
|
||||
// 调用
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
|
||||
// 断言
|
||||
assertEquals(singleton(1L), menuIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuIds_normal() {
|
||||
// 准备参数
|
||||
Long roleId = 100L;
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu01);
|
||||
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(2L);
|
||||
roleMenuMapper.insert(roleMenu02);
|
||||
|
||||
// 调用
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
|
||||
// 断言
|
||||
assertEquals(asSet(1L, 2L), menuIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuRoleIdListByMenuIdFromCache() {
|
||||
// 准备参数
|
||||
Long menuId = 1L;
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu01);
|
||||
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(200L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu02);
|
||||
|
||||
// 调用
|
||||
Set<Long> roleIds = permissionService.getMenuRoleIdListByMenuIdFromCache(menuId);
|
||||
// 断言
|
||||
assertEquals(asSet(100L, 200L), roleIds);
|
||||
}
|
||||
|
||||
// ========== 用户-角色的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testAssignUserRole() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
Set<Long> roleIds = asSet(200L, 300L);
|
||||
// mock 数据
|
||||
UserRoleDO userRole01 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(100L);
|
||||
userRoleMapper.insert(userRole01);
|
||||
UserRoleDO userRole02 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(200L);
|
||||
userRoleMapper.insert(userRole02);
|
||||
|
||||
// 调用
|
||||
permissionService.assignUserRole(userId, roleIds);
|
||||
// 断言
|
||||
List<UserRoleDO> userRoleDOList = userRoleMapper.selectList();
|
||||
assertEquals(2, userRoleDOList.size());
|
||||
assertEquals(1L, userRoleDOList.get(0).getUserId());
|
||||
assertEquals(200L, userRoleDOList.get(0).getRoleId());
|
||||
assertEquals(1L, userRoleDOList.get(1).getUserId());
|
||||
assertEquals(300L, userRoleDOList.get(1).getRoleId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessUserDeleted() {
|
||||
// 准备参数
|
||||
Long userId = randomLongId();
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(userId)); // 被删除
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除
|
||||
userRoleMapper.insert(userRoleDO02);
|
||||
|
||||
// 调用
|
||||
permissionService.processUserDeleted(userId);
|
||||
// 断言数据
|
||||
List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
|
||||
assertEquals(1, dbUserRoles.size());
|
||||
assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdListByUserId() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserId(userId);
|
||||
// 断言
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdListByUserIdFromCache() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
|
||||
// 断言
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdsFromCache() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
|
||||
// 断言
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdListByRoleId() {
|
||||
// 准备参数
|
||||
Collection<Long> roleIds = asSet(10L, 20L);
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> result = permissionService.getUserRoleIdListByRoleId(roleIds);
|
||||
// 断言
|
||||
assertEquals(asSet(1L, 2L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEnableUserRoleListByUserIdFromCache() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户登录的角色
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(200L));
|
||||
RoleDO role01 = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
RoleDO role02 = randomPojo(RoleDO.class, o -> o.setId(200L)
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(asSet(100L, 200L))))
|
||||
.thenReturn(toList(role01, role02));
|
||||
|
||||
// 调用
|
||||
List<RoleDO> result = permissionService.getEnableUserRoleListByUserIdFromCache(userId);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(role01, result.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 用户-部门的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testAssignRoleDataScope() {
|
||||
// 准备参数
|
||||
Long roleId = 1L;
|
||||
Integer dataScope = 2;
|
||||
Set<Long> dataScopeDeptIds = asSet(10L, 20L);
|
||||
|
||||
// 调用
|
||||
permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
|
||||
// 断言
|
||||
verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_All() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertTrue(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_DeptCustom() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
|
||||
null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_DeptOnly() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
|
||||
null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(1, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_DeptAndChild() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
|
||||
null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
// mock 方法(部门)
|
||||
DeptDO deptDO = randomPojo(DeptDO.class);
|
||||
when(deptService.getChildDeptIdListFromCache(eq(3L))).thenReturn(singleton(deptDO.getId()));
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(2, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_Self() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertTrue(result.getSelf());
|
||||
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
package cn.iocoder.lyzsys.module.system.service.permission;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.lyzsys.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.permission.vo.role.RolePageReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.lyzsys.module.system.dal.mysql.permission.RoleMapper;
|
||||
import cn.iocoder.lyzsys.module.system.enums.permission.DataScopeEnum;
|
||||
import cn.iocoder.lyzsys.module.system.enums.permission.RoleTypeEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
||||
import static cn.iocoder.lyzsys.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.lyzsys.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.lyzsys.module.system.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@Import(RoleServiceImpl.class)
|
||||
public class RoleServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private RoleServiceImpl roleService;
|
||||
|
||||
@Resource
|
||||
private RoleMapper roleMapper;
|
||||
|
||||
@MockBean
|
||||
private PermissionService permissionService;
|
||||
|
||||
@Test
|
||||
public void testCreateRole() {
|
||||
// 准备参数
|
||||
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class)
|
||||
.setId(null) // 防止 id 被赋值
|
||||
.setStatus(randomCommonStatus());
|
||||
|
||||
// 调用
|
||||
Long roleId = roleService.createRole(reqVO, null);
|
||||
// 断言
|
||||
RoleDO roleDO = roleMapper.selectById(roleId);
|
||||
assertPojoEquals(reqVO, roleDO, "id");
|
||||
assertEquals(RoleTypeEnum.CUSTOM.getType(), roleDO.getType());
|
||||
assertEquals(DataScopeEnum.ALL.getScope(), roleDO.getDataScope());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRole() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(id)
|
||||
.setStatus(randomCommonStatus()));
|
||||
|
||||
// 调用
|
||||
roleService.updateRole(reqVO);
|
||||
// 断言
|
||||
RoleDO newRoleDO = roleMapper.selectById(id);
|
||||
assertPojoEquals(reqVO, newRoleDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRoleDataScope() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
Integer dataScope = randomEle(DataScopeEnum.values()).getScope();
|
||||
Set<Long> dataScopeRoleIds = randomSet(Long.class);
|
||||
|
||||
// 调用
|
||||
roleService.updateRoleDataScope(id, dataScope, dataScopeRoleIds);
|
||||
// 断言
|
||||
RoleDO dbRoleDO = roleMapper.selectById(id);
|
||||
assertEquals(dataScope, dbRoleDO.getDataScope());
|
||||
assertEquals(dataScopeRoleIds, dbRoleDO.getDataScopeDeptIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRole() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 参数准备
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用
|
||||
roleService.deleteRole(id);
|
||||
// 断言
|
||||
assertNull(roleMapper.selectById(id));
|
||||
// verify 删除相关数据
|
||||
verify(permissionService).processRoleDeleted(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_success() {
|
||||
// 调用,不会抛异常
|
||||
roleService.validateRoleDuplicate(randomString(), randomString(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_nameDuplicate() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
String name = "role_name";
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
|
||||
ROLE_NAME_DUPLICATE, name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_codeDuplicate() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
String code = "code";
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
|
||||
ROLE_CODE_DUPLICATE, code);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_success() {
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用,无异常
|
||||
roleService.validateRoleForUpdate(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_roleIdNotExist() {
|
||||
assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
|
||||
assertServiceException(() -> roleService.validateRoleForUpdate(id),
|
||||
ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRole() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO);
|
||||
// 参数准备
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用
|
||||
RoleDO dbRoleDO = roleService.getRole(id);
|
||||
// 断言
|
||||
assertPojoEquals(roleDO, dbRoleDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleFromCache() {
|
||||
// mock 数据(缓存)
|
||||
RoleDO roleDO = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO);
|
||||
// 参数准备
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用
|
||||
RoleDO dbRoleDO = roleService.getRoleFromCache(id);
|
||||
// 断言
|
||||
assertPojoEquals(roleDO, dbRoleDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleListByStatus() {
|
||||
// mock 数据
|
||||
RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole01);
|
||||
RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
roleMapper.insert(dbRole02);
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleListByStatus(
|
||||
singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole01, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleList() {
|
||||
// mock 数据
|
||||
RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole01);
|
||||
RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
roleMapper.insert(dbRole02);
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleList();
|
||||
// 断言
|
||||
assertEquals(2, list.size());
|
||||
assertPojoEquals(dbRole01, list.get(0));
|
||||
assertPojoEquals(dbRole02, list.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleList_ids() {
|
||||
// mock 数据
|
||||
RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole01);
|
||||
RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
roleMapper.insert(dbRole02);
|
||||
// 准备参数
|
||||
Collection<Long> ids = singleton(dbRole01.getId());
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleList(ids);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole01, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleListFromCache() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole);
|
||||
// 测试 id 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
|
||||
// 准备参数
|
||||
Collection<Long> ids = singleton(dbRole.getId());
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleListFromCache(ids);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole, list.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRolePage() {
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class, o -> { // 等会查询到
|
||||
o.setName("土豆");
|
||||
o.setCode("tudou");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2022, 2, 8));
|
||||
});
|
||||
roleMapper.insert(dbRole);
|
||||
// 测试 name 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setName("红薯")));
|
||||
// 测试 code 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCode("hong")));
|
||||
// 测试 createTime 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCreateTime(buildTime(2022, 2, 16))));
|
||||
// 准备参数
|
||||
RolePageReqVO reqVO = new RolePageReqVO();
|
||||
reqVO.setName("土豆");
|
||||
reqVO.setCode("tu");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 12));
|
||||
|
||||
// 调用
|
||||
PageResult<RoleDO> pageResult = roleService.getRolePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbRole, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnySuperAdmin_true() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class).setCode("super_admin");
|
||||
roleMapper.insert(dbRole);
|
||||
// 准备参数
|
||||
Long id = dbRole.getId();
|
||||
|
||||
// 调用,并调用
|
||||
assertTrue(roleService.hasAnySuperAdmin(singletonList(id)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnySuperAdmin_false() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class).setCode("tenant_admin");
|
||||
roleMapper.insert(dbRole);
|
||||
// 准备参数
|
||||
Long id = dbRole.getId();
|
||||
|
||||
// 调用,并调用
|
||||
assertFalse(roleService.hasAnySuperAdmin(singletonList(id)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleList_success() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
List<Long> ids = singletonList(roleDO.getId());
|
||||
|
||||
// 调用,无需断言
|
||||
roleService.validateRoleList(ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleList_notFound() {
|
||||
// 准备参数
|
||||
List<Long> ids = singletonList(randomLongId());
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleList(ids), ROLE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleList_notEnable() {
|
||||
// mock 数据
|
||||
RoleDO RoleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
roleMapper.insert(RoleDO);
|
||||
// 准备参数
|
||||
List<Long> ids = singletonList(RoleDO.getId());
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleList(ids), ROLE_IS_DISABLE, RoleDO.getName());
|
||||
}
|
||||
}
|
||||
64
lyzsys-module-tjt/pom.xml
Normal file
64
lyzsys-module-tjt/pom.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>lyzsys-module-tjt</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
tjt 模块,承载特建投设计产值统计业务。
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-module-infra</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>lyzsys-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,82 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 员工")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/employee")
|
||||
@Validated
|
||||
public class EmployeeController {
|
||||
|
||||
@Resource
|
||||
private EmployeeService employeeService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建员工")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee:create')")
|
||||
public CommonResult<Long> createEmployee(@Valid @RequestBody EmployeeSaveReqVO createReqVO) {
|
||||
return success(employeeService.createEmployee(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改员工")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee:update')")
|
||||
public CommonResult<Boolean> updateEmployee(@Valid @RequestBody EmployeeSaveReqVO updateReqVO) {
|
||||
employeeService.updateEmployee(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除员工")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee:delete')")
|
||||
public CommonResult<Boolean> deleteEmployee(@RequestParam("id") Long id) {
|
||||
employeeService.deleteEmployee(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得员工详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee:query')")
|
||||
public CommonResult<EmployeeRespVO> getEmployee(@RequestParam("id") Long id) {
|
||||
return success(employeeService.getEmployee(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得员工分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee:query')")
|
||||
public CommonResult<PageResult<EmployeeRespVO>> getEmployeePage(@Valid EmployeePageReqVO pageReqVO) {
|
||||
return success(employeeService.getEmployeePage(pageReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得员工精简列表")
|
||||
public CommonResult<List<EmployeeSimpleRespVO>> getEmployeeSimpleList(
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "officeId", required = false) Long officeId,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "enabledFlag", required = false) Boolean enabledFlag,
|
||||
@RequestParam(value = "officeLeaderFlag", required = false) Boolean officeLeaderFlag) {
|
||||
return success(employeeService.getEmployeeSimpleList(keyword, officeId, status, enabledFlag, officeLeaderFlag));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - 员工分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmployeePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "员工姓名", example = "张")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "所属专业所 ID", example = "1")
|
||||
private Long officeId;
|
||||
|
||||
@Schema(description = "状态", example = "在职")
|
||||
private String employeeStatus;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "是否所长", example = "false")
|
||||
private Boolean officeLeaderFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 员工 Response VO")
|
||||
@Data
|
||||
public class EmployeeRespVO {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工姓名")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private String gender;
|
||||
|
||||
@Schema(description = "所属专业所 ID")
|
||||
private Long officeId;
|
||||
|
||||
@Schema(description = "所属专业所名称")
|
||||
private String officeName;
|
||||
|
||||
@Schema(description = "注册类型及等级")
|
||||
private String registrationType;
|
||||
|
||||
@Schema(description = "职称")
|
||||
private String jobTitle;
|
||||
|
||||
@Schema(description = "注册章号")
|
||||
private String registrationSealNo;
|
||||
|
||||
@Schema(description = "入职时间")
|
||||
private LocalDate entryDate;
|
||||
|
||||
@Schema(description = "离职时间")
|
||||
private LocalDate leaveDate;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String employeeStatus;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否所长")
|
||||
private Boolean officeLeaderFlag;
|
||||
|
||||
@Schema(description = "排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
|
||||
|
||||
@Schema(description = "管理后台 - 员工新增/修改 Request VO")
|
||||
@Data
|
||||
public class EmployeeSaveReqVO {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@NotBlank(message = "员工姓名不能为空")
|
||||
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "男")
|
||||
@NotBlank(message = "性别不能为空")
|
||||
@Size(max = 2, message = "性别长度不能超过 2 个字符")
|
||||
private String gender;
|
||||
|
||||
@Schema(description = "所属专业所 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "所属专业所不能为空")
|
||||
private Long officeId;
|
||||
|
||||
@Schema(description = "注册类型及等级", example = "一级注册建筑师")
|
||||
@Size(max = 100, message = "注册类型及等级长度不能超过 100 个字符")
|
||||
private String registrationType;
|
||||
|
||||
@Schema(description = "职称", example = "正高级")
|
||||
@Size(max = 50, message = "职称长度不能超过 50 个字符")
|
||||
private String jobTitle;
|
||||
|
||||
@Schema(description = "注册章号", example = "A123456")
|
||||
@Size(max = 100, message = "注册章号长度不能超过 100 个字符")
|
||||
private String registrationSealNo;
|
||||
|
||||
@Schema(description = "入职时间", example = "2026-01-01")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
private LocalDate entryDate;
|
||||
|
||||
@Schema(description = "离职时间", example = "2026-12-31")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
private LocalDate leaveDate;
|
||||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "在职")
|
||||
@NotBlank(message = "状态不能为空")
|
||||
@Size(max = 20, message = "状态长度不能超过 20 个字符")
|
||||
private String employeeStatus;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 255, message = "备注长度不能超过 255 个字符")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否所长", example = "false")
|
||||
private Boolean officeLeaderFlag;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 员工精简 Response VO")
|
||||
@Data
|
||||
public class EmployeeSimpleRespVO {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工姓名")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "所属专业所 ID")
|
||||
private Long officeId;
|
||||
|
||||
@Schema(description = "所属专业所名称")
|
||||
private String officeName;
|
||||
|
||||
@Schema(description = "员工状态")
|
||||
private String employeeStatus;
|
||||
|
||||
@Schema(description = "注册类型及等级")
|
||||
private String registrationType;
|
||||
|
||||
@Schema(description = "职称")
|
||||
private String jobTitle;
|
||||
|
||||
@Schema(description = "是否所长")
|
||||
private Boolean officeLeaderFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.*;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget.EmployeeYearCostBudgetService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 员工年度成本预算")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/employee-year-cost-budget")
|
||||
@Validated
|
||||
public class EmployeeYearCostBudgetController {
|
||||
|
||||
@Resource
|
||||
private EmployeeYearCostBudgetService employeeYearCostBudgetService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建员工年度成本预算")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:create')")
|
||||
public CommonResult<Long> createEmployeeYearCostBudget(@Valid @RequestBody EmployeeYearCostBudgetSaveReqVO createReqVO) {
|
||||
return success(employeeYearCostBudgetService.createEmployeeYearCostBudget(createReqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/generate")
|
||||
@Operation(summary = "按年度生成员工年度成本预算")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:create')")
|
||||
public CommonResult<EmployeeYearCostBudgetGenerateRespVO> generateEmployeeYearCostBudget(
|
||||
@Valid @RequestBody EmployeeYearCostBudgetGenerateReqVO generateReqVO) {
|
||||
return success(employeeYearCostBudgetService.generateEmployeeYearCostBudget(generateReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改员工年度成本预算")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:update')")
|
||||
public CommonResult<Boolean> updateEmployeeYearCostBudget(@Valid @RequestBody EmployeeYearCostBudgetSaveReqVO updateReqVO) {
|
||||
employeeYearCostBudgetService.updateEmployeeYearCostBudget(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除员工年度成本预算")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:delete')")
|
||||
public CommonResult<Boolean> deleteEmployeeYearCostBudget(@RequestParam("id") Long id) {
|
||||
employeeYearCostBudgetService.deleteEmployeeYearCostBudget(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得员工年度成本预算详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:query')")
|
||||
public CommonResult<EmployeeYearCostBudgetRespVO> getEmployeeYearCostBudget(@RequestParam("id") Long id) {
|
||||
return success(employeeYearCostBudgetService.getEmployeeYearCostBudget(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得员工年度成本预算分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:query')")
|
||||
public CommonResult<PageResult<EmployeeYearCostBudgetRespVO>> getEmployeeYearCostBudgetPage(
|
||||
@Valid EmployeeYearCostBudgetPageReqVO pageReqVO) {
|
||||
return success(employeeYearCostBudgetService.getEmployeeYearCostBudgetPage(pageReqVO));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 员工年度成本预算按年度生成 Request VO")
|
||||
@Data
|
||||
public class EmployeeYearCostBudgetGenerateReqVO {
|
||||
|
||||
@Schema(description = "预算年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "预算年度不能为空")
|
||||
private Integer budgetYear;
|
||||
|
||||
@Schema(description = "默认预计发生成本", example = "0")
|
||||
@DecimalMin(value = "0", message = "默认预计发生成本不能小于 0")
|
||||
private BigDecimal expectedCostAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 员工年度成本预算按年度生成 Response VO")
|
||||
@Data
|
||||
public class EmployeeYearCostBudgetGenerateRespVO {
|
||||
|
||||
@Schema(description = "预算年度")
|
||||
private Integer budgetYear;
|
||||
|
||||
@Schema(description = "已启用员工数量")
|
||||
private Integer totalEnabledEmployeeCount;
|
||||
|
||||
@Schema(description = "本次新增预算记录数量")
|
||||
private Integer createdCount;
|
||||
|
||||
@Schema(description = "已存在并跳过的预算记录数量")
|
||||
private Integer skippedCount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - 员工年度成本预算分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmployeeYearCostBudgetPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名", example = "张")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "预算年度", example = "2026")
|
||||
private Integer budgetYear;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 员工年度成本预算 Response VO")
|
||||
@Data
|
||||
public class EmployeeYearCostBudgetRespVO {
|
||||
|
||||
@Schema(description = "记录 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工 ID")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "预算年度")
|
||||
private Integer budgetYear;
|
||||
|
||||
@Schema(description = "预计发生成本")
|
||||
private BigDecimal expectedCostAmount;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 员工年度成本预算新增/修改 Request VO")
|
||||
@Data
|
||||
public class EmployeeYearCostBudgetSaveReqVO {
|
||||
|
||||
@Schema(description = "记录 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "员工 ID 不能为空")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三")
|
||||
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "预算年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "预算年度不能为空")
|
||||
private Integer budgetYear;
|
||||
|
||||
@Schema(description = "预计发生成本", requiredMode = Schema.RequiredMode.REQUIRED, example = "100000")
|
||||
@NotNull(message = "预计发生成本不能为空")
|
||||
private BigDecimal expectedCostAmount;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 255, message = "备注长度不能超过 255 个字符")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput.vo.*;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.employeeyearleaderoutput.EmployeeYearLeaderOutputService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 年度所长考核产值")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/employee-year-leader-output")
|
||||
@Validated
|
||||
public class EmployeeYearLeaderOutputController {
|
||||
|
||||
@Resource
|
||||
private EmployeeYearLeaderOutputService employeeYearLeaderOutputService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建年度所长考核产值")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-leader-output:create')")
|
||||
public CommonResult<Long> createEmployeeYearLeaderOutput(
|
||||
@Valid @RequestBody EmployeeYearLeaderOutputSaveReqVO createReqVO) {
|
||||
return success(employeeYearLeaderOutputService.createEmployeeYearLeaderOutput(createReqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/generate")
|
||||
@Operation(summary = "按年度生成年度所长考核产值")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-leader-output:create')")
|
||||
public CommonResult<EmployeeYearLeaderOutputGenerateRespVO> generateEmployeeYearLeaderOutput(
|
||||
@Valid @RequestBody EmployeeYearLeaderOutputGenerateReqVO generateReqVO) {
|
||||
return success(employeeYearLeaderOutputService.generateEmployeeYearLeaderOutput(generateReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改年度所长考核产值")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-leader-output:update')")
|
||||
public CommonResult<Boolean> updateEmployeeYearLeaderOutput(
|
||||
@Valid @RequestBody EmployeeYearLeaderOutputSaveReqVO updateReqVO) {
|
||||
employeeYearLeaderOutputService.updateEmployeeYearLeaderOutput(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除年度所长考核产值")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-leader-output:delete')")
|
||||
public CommonResult<Boolean> deleteEmployeeYearLeaderOutput(@RequestParam("id") Long id) {
|
||||
employeeYearLeaderOutputService.deleteEmployeeYearLeaderOutput(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得年度所长考核产值详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-leader-output:query')")
|
||||
public CommonResult<EmployeeYearLeaderOutputRespVO> getEmployeeYearLeaderOutput(@RequestParam("id") Long id) {
|
||||
return success(employeeYearLeaderOutputService.getEmployeeYearLeaderOutput(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得年度所长考核产值分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:employee-year-leader-output:query')")
|
||||
public CommonResult<PageResult<EmployeeYearLeaderOutputRespVO>> getEmployeeYearLeaderOutputPage(
|
||||
@Valid EmployeeYearLeaderOutputPageReqVO pageReqVO) {
|
||||
return success(employeeYearLeaderOutputService.getEmployeeYearLeaderOutputPage(pageReqVO));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 年度所长考核产值按年度生成 Request VO")
|
||||
@Data
|
||||
public class EmployeeYearLeaderOutputGenerateReqVO {
|
||||
|
||||
@Schema(description = "产值年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "产值年度不能为空")
|
||||
private Integer outputYear;
|
||||
|
||||
@Schema(description = "默认所长考核产值", example = "0")
|
||||
@DecimalMin(value = "0", message = "默认所长考核产值不能小于 0")
|
||||
private BigDecimal leaderOutputAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 年度所长考核产值按年度生成 Response VO")
|
||||
@Data
|
||||
public class EmployeeYearLeaderOutputGenerateRespVO {
|
||||
|
||||
@Schema(description = "产值年度")
|
||||
private Integer outputYear;
|
||||
|
||||
@Schema(description = "已启用所长数量")
|
||||
private Integer totalEnabledLeaderCount;
|
||||
|
||||
@Schema(description = "本次新增记录数量")
|
||||
private Integer createdCount;
|
||||
|
||||
@Schema(description = "已存在并跳过的记录数量")
|
||||
private Integer skippedCount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - 年度所长考核产值分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmployeeYearLeaderOutputPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名", example = "张")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "产值年度", example = "2026")
|
||||
private Integer outputYear;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 年度所长考核产值 Response VO")
|
||||
@Data
|
||||
public class EmployeeYearLeaderOutputRespVO {
|
||||
|
||||
@Schema(description = "记录 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工 ID")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "产值年度")
|
||||
private Integer outputYear;
|
||||
|
||||
@Schema(description = "所长考核产值")
|
||||
private BigDecimal leaderOutputAmount;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearleaderoutput.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 年度所长考核产值新增/修改 Request VO")
|
||||
@Data
|
||||
public class EmployeeYearLeaderOutputSaveReqVO {
|
||||
|
||||
@Schema(description = "记录 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "员工 ID 不能为空")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三")
|
||||
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "产值年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "产值年度不能为空")
|
||||
private Integer outputYear;
|
||||
|
||||
@Schema(description = "所长考核产值", requiredMode = Schema.RequiredMode.REQUIRED, example = "100000")
|
||||
@NotNull(message = "所长考核产值不能为空")
|
||||
private BigDecimal leaderOutputAmount;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 255, message = "备注长度不能超过 255 个字符")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.office;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.office.OfficeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 专业所")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/office")
|
||||
@Validated
|
||||
public class OfficeController {
|
||||
|
||||
@Resource
|
||||
private OfficeService officeService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建专业所")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:office:create')")
|
||||
public CommonResult<Long> createOffice(@Valid @RequestBody OfficeSaveReqVO createReqVO) {
|
||||
return success(officeService.createOffice(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改专业所")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:office:update')")
|
||||
public CommonResult<Boolean> updateOffice(@Valid @RequestBody OfficeSaveReqVO updateReqVO) {
|
||||
officeService.updateOffice(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除专业所")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:office:delete')")
|
||||
public CommonResult<Boolean> deleteOffice(@RequestParam("id") Long id) {
|
||||
officeService.deleteOffice(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得专业所详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:office:query')")
|
||||
public CommonResult<OfficeRespVO> getOffice(@RequestParam("id") Long id) {
|
||||
return success(officeService.getOffice(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得专业所分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:office:query')")
|
||||
public CommonResult<PageResult<OfficeRespVO>> getOfficePage(@Valid OfficePageReqVO pageReqVO) {
|
||||
return success(officeService.getOfficePage(pageReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获得专业所精简列表")
|
||||
public CommonResult<List<OfficeSimpleRespVO>> getOfficeSimpleList() {
|
||||
return success(officeService.getOfficeSimpleList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - 专业所分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OfficePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "专业所名称", example = "建筑")
|
||||
private String officeName;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 专业所 Response VO")
|
||||
@Data
|
||||
public class OfficeRespVO {
|
||||
|
||||
@Schema(description = "专业所 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "专业所名称", example = "建筑一所")
|
||||
private String officeName;
|
||||
|
||||
@Schema(description = "专业所编码", example = "JZY")
|
||||
private String officeCode;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Schema(description = "管理后台 - 专业所新增/修改 Request VO")
|
||||
@Data
|
||||
public class OfficeSaveReqVO {
|
||||
|
||||
@Schema(description = "专业所 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "专业所名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑一所")
|
||||
@NotBlank(message = "专业所名称不能为空")
|
||||
@Size(max = 100, message = "专业所名称长度不能超过 100 个字符")
|
||||
private String officeName;
|
||||
|
||||
@Schema(description = "专业所编码", example = "JZY")
|
||||
@Size(max = 50, message = "专业所编码长度不能超过 50 个字符")
|
||||
private String officeCode;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "备注", example = "默认专业所")
|
||||
@Size(max = 255, message = "备注长度不能超过 255 个字符")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 专业所精简 Response VO")
|
||||
@Data
|
||||
public class OfficeSimpleRespVO {
|
||||
|
||||
@Schema(description = "专业所 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "专业所名称", example = "建筑一所")
|
||||
private String officeName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitPlanningDetailRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutputSplitSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planning.ProjectPlanningService;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planningquarter.ProjectPlanningQuarterService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 特建投页面4拆分比例")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/output-split")
|
||||
@Validated
|
||||
public class ProjectOutputSplitController {
|
||||
|
||||
private static final int RATIO_SCALE = 4;
|
||||
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
|
||||
@Resource
|
||||
private ProjectOutputSplitService projectOutputSplitService;
|
||||
@Resource
|
||||
private ProjectPlanningService projectPlanningService;
|
||||
@Resource
|
||||
private ProjectPlanningQuarterService projectPlanningQuarterService;
|
||||
|
||||
@GetMapping("/get-by-planning")
|
||||
@Operation(summary = "根据合约规划获得页面4拆分比例")
|
||||
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:output-split:query')")
|
||||
public CommonResult<ProjectOutputSplitRespVO> getProjectOutputSplitByPlanningId(@RequestParam("planningId") Long planningId) {
|
||||
return success(projectOutputSplitService.getProjectOutputSplit(planningId));
|
||||
}
|
||||
|
||||
@GetMapping("/planning-detail")
|
||||
@Operation(summary = "获得页面4合约规划分配聚合详情")
|
||||
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query') && @ss.hasPermission('tjt:output-split:query') && @ss.hasPermission('tjt:planning-quarter:query')")
|
||||
public CommonResult<ProjectOutputSplitPlanningDetailRespVO> getProjectOutputSplitPlanningDetail(
|
||||
@RequestParam("planningId") Long planningId) {
|
||||
ProjectPlanningDO planning = projectPlanningService.getProjectPlanning(planningId);
|
||||
if (planning == null) {
|
||||
return success(null);
|
||||
}
|
||||
ProjectOutputSplitPlanningDetailRespVO respVO = new ProjectOutputSplitPlanningDetailRespVO();
|
||||
Map<Long, BigDecimal> allocatedAmountMap =
|
||||
projectPlanningService.getAllocatedAmountMap(Collections.singleton(planningId));
|
||||
respVO.setPlanning(BeanUtils.toBean(planning, ProjectPlanningRespVO.class,
|
||||
planningRespVO -> fillDistributionSummary(planningRespVO, allocatedAmountMap)));
|
||||
respVO.setOutputSplit(projectOutputSplitService.getProjectOutputSplit(planningId));
|
||||
|
||||
List<ProjectPlanningQuarterDO> quarterList =
|
||||
projectPlanningQuarterService.getProjectPlanningQuarterListByPlanningId(planningId);
|
||||
respVO.setQuarters(BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class));
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@PutMapping("/save")
|
||||
@Operation(summary = "保存页面4拆分比例")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:output-split:update')")
|
||||
public CommonResult<Long> saveProjectOutputSplit(@Valid @RequestBody ProjectOutputSplitSaveReqVO reqVO) {
|
||||
return success(projectOutputSplitService.saveProjectOutputSplit(reqVO));
|
||||
}
|
||||
|
||||
private void fillDistributionSummary(ProjectPlanningRespVO respVO, Map<Long, BigDecimal> allocatedAmountMap) {
|
||||
BigDecimal allocatedRatio = allocatedAmountMap.getOrDefault(respVO.getId(), ZERO_RATIO);
|
||||
BigDecimal totalDistributionAmount = respVO.getTotalDistributionAmount() == null
|
||||
? ZERO_RATIO : respVO.getTotalDistributionAmount().setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
BigDecimal allocatedAmount = totalDistributionAmount.multiply(allocatedRatio)
|
||||
.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
respVO.setAllocatedAmount(allocatedAmount);
|
||||
respVO.setPendingAmount(totalDistributionAmount.subtract(allocatedAmount)
|
||||
.setScale(RATIO_SCALE, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 页面4合约规划分配聚合详情 Response VO")
|
||||
@Data
|
||||
public class ProjectOutputSplitPlanningDetailRespVO {
|
||||
|
||||
@Schema(description = "合约规划详情")
|
||||
private ProjectPlanningRespVO planning;
|
||||
|
||||
@Schema(description = "页面4拆分比例")
|
||||
private ProjectOutputSplitRespVO outputSplit;
|
||||
|
||||
@Schema(description = "季度分配列表")
|
||||
private List<ProjectPlanningQuarterRespVO> quarters = Collections.emptyList();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 页面4拆分比例 Response VO")
|
||||
@Data
|
||||
public class ProjectOutputSplitRespVO {
|
||||
|
||||
@Schema(description = "页面4拆分 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "合约规划 ID", example = "1")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目任务包")
|
||||
private String planningContent;
|
||||
|
||||
@Schema(description = "年度")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "考核产值")
|
||||
private BigDecimal assessmentOutputValue;
|
||||
|
||||
@Schema(description = "项目经理")
|
||||
private String projectManagerName;
|
||||
|
||||
@Schema(description = "工程负责人")
|
||||
private String engineeringLeaderName;
|
||||
|
||||
@Schema(description = "项目经理/工程负责人")
|
||||
private String projectLeadName;
|
||||
|
||||
@Schema(description = "项目经理/工程负责人合并比例")
|
||||
private BigDecimal projectLeadRatio;
|
||||
|
||||
@Schema(description = "项目经理/工程负责人合并金额")
|
||||
private BigDecimal projectLeadAmount;
|
||||
|
||||
@Schema(description = "专业所比例")
|
||||
private BigDecimal officeRatio;
|
||||
|
||||
@Schema(description = "专业所金额")
|
||||
private BigDecimal officeAmount;
|
||||
|
||||
@Schema(description = "建筑专业比例")
|
||||
private BigDecimal archRatio;
|
||||
|
||||
@Schema(description = "建筑专业金额")
|
||||
private BigDecimal archAmount;
|
||||
|
||||
@Schema(description = "装修专业比例")
|
||||
private BigDecimal decorRatio;
|
||||
|
||||
@Schema(description = "装修专业金额")
|
||||
private BigDecimal decorAmount;
|
||||
|
||||
@Schema(description = "结构专业比例")
|
||||
private BigDecimal structRatio;
|
||||
|
||||
@Schema(description = "结构专业金额")
|
||||
private BigDecimal structAmount;
|
||||
|
||||
@Schema(description = "水专业比例")
|
||||
private BigDecimal waterRatio;
|
||||
|
||||
@Schema(description = "水专业金额")
|
||||
private BigDecimal waterAmount;
|
||||
|
||||
@Schema(description = "电气专业比例")
|
||||
private BigDecimal elecRatio;
|
||||
|
||||
@Schema(description = "电气专业金额")
|
||||
private BigDecimal elecAmount;
|
||||
|
||||
@Schema(description = "暖通专业比例")
|
||||
private BigDecimal hvacRatio;
|
||||
|
||||
@Schema(description = "暖通专业金额")
|
||||
private BigDecimal hvacAmount;
|
||||
|
||||
@Schema(description = "数字化设计专业比例")
|
||||
private BigDecimal digitalRatio;
|
||||
|
||||
@Schema(description = "数字化设计专业金额")
|
||||
private BigDecimal digitalAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 页面4拆分比例新增/修改 Request VO")
|
||||
@Data
|
||||
public class ProjectOutputSplitSaveReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "项目经理/工程负责人合并比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0550")
|
||||
@NotNull(message = "项目经理/工程负责人合并比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "projectLeadRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "projectLeadRatio must be <= 1")
|
||||
private BigDecimal projectLeadRatio;
|
||||
|
||||
@Schema(description = "专业所比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.9500")
|
||||
@NotNull(message = "专业所比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "officeRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "officeRatio must be <= 1")
|
||||
private BigDecimal officeRatio;
|
||||
|
||||
@Schema(description = "建筑专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000")
|
||||
@NotNull(message = "建筑专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "archRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "archRatio must be <= 1")
|
||||
private BigDecimal archRatio;
|
||||
|
||||
@Schema(description = "装修专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0000")
|
||||
@NotNull(message = "装修专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "decorRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "decorRatio must be <= 1")
|
||||
private BigDecimal decorRatio;
|
||||
|
||||
@Schema(description = "结构专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.2000")
|
||||
@NotNull(message = "结构专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "structRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "structRatio must be <= 1")
|
||||
private BigDecimal structRatio;
|
||||
|
||||
@Schema(description = "水专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000")
|
||||
@NotNull(message = "水专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "waterRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "waterRatio must be <= 1")
|
||||
private BigDecimal waterRatio;
|
||||
|
||||
@Schema(description = "电气专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000")
|
||||
@NotNull(message = "电气专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "elecRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "elecRatio must be <= 1")
|
||||
private BigDecimal elecRatio;
|
||||
|
||||
@Schema(description = "暖通专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
|
||||
@NotNull(message = "暖通专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "hvacRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "hvacRatio must be <= 1")
|
||||
private BigDecimal hvacRatio;
|
||||
|
||||
@Schema(description = "数字化设计专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
|
||||
@NotNull(message = "数字化设计专业比例不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "digitalRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "digitalRatio must be <= 1")
|
||||
private BigDecimal digitalRatio;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planning;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningOutputEditDetailRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planning.ProjectPlanningService;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 特建投合约规划")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/planning")
|
||||
@Validated
|
||||
public class ProjectPlanningController {
|
||||
|
||||
private static final int RATIO_SCALE = 4;
|
||||
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
|
||||
@Resource
|
||||
private ProjectPlanningService projectPlanningService;
|
||||
@Resource
|
||||
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建合约规划")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:create')")
|
||||
public CommonResult<Long> createProjectPlanning(@Valid @RequestBody ProjectPlanningSaveReqVO createReqVO) {
|
||||
return success(projectPlanningService.createProjectPlanning(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改合约规划")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
|
||||
public CommonResult<Boolean> updateProjectPlanning(@Valid @RequestBody ProjectPlanningSaveReqVO updateReqVO) {
|
||||
projectPlanningService.updateProjectPlanning(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除合约规划")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:delete')")
|
||||
public CommonResult<Boolean> deleteProjectPlanning(@RequestParam("id") Long id) {
|
||||
projectPlanningService.deleteProjectPlanning(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Operation(summary = "批量删除合约规划")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:delete')")
|
||||
public CommonResult<Boolean> deleteProjectPlanningList(@RequestParam("ids") List<Long> ids) {
|
||||
projectPlanningService.deleteProjectPlanningList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得合约规划分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
|
||||
public CommonResult<PageResult<ProjectPlanningRespVO>> getProjectPlanningPage(@Valid ProjectPlanningPageReqVO pageReqVO) {
|
||||
PageResult<ProjectPlanningDO> pageResult = projectPlanningService.getProjectPlanningPage(pageReqVO);
|
||||
Map<Long, BigDecimal> allocatedAmountMap = projectPlanningService.getAllocatedAmountMap(
|
||||
CollectionUtils.convertSet(pageResult.getList(), ProjectPlanningDO::getId));
|
||||
return success(BeanUtils.toBean(pageResult, ProjectPlanningRespVO.class,
|
||||
respVO -> fillDistributionSummary(respVO, allocatedAmountMap)));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得合约规划详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
|
||||
public CommonResult<ProjectPlanningRespVO> getProjectPlanning(@RequestParam("id") Long id) {
|
||||
ProjectPlanningDO planning = projectPlanningService.getProjectPlanning(id);
|
||||
if (planning == null) {
|
||||
return success(null);
|
||||
}
|
||||
Map<Long, BigDecimal> allocatedAmountMap = projectPlanningService.getAllocatedAmountMap(
|
||||
Collections.singleton(planning.getId()));
|
||||
return success(BeanUtils.toBean(planning, ProjectPlanningRespVO.class,
|
||||
respVO -> fillDistributionSummary(respVO, allocatedAmountMap)));
|
||||
}
|
||||
|
||||
@GetMapping("/output-edit-detail")
|
||||
@Operation(summary = "获得合约规划测算参数编辑详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
|
||||
public CommonResult<ProjectPlanningOutputEditDetailRespVO> getProjectPlanningOutputEditDetail(@RequestParam("id") Long id) {
|
||||
ProjectPlanningDO planning = projectPlanningService.getProjectPlanning(id);
|
||||
if (planning == null) {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
Map<Long, BigDecimal> allocatedAmountMap = projectPlanningService.getAllocatedAmountMap(
|
||||
Collections.singleton(planning.getId()));
|
||||
ProjectPlanningOutputEditDetailRespVO respVO = new ProjectPlanningOutputEditDetailRespVO();
|
||||
respVO.setPlanning(BeanUtils.toBean(planning, ProjectPlanningRespVO.class,
|
||||
planningRespVO -> fillDistributionSummary(planningRespVO, allocatedAmountMap)));
|
||||
if (ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
|
||||
planning.getOwnershipType(), planning.getCalculationMethod())) {
|
||||
List<ProjectPlanningGuideDetailDO> guideDetails =
|
||||
projectPlanningGuideDetailService.getProjectPlanningGuideDetailListByPlanningId(id);
|
||||
respVO.setGuideDetails(BeanUtils.toBean(guideDetails, ProjectPlanningGuideDetailRespVO.class));
|
||||
}
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-project")
|
||||
@Operation(summary = "根据项目获得合约规划列表")
|
||||
@Parameter(name = "projectId", description = "项目 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
|
||||
public CommonResult<List<ProjectPlanningRespVO>> getProjectPlanningListByProjectId(@RequestParam("projectId") Long projectId) {
|
||||
List<ProjectPlanningDO> list = projectPlanningService.getProjectPlanningListByProjectId(projectId);
|
||||
Map<Long, BigDecimal> allocatedAmountMap = projectPlanningService.getAllocatedAmountMap(
|
||||
CollectionUtils.convertSet(list, ProjectPlanningDO::getId));
|
||||
return success(BeanUtils.toBean(list, ProjectPlanningRespVO.class,
|
||||
respVO -> fillDistributionSummary(respVO, allocatedAmountMap)));
|
||||
}
|
||||
|
||||
private void fillDistributionSummary(ProjectPlanningRespVO respVO, Map<Long, BigDecimal> allocatedAmountMap) {
|
||||
BigDecimal allocatedRatio = allocatedAmountMap.getOrDefault(respVO.getId(), ZERO_RATIO);
|
||||
BigDecimal totalDistributionAmount = respVO.getTotalDistributionAmount() == null
|
||||
? ZERO_RATIO : respVO.getTotalDistributionAmount().setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
BigDecimal allocatedAmount = totalDistributionAmount.multiply(allocatedRatio)
|
||||
.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
respVO.setAllocatedAmount(allocatedAmount);
|
||||
respVO.setPendingAmount(totalDistributionAmount.subtract(allocatedAmount)
|
||||
.setScale(RATIO_SCALE, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划测算参数编辑聚合详情 Response VO")
|
||||
@Data
|
||||
public class ProjectPlanningOutputEditDetailRespVO {
|
||||
|
||||
@Schema(description = "合约规划详情")
|
||||
private ProjectPlanningRespVO planning;
|
||||
|
||||
@Schema(description = "指导价法明细列表,仅专业所 + 指导价法场景返回")
|
||||
private List<ProjectPlanningGuideDetailRespVO> guideDetails = Collections.emptyList();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectPlanningPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "归属类型", example = "专业所")
|
||||
private String ownershipType;
|
||||
|
||||
@Schema(description = "产值计算方式", example = "指导价法")
|
||||
private String calculationMethod;
|
||||
|
||||
@Schema(description = "项目任务包,模糊匹配", example = "建筑")
|
||||
private String planningContent;
|
||||
|
||||
@Schema(description = "开始年度", example = "2026")
|
||||
private Integer planningStartYear;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划 Response VO")
|
||||
@Data
|
||||
public class ProjectPlanningRespVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "归属类型")
|
||||
private String ownershipType;
|
||||
|
||||
@Schema(description = "产值计算方式")
|
||||
private String calculationMethod;
|
||||
|
||||
@Schema(description = "项目任务包")
|
||||
private String planningContent;
|
||||
|
||||
@Schema(description = "分项合同产值")
|
||||
private BigDecimal planningAmount;
|
||||
|
||||
@Schema(description = "合同产值数量")
|
||||
private BigDecimal contractValueQuantity;
|
||||
|
||||
@Schema(description = "合同产值单价")
|
||||
private BigDecimal contractValueUnitPrice;
|
||||
|
||||
@Schema(description = "管理费率")
|
||||
private BigDecimal managementFeeRate;
|
||||
|
||||
@Schema(description = "管理费")
|
||||
private BigDecimal managementFee;
|
||||
|
||||
@Schema(description = "增值税率")
|
||||
private BigDecimal vatRate;
|
||||
|
||||
@Schema(description = "增值税")
|
||||
private BigDecimal vatAmount;
|
||||
|
||||
@Schema(description = "项目预算产值")
|
||||
private BigDecimal projectBudgetOutputValue;
|
||||
|
||||
@Schema(description = "意向实施团队")
|
||||
private String implementationTeam;
|
||||
|
||||
@Schema(description = "开始年度")
|
||||
private Integer planningStartYear;
|
||||
|
||||
@Schema(description = "面积")
|
||||
private BigDecimal planningArea;
|
||||
|
||||
@Schema(description = "设计阶段")
|
||||
private String designStage;
|
||||
|
||||
@Schema(description = "本次设计阶段比例")
|
||||
private BigDecimal currentDesignStageRatio;
|
||||
|
||||
@Schema(description = "审核审定是否外包")
|
||||
private Boolean reviewOutsourceFlag;
|
||||
|
||||
@Schema(description = "审核审定占比")
|
||||
private BigDecimal reviewOutsourceRatio;
|
||||
|
||||
@Schema(description = "总分配比例")
|
||||
private BigDecimal totalDistributionAmount;
|
||||
|
||||
@Schema(description = "已分配比例")
|
||||
private BigDecimal allocatedAmount;
|
||||
|
||||
@Schema(description = "待分配比例")
|
||||
private BigDecimal pendingAmount;
|
||||
|
||||
@Schema(description = "提取进度备注")
|
||||
private String progressRemark;
|
||||
|
||||
@Schema(description = "楼栋数或户型数")
|
||||
private Integer buildingOrUnitCount;
|
||||
|
||||
@Schema(description = "套图系数")
|
||||
private BigDecimal drawingSetFactor;
|
||||
|
||||
@Schema(description = "规模系数")
|
||||
private BigDecimal scaleFactor;
|
||||
|
||||
@Schema(description = "修改系数")
|
||||
private BigDecimal modificationFactor;
|
||||
|
||||
@Schema(description = "复杂系数")
|
||||
private BigDecimal complexityFactor;
|
||||
|
||||
@Schema(description = "内部指导单价(元/m²)")
|
||||
private BigDecimal internalGuidanceUnitPrice;
|
||||
|
||||
@Schema(description = "虚拟产值计算方式")
|
||||
private String virtualCalculationMethod;
|
||||
|
||||
@Schema(description = "工日")
|
||||
private BigDecimal workingDayCount;
|
||||
|
||||
@Schema(description = "工日单价")
|
||||
private BigDecimal workingDayUnitPrice;
|
||||
|
||||
@Schema(description = "指导单价")
|
||||
private BigDecimal guidanceUnitPrice;
|
||||
|
||||
@Schema(description = "指导总价")
|
||||
private BigDecimal guidanceTotalPrice;
|
||||
|
||||
@Schema(description = "产值计算比例")
|
||||
private BigDecimal calculationRatio;
|
||||
|
||||
@Schema(description = "合同单价(元/m²)")
|
||||
private BigDecimal contractUnitPrice;
|
||||
|
||||
@Schema(description = "合计调整系数")
|
||||
private BigDecimal totalAdjustmentFactor;
|
||||
|
||||
@Schema(description = "考核面积")
|
||||
private BigDecimal assessmentArea;
|
||||
|
||||
@Schema(description = "虚拟产值")
|
||||
private BigDecimal virtualOutputValue;
|
||||
|
||||
@Schema(description = "考核产值")
|
||||
private BigDecimal assessmentOutputValue;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划新增/修改 Request VO")
|
||||
@Data
|
||||
public class ProjectPlanningSaveReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "排序", example = "0")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "归属类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "专业所")
|
||||
@NotBlank(message = "归属类型不能为空")
|
||||
@Size(max = 20, message = "归属类型长度不能超过 20 个字符")
|
||||
private String ownershipType;
|
||||
|
||||
@Schema(description = "产值计算方式,页面 2 维护", example = "指导价法")
|
||||
@Size(max = 30, message = "产值计算方式长度不能超过 30 个字符")
|
||||
private String calculationMethod;
|
||||
|
||||
@Schema(description = "项目任务包", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑设计")
|
||||
@NotBlank(message = "项目任务包不能为空")
|
||||
@Size(max = 255, message = "项目任务包长度不能超过 255 个字符")
|
||||
private String planningContent;
|
||||
|
||||
@Schema(description = "分项合同产值,系统根据合同产值数量和合同产值单价自动计算", example = "500000")
|
||||
private BigDecimal planningAmount;
|
||||
|
||||
@Schema(description = "合同产值数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合同产值数量不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "contractValueQuantity must be >= 0")
|
||||
private BigDecimal contractValueQuantity;
|
||||
|
||||
@Schema(description = "合同产值单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "500000")
|
||||
@NotNull(message = "合同产值单价不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "contractValueUnitPrice must be >= 0")
|
||||
private BigDecimal contractValueUnitPrice;
|
||||
|
||||
@Schema(description = "管理费率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
|
||||
@NotNull(message = "管理费率不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "managementFeeRate must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "managementFeeRate must be <= 1")
|
||||
private BigDecimal managementFeeRate;
|
||||
|
||||
@Schema(description = "增值税率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0600")
|
||||
@NotNull(message = "增值税率不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "vatRate must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "vatRate must be <= 1")
|
||||
private BigDecimal vatRate;
|
||||
|
||||
@Schema(description = "意向实施团队", example = "建筑一所")
|
||||
@Size(max = 100, message = "意向实施团队长度不能超过 100 个字符")
|
||||
private String implementationTeam;
|
||||
|
||||
@Schema(description = "开始年度", example = "2026")
|
||||
private Integer planningStartYear;
|
||||
|
||||
@Schema(description = "面积", example = "30000")
|
||||
private BigDecimal planningArea;
|
||||
|
||||
@Schema(description = "设计阶段", example = "方案设计")
|
||||
@Size(max = 50, message = "设计阶段长度不能超过 50 个字符")
|
||||
private String designStage;
|
||||
|
||||
@Schema(description = "本次设计阶段比例", example = "0.3000")
|
||||
@DecimalMin(value = "0.0000", message = "currentDesignStageRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "currentDesignStageRatio must be <= 1")
|
||||
private BigDecimal currentDesignStageRatio;
|
||||
|
||||
@Schema(description = "审核审定是否外包", example = "false")
|
||||
private Boolean reviewOutsourceFlag;
|
||||
|
||||
@Schema(description = "审核审定占比", example = "0.0600")
|
||||
@DecimalMin(value = "0.0000", message = "reviewOutsourceRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "reviewOutsourceRatio must be <= 1")
|
||||
private BigDecimal reviewOutsourceRatio;
|
||||
|
||||
@Schema(description = "总分配比例", example = "1.0000")
|
||||
@DecimalMin(value = "0.0000", message = "totalDistributionAmount must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "totalDistributionAmount must be <= 1")
|
||||
private BigDecimal totalDistributionAmount;
|
||||
|
||||
@Schema(description = "提取进度备注", example = "Q1 提取安排")
|
||||
@Size(max = 500, message = "提取进度备注长度不能超过 500 个字符")
|
||||
private String progressRemark;
|
||||
|
||||
@Schema(description = "楼栋数或户型数", example = "10")
|
||||
private Integer buildingOrUnitCount;
|
||||
|
||||
@Schema(description = "套图系数,保留两位小数", example = "1.00")
|
||||
private BigDecimal drawingSetFactor;
|
||||
|
||||
@Schema(description = "规模系数,保留两位小数", example = "1.00")
|
||||
private BigDecimal scaleFactor;
|
||||
|
||||
@Schema(description = "修改系数,保留两位小数", example = "1.00")
|
||||
private BigDecimal modificationFactor;
|
||||
|
||||
@Schema(description = "复杂系数/复杂等级,按比例值存储(100%=1.0000)", example = "1.0000")
|
||||
@DecimalMin(value = "0.0000", message = "complexityFactor must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "complexityFactor must be <= 1")
|
||||
private BigDecimal complexityFactor;
|
||||
|
||||
@Schema(description = "内部指导单价(元/m²)", example = "80.00")
|
||||
private BigDecimal internalGuidanceUnitPrice;
|
||||
|
||||
@Schema(description = "虚拟产值计算方式:指导单价法/指导总价法/工日法", example = "工日法")
|
||||
@Size(max = 30, message = "虚拟产值计算方式长度不能超过 30 个字符")
|
||||
private String virtualCalculationMethod;
|
||||
|
||||
@Schema(description = "工日", example = "100")
|
||||
private BigDecimal workingDayCount;
|
||||
|
||||
@Schema(description = "工日单价", example = "1000.00")
|
||||
private BigDecimal workingDayUnitPrice;
|
||||
|
||||
@Schema(description = "指导单价", example = "88.00")
|
||||
private BigDecimal guidanceUnitPrice;
|
||||
|
||||
@Schema(description = "指导总价", example = "880000.00")
|
||||
private BigDecimal guidanceTotalPrice;
|
||||
|
||||
@Schema(description = "产值计算比例", example = "0.0800")
|
||||
@DecimalMin(value = "0.0000", message = "calculationRatio must be >= 0")
|
||||
@DecimalMax(value = "1.0000", message = "calculationRatio must be <= 1")
|
||||
private BigDecimal calculationRatio;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 合约规划指导价法明细")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/planning-guide-detail")
|
||||
@Validated
|
||||
public class ProjectPlanningGuideDetailController {
|
||||
|
||||
@Resource
|
||||
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
|
||||
|
||||
@GetMapping("/list-by-planning")
|
||||
@Operation(summary = "根据合约规划获得指导价法明细列表")
|
||||
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
|
||||
public CommonResult<List<ProjectPlanningGuideDetailRespVO>> getProjectPlanningGuideDetailListByPlanningId(
|
||||
@RequestParam("planningId") Long planningId) {
|
||||
List<ProjectPlanningGuideDetailDO> list =
|
||||
projectPlanningGuideDetailService.getProjectPlanningGuideDetailListByPlanningId(planningId);
|
||||
return success(BeanUtils.toBean(list, ProjectPlanningGuideDetailRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/batch-save")
|
||||
@Operation(summary = "批量保存指导价法明细")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
|
||||
public CommonResult<Boolean> batchSaveProjectPlanningGuideDetail(
|
||||
@Valid @RequestBody ProjectPlanningGuideDetailBatchSaveReqVO reqVO) {
|
||||
projectPlanningGuideDetailService.batchSaveProjectPlanningGuideDetail(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除指导价法明细")
|
||||
@Parameter(name = "id", description = "明细 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
|
||||
public CommonResult<Boolean> deleteProjectPlanningGuideDetail(@RequestParam("id") Long id) {
|
||||
projectPlanningGuideDetailService.deleteProjectPlanningGuideDetail(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划指导价法明细批量保存 Request VO")
|
||||
@Data
|
||||
public class ProjectPlanningGuideDetailBatchSaveReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "指导价法明细列表")
|
||||
@Valid
|
||||
private List<ProjectPlanningGuideDetailSaveReqVO> details;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划指导价法明细 Response VO")
|
||||
@Data
|
||||
public class ProjectPlanningGuideDetailRespVO {
|
||||
|
||||
@Schema(description = "明细 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "合约规划 ID", example = "1")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "设计部位")
|
||||
private String designPart;
|
||||
|
||||
@Schema(description = "设计内容/设计类型")
|
||||
private String buildingType;
|
||||
|
||||
@Schema(description = "设计面积")
|
||||
private BigDecimal designArea;
|
||||
|
||||
@Schema(description = "内部指导单价(元/m²)")
|
||||
private BigDecimal internalGuidanceUnitPrice;
|
||||
|
||||
@Schema(description = "楼栋数/户型数")
|
||||
private Integer buildingOrUnitCount;
|
||||
|
||||
@Schema(description = "套图系数")
|
||||
private BigDecimal drawingSetFactor;
|
||||
|
||||
@Schema(description = "规模系数")
|
||||
private BigDecimal scaleFactor;
|
||||
|
||||
@Schema(description = "修改系数")
|
||||
private BigDecimal modificationFactor;
|
||||
|
||||
@Schema(description = "复杂系数")
|
||||
private BigDecimal complexityFactor;
|
||||
|
||||
@Schema(description = "合计调整系数")
|
||||
private BigDecimal totalAdjustmentFactor;
|
||||
|
||||
@Schema(description = "设计占比")
|
||||
private BigDecimal designRatio;
|
||||
|
||||
@Schema(description = "考核面积")
|
||||
private BigDecimal assessmentArea;
|
||||
|
||||
@Schema(description = "考核产值")
|
||||
private BigDecimal assessmentOutputValue;
|
||||
|
||||
@Schema(description = "排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划指导价法明细新增/修改 Request VO")
|
||||
@Data
|
||||
public class ProjectPlanningGuideDetailSaveReqVO {
|
||||
|
||||
@Schema(description = "明细 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "设计部位", requiredMode = Schema.RequiredMode.REQUIRED, example = "地上部分")
|
||||
@NotBlank(message = "设计部位不能为空")
|
||||
@Size(max = 20, message = "设计部位长度不能超过 20 个字符")
|
||||
private String designPart;
|
||||
|
||||
@Schema(description = "设计内容/设计类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "住宅")
|
||||
@NotBlank(message = "设计内容/设计类型不能为空")
|
||||
@Size(max = 100, message = "设计内容/设计类型长度不能超过 100 个字符")
|
||||
private String buildingType;
|
||||
|
||||
@Schema(description = "设计面积", requiredMode = Schema.RequiredMode.REQUIRED, example = "12000")
|
||||
@NotNull(message = "设计面积不能为空")
|
||||
@DecimalMin(value = "0", message = "designArea must be >= 0")
|
||||
private BigDecimal designArea;
|
||||
|
||||
@Schema(description = "内部指导单价(元/m²)", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
|
||||
@NotNull(message = "内部指导单价不能为空")
|
||||
@DecimalMin(value = "0", message = "internalGuidanceUnitPrice must be >= 0")
|
||||
private BigDecimal internalGuidanceUnitPrice;
|
||||
|
||||
@Schema(description = "楼栋数/户型数", example = "10")
|
||||
private Integer buildingOrUnitCount;
|
||||
|
||||
@Schema(description = "套图系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
|
||||
@NotNull(message = "套图系数不能为空")
|
||||
@DecimalMin(value = "0", message = "drawingSetFactor must be >= 0")
|
||||
private BigDecimal drawingSetFactor;
|
||||
|
||||
@Schema(description = "规模系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
|
||||
@NotNull(message = "规模系数不能为空")
|
||||
@DecimalMin(value = "0", message = "scaleFactor must be >= 0")
|
||||
private BigDecimal scaleFactor;
|
||||
|
||||
@Schema(description = "修改系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
|
||||
@NotNull(message = "修改系数不能为空")
|
||||
@DecimalMin(value = "0", message = "modificationFactor must be >= 0")
|
||||
private BigDecimal modificationFactor;
|
||||
|
||||
@Schema(description = "复杂系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
|
||||
@NotNull(message = "复杂系数不能为空")
|
||||
@DecimalMin(value = "0", message = "complexityFactor must be >= 0")
|
||||
private BigDecimal complexityFactor;
|
||||
|
||||
@Schema(description = "设计占比", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000")
|
||||
@NotNull(message = "设计占比不能为空")
|
||||
@DecimalMin(value = "0", message = "designRatio must be >= 0")
|
||||
@DecimalMax(value = "1", message = "designRatio must be <= 1")
|
||||
private BigDecimal designRatio;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "备注", example = "样板房")
|
||||
@Size(max = 500, message = "备注长度不能超过 500 个字符")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterPlanningDetailRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo.ProjectPlanningQuarterSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planning.ProjectPlanningService;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.planningquarter.ProjectPlanningQuarterService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 特建投季度分配")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/planning-quarter")
|
||||
@Validated
|
||||
public class ProjectPlanningQuarterController {
|
||||
|
||||
private static final int RATIO_SCALE = 4;
|
||||
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
|
||||
@Resource
|
||||
private ProjectPlanningQuarterService projectPlanningQuarterService;
|
||||
@Resource
|
||||
private ProjectPlanningService projectPlanningService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建季度分配")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning-quarter:create')")
|
||||
public CommonResult<Long> createProjectPlanningQuarter(@Valid @RequestBody ProjectPlanningQuarterSaveReqVO createReqVO) {
|
||||
return success(projectPlanningQuarterService.createProjectPlanningQuarter(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改季度分配")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning-quarter:update')")
|
||||
public CommonResult<Boolean> updateProjectPlanningQuarter(@Valid @RequestBody ProjectPlanningQuarterSaveReqVO updateReqVO) {
|
||||
projectPlanningQuarterService.updateProjectPlanningQuarter(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除季度分配")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning-quarter:delete')")
|
||||
public CommonResult<Boolean> deleteProjectPlanningQuarter(@RequestParam("id") Long id) {
|
||||
projectPlanningQuarterService.deleteProjectPlanningQuarter(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Operation(summary = "批量删除季度分配")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning-quarter:delete')")
|
||||
public CommonResult<Boolean> deleteProjectPlanningQuarterList(@RequestParam("ids") List<Long> ids) {
|
||||
projectPlanningQuarterService.deleteProjectPlanningQuarterList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得季度分配详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning-quarter:query')")
|
||||
public CommonResult<ProjectPlanningQuarterRespVO> getProjectPlanningQuarter(@RequestParam("id") Long id) {
|
||||
ProjectPlanningQuarterDO quarter = projectPlanningQuarterService.getProjectPlanningQuarter(id);
|
||||
return success(BeanUtils.toBean(quarter, ProjectPlanningQuarterRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-planning")
|
||||
@Operation(summary = "根据合约规划获得季度分配列表")
|
||||
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning-quarter:query')")
|
||||
public CommonResult<List<ProjectPlanningQuarterRespVO>> getProjectPlanningQuarterListByPlanningId(
|
||||
@RequestParam("planningId") Long planningId) {
|
||||
List<ProjectPlanningQuarterDO> list = projectPlanningQuarterService.getProjectPlanningQuarterListByPlanningId(planningId);
|
||||
return success(BeanUtils.toBean(list, ProjectPlanningQuarterRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/planning-detail")
|
||||
@Operation(summary = "获得合约规划详情及季度分配列表")
|
||||
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:planning:query') && @ss.hasPermission('tjt:planning-quarter:query')")
|
||||
public CommonResult<ProjectPlanningQuarterPlanningDetailRespVO> getProjectPlanningQuarterPlanningDetail(
|
||||
@RequestParam("planningId") Long planningId) {
|
||||
ProjectPlanningDO planning = projectPlanningService.getProjectPlanning(planningId);
|
||||
if (planning == null) {
|
||||
return success(null);
|
||||
}
|
||||
List<ProjectPlanningQuarterDO> quarterList =
|
||||
projectPlanningQuarterService.getProjectPlanningQuarterListByPlanningId(planningId);
|
||||
|
||||
ProjectPlanningQuarterPlanningDetailRespVO respVO = new ProjectPlanningQuarterPlanningDetailRespVO();
|
||||
Map<Long, BigDecimal> allocatedAmountMap =
|
||||
projectPlanningService.getAllocatedAmountMap(Collections.singleton(planningId));
|
||||
respVO.setPlanning(BeanUtils.toBean(planning, ProjectPlanningRespVO.class,
|
||||
planningRespVO -> fillDistributionSummary(planningRespVO, allocatedAmountMap)));
|
||||
respVO.setQuarters(BeanUtils.toBean(quarterList, ProjectPlanningQuarterRespVO.class));
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
private void fillDistributionSummary(ProjectPlanningRespVO respVO, Map<Long, BigDecimal> allocatedAmountMap) {
|
||||
BigDecimal allocatedRatio = allocatedAmountMap.getOrDefault(respVO.getId(), ZERO_RATIO);
|
||||
BigDecimal totalDistributionAmount = respVO.getTotalDistributionAmount() == null
|
||||
? ZERO_RATIO : respVO.getTotalDistributionAmount().setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
BigDecimal allocatedAmount = totalDistributionAmount.multiply(allocatedRatio)
|
||||
.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
|
||||
respVO.setAllocatedAmount(allocatedAmount);
|
||||
respVO.setPendingAmount(totalDistributionAmount.subtract(allocatedAmount)
|
||||
.setScale(RATIO_SCALE, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo.ProjectPlanningRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 合约规划季度分配聚合详情 Response VO")
|
||||
@Data
|
||||
public class ProjectPlanningQuarterPlanningDetailRespVO {
|
||||
|
||||
@Schema(description = "合约规划详情")
|
||||
private ProjectPlanningRespVO planning;
|
||||
|
||||
@Schema(description = "季度分配列表")
|
||||
private List<ProjectPlanningQuarterRespVO> quarters = Collections.emptyList();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "Admin - project planning quarter Response VO")
|
||||
@Data
|
||||
public class ProjectPlanningQuarterRespVO {
|
||||
|
||||
@Schema(description = "Quarter distribution ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "Planning ID", example = "1")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "Distribution year")
|
||||
private Integer distributionYear;
|
||||
|
||||
@Schema(description = "Quarter number")
|
||||
private Integer quarterNo;
|
||||
|
||||
@Schema(description = "Distribution ratio")
|
||||
private BigDecimal distributionRatio;
|
||||
|
||||
@Schema(description = "Distribution amount")
|
||||
private BigDecimal distributionAmount;
|
||||
|
||||
@Schema(description = "Create time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningquarter.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "Admin - project planning quarter save Request VO")
|
||||
@Data
|
||||
public class ProjectPlanningQuarterSaveReqVO {
|
||||
|
||||
@Schema(description = "Quarter distribution ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "Planning ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "planningId cannot be null")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "Distribution year", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "distributionYear cannot be null")
|
||||
private Integer distributionYear;
|
||||
|
||||
@Schema(description = "Quarter number", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "quarterNo cannot be null")
|
||||
@Min(value = 1, message = "quarterNo must be between 1 and 4")
|
||||
@Max(value = 4, message = "quarterNo must be between 1 and 4")
|
||||
private Integer quarterNo;
|
||||
|
||||
@Schema(description = "Distribution ratio", example = "0.2500")
|
||||
private BigDecimal distributionRatio;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.profit;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.profit.ProjectProfitService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 特建投项目盈亏")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/profit")
|
||||
@Validated
|
||||
public class ProjectProfitController {
|
||||
|
||||
@Resource
|
||||
private ProjectProfitService projectProfitService;
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得项目盈亏详情")
|
||||
@Parameter(name = "projectId", description = "项目 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:profit:query')")
|
||||
public CommonResult<ProjectProfitRespVO> getProjectProfit(@RequestParam("projectId") Long projectId) {
|
||||
return success(projectProfitService.getProjectProfit(projectId));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得项目盈亏分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:profit:query')")
|
||||
public CommonResult<PageResult<ProjectProfitRespVO>> getProjectProfitPage(@Valid ProjectProfitPageReqVO pageReqVO) {
|
||||
return success(projectProfitService.getProjectProfitPage(pageReqVO));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 项目盈亏分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectProfitPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "项目名称,模糊匹配", example = "设计")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "是否签订合同", example = "true")
|
||||
private Boolean contractSignedFlag;
|
||||
|
||||
@Schema(description = "项目开始年度", example = "2026")
|
||||
private Integer projectStartYear;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 项目盈亏 Response VO")
|
||||
@Data
|
||||
public class ProjectProfitRespVO {
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否签订合同")
|
||||
private Boolean contractSignedFlag;
|
||||
|
||||
@Schema(description = "合同总产值")
|
||||
private BigDecimal contractAmount;
|
||||
|
||||
@Schema(description = "结算合同总产值")
|
||||
private BigDecimal finalSettlementAmount;
|
||||
|
||||
@Schema(description = "项目预算产值总计,结算合同总产值大于 0 时取结算合同总产值,否则取合同总产值")
|
||||
private BigDecimal effectiveSettlementAmount;
|
||||
|
||||
@Schema(description = "综合所人工成本")
|
||||
private BigDecimal comprehensivePlanningAmount;
|
||||
|
||||
@Schema(description = "专项/源头合作分包人工成本合计")
|
||||
private BigDecimal subcontractPlanningAmount;
|
||||
|
||||
@Schema(description = "专项分包人工成本")
|
||||
private BigDecimal specialSubcontractPlanningAmount;
|
||||
|
||||
@Schema(description = "源头合作分包人工成本")
|
||||
private BigDecimal sourceCoopSubcontractPlanningAmount;
|
||||
|
||||
@Schema(description = "专业所考核产值")
|
||||
private BigDecimal majorOutputValue;
|
||||
|
||||
@Schema(description = "专业所人工成本")
|
||||
private BigDecimal majorExpectedPerformance;
|
||||
|
||||
@Schema(description = "科创产值比例")
|
||||
private BigDecimal innovationOutputRate;
|
||||
|
||||
@Schema(description = "科创产值")
|
||||
private BigDecimal innovationOutputValue;
|
||||
|
||||
@Schema(description = "其他成本")
|
||||
private BigDecimal otherCost;
|
||||
|
||||
@Schema(description = "预算盈亏值")
|
||||
private BigDecimal profitLossValue;
|
||||
|
||||
@Schema(description = "预算盈亏百分比")
|
||||
private BigDecimal profitLossRate;
|
||||
|
||||
@Schema(description = "项目开始年度")
|
||||
private Integer projectStartYear;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.project;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.project.ProjectService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 特建投项目")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/project")
|
||||
@Validated
|
||||
public class ProjectController {
|
||||
|
||||
@Resource
|
||||
private ProjectService projectService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建项目")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:project:create')")
|
||||
public CommonResult<Long> createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
|
||||
return success(projectService.createProject(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改项目")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:project:update')")
|
||||
public CommonResult<Boolean> updateProject(@Valid @RequestBody ProjectSaveReqVO updateReqVO) {
|
||||
projectService.updateProject(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除项目")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:project:delete')")
|
||||
public CommonResult<Boolean> deleteProject(@RequestParam("id") Long id) {
|
||||
projectService.deleteProject(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Operation(summary = "批量删除项目")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('tjt:project:delete')")
|
||||
public CommonResult<Boolean> deleteProjectList(@RequestParam("ids") List<Long> ids) {
|
||||
projectService.deleteProjectList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得项目分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:project:query')")
|
||||
public CommonResult<PageResult<ProjectRespVO>> getProjectPage(@Valid ProjectPageReqVO pageReqVO) {
|
||||
return success(projectService.getProjectPage(pageReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得项目详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:project:query')")
|
||||
public CommonResult<ProjectRespVO> getProject(@RequestParam("id") Long id) {
|
||||
return success(projectService.getProject(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 项目分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "项目名称,模糊匹配", example = "设计")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "是否签订合同", example = "true")
|
||||
private Boolean contractSignedFlag;
|
||||
|
||||
@Schema(description = "项目开始年度", example = "2026")
|
||||
private Integer projectStartYear;
|
||||
|
||||
@Schema(description = "项目状态", example = "进行中")
|
||||
private String projectStatus;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 项目 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class ProjectRespVO {
|
||||
|
||||
private static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
|
||||
|
||||
@Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty("项目ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "排序")
|
||||
@ExcelProperty("排序")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("是否签订合同")
|
||||
private Boolean contractSignedFlag;
|
||||
|
||||
@Schema(description = "合同总产值", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("合同总产值")
|
||||
private BigDecimal contractAmount;
|
||||
|
||||
@Schema(description = "建筑面积", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("建筑面积")
|
||||
private BigDecimal totalConstructionArea;
|
||||
|
||||
@Schema(description = "建设单位")
|
||||
@ExcelProperty("建设单位")
|
||||
private String constructionUnitName;
|
||||
|
||||
@Schema(description = "建设单位联系人")
|
||||
@ExcelProperty("建设单位联系人")
|
||||
private String contactName;
|
||||
|
||||
@Schema(description = "建设单位联系电话")
|
||||
@ExcelProperty("建设单位联系电话")
|
||||
private String contactPhone;
|
||||
|
||||
@Schema(description = "合同签订日期")
|
||||
@ExcelProperty("合同签订日期")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
private LocalDate contractSigningDate;
|
||||
|
||||
@Schema(description = "项目经理")
|
||||
@ExcelProperty("项目经理")
|
||||
private String projectManagerName;
|
||||
|
||||
@Schema(description = "工程负责人")
|
||||
@ExcelProperty("工程负责人")
|
||||
private String engineeringPrincipalName;
|
||||
|
||||
@Schema(description = "工程类型")
|
||||
@ExcelProperty("工程类型")
|
||||
private String projectType;
|
||||
|
||||
@Schema(description = "设计类型")
|
||||
@ExcelProperty("设计类型")
|
||||
private String projectCategory;
|
||||
|
||||
@Schema(description = "项目开始年度")
|
||||
@ExcelProperty("项目开始年度")
|
||||
private Integer projectStartYear;
|
||||
|
||||
@Schema(description = "项目状态")
|
||||
@ExcelProperty("项目状态")
|
||||
private String projectStatus;
|
||||
|
||||
@Schema(description = "是否封档")
|
||||
private Boolean archiveFlag;
|
||||
|
||||
@Schema(description = "封档时间")
|
||||
private LocalDateTime archiveTime;
|
||||
|
||||
@Schema(description = "暂停原因")
|
||||
private String pauseReason;
|
||||
|
||||
@Schema(description = "中止原因")
|
||||
private String terminateReason;
|
||||
|
||||
@Schema(description = "结算合同总产值")
|
||||
@ExcelProperty("结算合同总产值")
|
||||
private BigDecimal finalSettlementAmount;
|
||||
|
||||
@Schema(description = "科创产值比例")
|
||||
@ExcelProperty("科创产值比例")
|
||||
private BigDecimal innovationOutputRate;
|
||||
|
||||
@Schema(description = "其他成本")
|
||||
@ExcelProperty("其他成本")
|
||||
private BigDecimal otherCost;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "项目角色人员列表")
|
||||
private List<ProjectRolePersonRespVO> rolePersons;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 项目角色人员 Response VO")
|
||||
@Data
|
||||
public class ProjectRolePersonRespVO {
|
||||
|
||||
@Schema(description = "主键 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "角色编码", example = "project_manager")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "角色名称", example = "项目经理")
|
||||
private String roleName;
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名", example = "张三")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Schema(description = "管理后台 - 项目角色人员新增/修改 Request VO")
|
||||
@Data
|
||||
public class ProjectRolePersonSaveReqVO {
|
||||
|
||||
@Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "project_manager")
|
||||
@NotBlank(message = "角色编码不能为空")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "员工 ID 不能为空")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三")
|
||||
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "排序号", example = "1")
|
||||
private Integer sortNo;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
|
||||
|
||||
@Schema(description = "管理后台 - 项目新增/修改 Request VO")
|
||||
@Data
|
||||
public class ProjectSaveReqVO {
|
||||
|
||||
@Schema(description = "项目 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX 设计项目")
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Size(max = 200, message = "项目名称长度不能超过 200 个字符")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "排序", example = "0")
|
||||
private Integer sortNo;
|
||||
|
||||
@Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@NotNull(message = "是否签订合同不能为空")
|
||||
private Boolean contractSignedFlag;
|
||||
|
||||
@Schema(description = "合同总产值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000000")
|
||||
@NotNull(message = "合同总产值不能为空")
|
||||
private BigDecimal contractAmount;
|
||||
|
||||
@Schema(description = "建筑面积", requiredMode = Schema.RequiredMode.REQUIRED, example = "30000")
|
||||
@NotNull(message = "建筑面积不能为空")
|
||||
private BigDecimal totalConstructionArea;
|
||||
|
||||
@Schema(description = "建设单位", example = "XX 建设单位")
|
||||
@Size(max = 200, message = "建设单位长度不能超过 200 个字符")
|
||||
private String constructionUnitName;
|
||||
|
||||
@Schema(description = "建设单位联系人", example = "张三")
|
||||
@Size(max = 64, message = "建设单位联系人长度不能超过 64 个字符")
|
||||
private String contactName;
|
||||
|
||||
@Schema(description = "建设单位联系电话", example = "13800000000")
|
||||
@Size(max = 32, message = "建设单位联系电话长度不能超过 32 个字符")
|
||||
private String contactPhone;
|
||||
|
||||
@Schema(description = "合同签订日期", example = "2026-04-14")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
|
||||
private LocalDate contractSigningDate;
|
||||
|
||||
@Schema(description = "工程类型", example = "建筑工程")
|
||||
@Size(max = 50, message = "工程类型长度不能超过 50 个字符")
|
||||
private String projectType;
|
||||
|
||||
@Schema(description = "设计类型", example = "住宅")
|
||||
@Size(max = 50, message = "设计类型长度不能超过 50 个字符")
|
||||
private String projectCategory;
|
||||
|
||||
@Schema(description = "项目开始年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "项目开始年度不能为空")
|
||||
private Integer projectStartYear;
|
||||
|
||||
@Schema(description = "项目状态", example = "进行中")
|
||||
@Size(max = 20, message = "项目状态长度不能超过 20 个字符")
|
||||
private String projectStatus;
|
||||
|
||||
@Schema(description = "暂停原因")
|
||||
@Size(max = 255, message = "暂停原因长度不能超过 255 个字符")
|
||||
private String pauseReason;
|
||||
|
||||
@Schema(description = "中止原因")
|
||||
@Size(max = 255, message = "中止原因长度不能超过 255 个字符")
|
||||
private String terminateReason;
|
||||
|
||||
@Schema(description = "结算合同总产值", example = "1200000")
|
||||
private BigDecimal finalSettlementAmount;
|
||||
|
||||
@Schema(description = "科创产值比例", example = "0.0100")
|
||||
@DecimalMin(value = "0.0000", message = "科创产值比例不能小于 0%")
|
||||
@DecimalMax(value = "1.0000", message = "科创产值比例不能大于 100%")
|
||||
private BigDecimal innovationOutputRate;
|
||||
|
||||
@Schema(description = "其他成本", example = "20000.00")
|
||||
private BigDecimal otherCost;
|
||||
|
||||
@Schema(description = "项目角色人员列表")
|
||||
@Valid
|
||||
private java.util.List<ProjectRolePersonSaveReqVO> rolePersons;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.*;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.report.ProjectOutputReportService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
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 javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 产值报表导出")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/report")
|
||||
@Validated
|
||||
public class ProjectOutputReportController {
|
||||
|
||||
@Resource
|
||||
private ProjectOutputReportService projectOutputReportService;
|
||||
|
||||
@GetMapping("/project-budget/export-excel")
|
||||
@Operation(summary = "导出项目考核产值预算表")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:report-budget:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportProjectBudgetExcel(HttpServletResponse response,
|
||||
@Valid ProjectBudgetExportReqVO reqVO) throws IOException {
|
||||
projectOutputReportService.exportProjectBudgetExcel(response, reqVO);
|
||||
}
|
||||
|
||||
@GetMapping("/project-quarter-output/export-excel")
|
||||
@Operation(summary = "导出项目级年度季度计取表")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:report-project-quarter:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportProjectQuarterOutputExcel(HttpServletResponse response,
|
||||
@Valid ProjectQuarterOutputExportReqVO reqVO) throws IOException {
|
||||
projectOutputReportService.exportProjectQuarterOutputExcel(response, reqVO);
|
||||
}
|
||||
|
||||
@GetMapping("/project-lead-quarter-output/export-excel")
|
||||
@Operation(summary = "导出工程负责人年度季度计取表")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:report-project-quarter:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportProjectLeadQuarterOutputExcel(HttpServletResponse response,
|
||||
@Valid ProjectLeadQuarterOutputExportReqVO reqVO)
|
||||
throws IOException {
|
||||
projectOutputReportService.exportProjectLeadQuarterOutputExcel(response, reqVO);
|
||||
}
|
||||
|
||||
@GetMapping("/specialty-person-output/export-excel")
|
||||
@Operation(summary = "导出专业内人员年度季度计取表")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:report-specialty-person:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportSpecialtyPersonOutputExcel(HttpServletResponse response,
|
||||
@Valid SpecialtyPersonOutputExportReqVO reqVO) throws IOException {
|
||||
projectOutputReportService.exportSpecialtyPersonOutputExcel(response, reqVO);
|
||||
}
|
||||
|
||||
@GetMapping("/specialty-person-output/preview")
|
||||
@Operation(summary = "预览专业内人员年度季度计取表")
|
||||
@PreAuthorize("@ss.hasAnyPermissions('tjt:report-specialty-person:query', 'tjt:report-specialty-person:export')")
|
||||
public CommonResult<SpecialtyPersonOutputPreviewRespVO> getSpecialtyPersonOutputPreview(
|
||||
@Valid SpecialtyPersonOutputPreviewReqVO reqVO) {
|
||||
return success(projectOutputReportService.getSpecialtyPersonOutputPreview(reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/project-overview/preview")
|
||||
@Operation(summary = "预览项目总览表")
|
||||
@PreAuthorize("@ss.hasAnyPermissions('tjt:report-summary:query', 'tjt:report-summary:export')")
|
||||
public CommonResult<ProjectOverviewPreviewRespVO> getProjectOverviewPreview(
|
||||
@Valid ProjectOverviewExportReqVO reqVO) {
|
||||
return success(projectOutputReportService.getProjectOverviewPreview(reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/project-overview/export-excel")
|
||||
@Operation(summary = "导出项目总览表")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:report-summary:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportProjectOverviewExcel(HttpServletResponse response,
|
||||
@Valid ProjectOverviewExportReqVO reqVO) throws IOException {
|
||||
projectOutputReportService.exportProjectOverviewExcel(response, reqVO);
|
||||
}
|
||||
|
||||
@GetMapping("/employee-output-summary/preview")
|
||||
@Operation(summary = "预览员工个人考核产值汇总表")
|
||||
@PreAuthorize("@ss.hasAnyPermissions('tjt:report-summary:query', 'tjt:report-summary:export')")
|
||||
public CommonResult<EmployeeOutputSummaryPreviewRespVO> getEmployeeOutputSummaryPreview(
|
||||
@Valid EmployeeOutputSummaryExportReqVO reqVO) {
|
||||
return success(projectOutputReportService.getEmployeeOutputSummaryPreview(reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/employee-output-summary/export-excel")
|
||||
@Operation(summary = "导出员工个人考核产值汇总表")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:report-summary:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportEmployeeOutputSummaryExcel(HttpServletResponse response,
|
||||
@Valid EmployeeOutputSummaryExportReqVO reqVO)
|
||||
throws IOException {
|
||||
projectOutputReportService.exportEmployeeOutputSummaryExcel(response, reqVO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 员工个人考核产值汇总 Excel 导出 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class EmployeeOutputSummaryExcelRespVO {
|
||||
|
||||
@ExcelProperty("序号")
|
||||
private Integer serialNo;
|
||||
|
||||
@ExcelProperty("姓名")
|
||||
private String employeeName;
|
||||
|
||||
@ExcelProperty("所属专业所")
|
||||
private String officeName;
|
||||
|
||||
@ExcelProperty("第一季度")
|
||||
private BigDecimal quarterOneAmount;
|
||||
|
||||
@ExcelProperty("第二季度")
|
||||
private BigDecimal quarterTwoAmount;
|
||||
|
||||
@ExcelProperty("第三季度")
|
||||
private BigDecimal quarterThreeAmount;
|
||||
|
||||
@ExcelProperty("第四季度")
|
||||
private BigDecimal quarterFourAmount;
|
||||
|
||||
@ExcelProperty("年度合计")
|
||||
private BigDecimal annualTotalAmount;
|
||||
|
||||
@ExcelProperty("所长/BIM考核产值")
|
||||
private BigDecimal officeLeaderOrBimAmount;
|
||||
|
||||
@ExcelProperty("年度考核产值合计")
|
||||
private BigDecimal totalAssessmentOutputAmount;
|
||||
|
||||
@ExcelProperty("1~12月份预计发生成本")
|
||||
private BigDecimal expectedCostAmount;
|
||||
|
||||
@ExcelProperty("基本考核产值")
|
||||
private BigDecimal basicAssessmentOutputAmount;
|
||||
|
||||
@ExcelProperty("剩余产值")
|
||||
private BigDecimal remainingOutputAmount;
|
||||
|
||||
@ExcelProperty("预估年底绩效")
|
||||
private BigDecimal estimatedYearEndPerformanceAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 员工个人考核产值汇总导出 Request VO")
|
||||
@Data
|
||||
public class EmployeeOutputSummaryExportReqVO {
|
||||
|
||||
@Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "年度不能为空")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "专业所 ID", example = "1")
|
||||
private Long officeId;
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工状态", example = "在职")
|
||||
private String employeeStatus;
|
||||
|
||||
@Schema(description = "排序方式", example = "annual_total_desc")
|
||||
private String sortType;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 员工个人考核产值汇总表预览 Response VO")
|
||||
@Data
|
||||
public class EmployeeOutputSummaryPreviewRespVO {
|
||||
|
||||
@Schema(description = "年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "预计 K 值,小数值,例如 0.4 表示 40%")
|
||||
private BigDecimal kValue;
|
||||
|
||||
@Schema(description = "员工明细行,金额单位:万元")
|
||||
private List<EmployeeOutputSummaryExcelRespVO> rows = Collections.emptyList();
|
||||
|
||||
@Schema(description = "合计行,金额单位:万元")
|
||||
private EmployeeOutputSummaryExcelRespVO totalRow;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 项目考核产值预算表导出 Request VO")
|
||||
@Data
|
||||
public class ProjectBudgetExportReqVO {
|
||||
|
||||
@Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "项目 ID 不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "导出年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 工程负责人年度季度计取表导出 Request VO")
|
||||
@Data
|
||||
public class ProjectLeadQuarterOutputExportReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "导出年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 项目总览表 Excel 导出 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class ProjectOverviewExcelRespVO {
|
||||
|
||||
@ExcelProperty("序号")
|
||||
private Integer serialNo;
|
||||
|
||||
@ExcelProperty("项目名称")
|
||||
private String projectName;
|
||||
|
||||
@ExcelProperty("项目任务包")
|
||||
private String planningContent;
|
||||
|
||||
@ExcelProperty("工程进度情况及其它说明")
|
||||
private String progressText;
|
||||
|
||||
@ExcelProperty("工作阶段")
|
||||
private String designStage;
|
||||
|
||||
@ExcelProperty("本专业+项总核算总产值")
|
||||
private BigDecimal totalOutputAmount;
|
||||
|
||||
@ExcelProperty("往年已发放比例")
|
||||
private String historicalIssuedRatioText;
|
||||
|
||||
@ExcelProperty("本期结算")
|
||||
private BigDecimal currentSettlementAmount;
|
||||
|
||||
@ExcelProperty("未结算比例")
|
||||
private String pendingRatioText;
|
||||
|
||||
@ExcelProperty("一季度")
|
||||
private BigDecimal quarterOneAmount;
|
||||
|
||||
@ExcelProperty("二季度")
|
||||
private BigDecimal quarterTwoAmount;
|
||||
|
||||
@ExcelProperty("三季度")
|
||||
private BigDecimal quarterThreeAmount;
|
||||
|
||||
@ExcelProperty("四季度")
|
||||
private BigDecimal quarterFourAmount;
|
||||
|
||||
@ExcelProperty("本年度小计")
|
||||
private BigDecimal yearTotalAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "Management Backend - Project overview export Request VO")
|
||||
@Data
|
||||
public class ProjectOverviewExportReqVO {
|
||||
|
||||
@Schema(description = "Year", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "Office ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "专业所不能为空")
|
||||
private Long officeId;
|
||||
|
||||
@Schema(description = "Sort type", example = "output_desc")
|
||||
private String sortType;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - 项目总览表预览 Response VO")
|
||||
@Data
|
||||
public class ProjectOverviewPreviewRespVO {
|
||||
|
||||
@Schema(description = "年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "专业所名称", example = "给排水所")
|
||||
private String officeName;
|
||||
|
||||
@Schema(description = "员工动态列")
|
||||
private List<EmployeeColumn> employeeColumns = Collections.emptyList();
|
||||
|
||||
@Schema(description = "项目行")
|
||||
private List<ProjectRow> rows = Collections.emptyList();
|
||||
|
||||
@Schema(description = "合计行")
|
||||
private ProjectRow totalRow;
|
||||
|
||||
@Schema(description = "员工动态列")
|
||||
@Data
|
||||
public static class EmployeeColumn {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名", example = "张三")
|
||||
private String employeeName;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "项目总览行")
|
||||
@Data
|
||||
public static class ProjectRow {
|
||||
|
||||
@Schema(description = "序号", example = "1")
|
||||
private Integer serialNo;
|
||||
|
||||
@Schema(description = "是否合计行", example = "false")
|
||||
private Boolean totalRow = false;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "工程进度情况及其它说明")
|
||||
private String progressText;
|
||||
|
||||
@Schema(description = "工作阶段")
|
||||
private String workStage;
|
||||
|
||||
@Schema(description = "本专业 + 项目总核算总产值,单位:万元")
|
||||
private BigDecimal totalOutputAmountWan;
|
||||
|
||||
@Schema(description = "往期已发放比例,小数值,例如 0.2 表示 20%")
|
||||
private BigDecimal historicalIssuedRatio;
|
||||
|
||||
@Schema(description = "本期结算占比,小数值,例如 0.2 表示 20%")
|
||||
private BigDecimal currentSettlementRatio;
|
||||
|
||||
@Schema(description = "本期结算考核产值,单位:万元")
|
||||
private BigDecimal currentSettlementAmountWan;
|
||||
|
||||
@Schema(description = "未结算比例,小数值,例如 0.2 表示 20%")
|
||||
private BigDecimal pendingRatio;
|
||||
|
||||
@Schema(description = "员工季度金额,key 为员工 ID,金额单位:万元")
|
||||
private Map<Long, EmployeeAmountValue> employeeAmountMap = new LinkedHashMap<>();
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "员工季度金额")
|
||||
@Data
|
||||
public static class EmployeeAmountValue {
|
||||
|
||||
@Schema(description = "一季度,单位:万元")
|
||||
private BigDecimal quarterOneAmountWan;
|
||||
|
||||
@Schema(description = "二季度,单位:万元")
|
||||
private BigDecimal quarterTwoAmountWan;
|
||||
|
||||
@Schema(description = "三季度,单位:万元")
|
||||
private BigDecimal quarterThreeAmountWan;
|
||||
|
||||
@Schema(description = "四季度,单位:万元")
|
||||
private BigDecimal quarterFourAmountWan;
|
||||
|
||||
@Schema(description = "本年度小计,单位:万元")
|
||||
private BigDecimal annualTotalAmountWan;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 项目级年度季度计取表导出 Request VO")
|
||||
@Data
|
||||
public class ProjectQuarterOutputExportReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "导出年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 专业内人员年度季度计取表导出 Request VO")
|
||||
@Data
|
||||
public class SpecialtyPersonOutputExportReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "water")
|
||||
@NotBlank(message = "专业编码不能为空")
|
||||
private String specialtyCode;
|
||||
|
||||
@Schema(description = "导出年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 专业内人员年度季度计取预览 Request VO")
|
||||
@Data
|
||||
public class SpecialtyPersonOutputPreviewReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "预览年度", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 专业内人员年度季度计取预览 Response VO")
|
||||
@Data
|
||||
public class SpecialtyPersonOutputPreviewRespVO {
|
||||
|
||||
@Schema(description = "锚点合约规划 ID")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "预览年度")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "专业分组")
|
||||
private List<GroupRow> groups;
|
||||
|
||||
@Schema(description = "专业分组")
|
||||
@Data
|
||||
public static class GroupRow {
|
||||
|
||||
@Schema(description = "专业编码")
|
||||
private String specialtyCode;
|
||||
|
||||
@Schema(description = "专业名称")
|
||||
private String specialtyName;
|
||||
|
||||
@Schema(description = "是否允许导出专业内人员计取表")
|
||||
private Boolean exportable;
|
||||
|
||||
@Schema(description = "专业金额(元)")
|
||||
private BigDecimal specialtyAmount;
|
||||
|
||||
@Schema(description = "角色比例合计")
|
||||
private BigDecimal roleTotal;
|
||||
|
||||
@Schema(description = "已配置人数")
|
||||
private Integer personCount;
|
||||
|
||||
@Schema(description = "是否超额")
|
||||
private Boolean overRatio;
|
||||
|
||||
@Schema(description = "专业一季度金额(万元)")
|
||||
private BigDecimal specialtyQuarterOneAmountWan;
|
||||
|
||||
@Schema(description = "专业二季度金额(万元)")
|
||||
private BigDecimal specialtyQuarterTwoAmountWan;
|
||||
|
||||
@Schema(description = "专业三季度金额(万元)")
|
||||
private BigDecimal specialtyQuarterThreeAmountWan;
|
||||
|
||||
@Schema(description = "专业四季度金额(万元)")
|
||||
private BigDecimal specialtyQuarterFourAmountWan;
|
||||
|
||||
@Schema(description = "专业本年度小计金额(万元)")
|
||||
private BigDecimal specialtyYearTotalAmountWan;
|
||||
|
||||
@Schema(description = "角色人员年度季度计取明细")
|
||||
private List<RoleRow> rows;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "角色人员年度季度计取明细")
|
||||
@Data
|
||||
public static class RoleRow {
|
||||
|
||||
@Schema(description = "专业编码")
|
||||
private String specialtyCode;
|
||||
|
||||
@Schema(description = "专业名称")
|
||||
private String specialtyName;
|
||||
|
||||
@Schema(description = "角色编码")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "角色名称")
|
||||
private String roleName;
|
||||
|
||||
@Schema(description = "角色比例")
|
||||
private BigDecimal roleRatio;
|
||||
|
||||
@Schema(description = "角色金额(元)")
|
||||
private BigDecimal roleAmount;
|
||||
|
||||
@Schema(description = "人员比例合计")
|
||||
private BigDecimal personTotalRatio;
|
||||
|
||||
@Schema(description = "人员明细")
|
||||
private List<PersonRow> persons;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "人员年度季度计取明细")
|
||||
@Data
|
||||
public static class PersonRow {
|
||||
|
||||
@Schema(description = "人员唯一键")
|
||||
private String personKey;
|
||||
|
||||
@Schema(description = "员工 ID")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "人员工作量比例")
|
||||
private BigDecimal personRatio;
|
||||
|
||||
@Schema(description = "人员金额(元)")
|
||||
private BigDecimal personAmount;
|
||||
|
||||
@Schema(description = "参与角色")
|
||||
private String roleNames;
|
||||
|
||||
@Schema(description = "占专业比例合计")
|
||||
private BigDecimal adjustedPersonRatio;
|
||||
|
||||
@Schema(description = "一季度比例")
|
||||
private BigDecimal quarterOneRatio;
|
||||
|
||||
@Schema(description = "一季度金额(万元)")
|
||||
private BigDecimal quarterOneAmountWan;
|
||||
|
||||
@Schema(description = "二季度比例")
|
||||
private BigDecimal quarterTwoRatio;
|
||||
|
||||
@Schema(description = "二季度金额(万元)")
|
||||
private BigDecimal quarterTwoAmountWan;
|
||||
|
||||
@Schema(description = "三季度比例")
|
||||
private BigDecimal quarterThreeRatio;
|
||||
|
||||
@Schema(description = "三季度金额(万元)")
|
||||
private BigDecimal quarterThreeAmountWan;
|
||||
|
||||
@Schema(description = "四季度比例")
|
||||
private BigDecimal quarterFourRatio;
|
||||
|
||||
@Schema(description = "四季度金额(万元)")
|
||||
private BigDecimal quarterFourAmountWan;
|
||||
|
||||
@Schema(description = "本年度小计比例")
|
||||
private BigDecimal yearTotalRatio;
|
||||
|
||||
@Schema(description = "本年度小计金额(万元)")
|
||||
private BigDecimal yearTotalAmountWan;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitBatchSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 特建投页面5角色比例")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/specialty-role-split")
|
||||
@Validated
|
||||
public class SpecialtyRoleSplitController {
|
||||
|
||||
@Resource
|
||||
private SpecialtyRoleSplitService specialtyRoleSplitService;
|
||||
|
||||
@GetMapping("/list-by-planning")
|
||||
@Operation(summary = "根据合约规划获得页面5角色比例列表")
|
||||
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:specialty-role-split:query')")
|
||||
public CommonResult<List<SpecialtyRoleSplitRespVO>> getSpecialtyRoleSplitListByPlanningId(@RequestParam("planningId") Long planningId) {
|
||||
return success(specialtyRoleSplitService.getSpecialtyRoleSplitListByPlanningId(planningId));
|
||||
}
|
||||
|
||||
@PutMapping("/save-batch")
|
||||
@Operation(summary = "批量保存页面5角色比例")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:specialty-role-split:update')")
|
||||
public CommonResult<Boolean> saveSpecialtyRoleSplitBatch(@Valid @RequestBody SpecialtyRoleSplitBatchSaveReqVO reqVO) {
|
||||
specialtyRoleSplitService.saveSpecialtyRoleSplitBatch(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 页面5角色人员 Response VO")
|
||||
@Data
|
||||
public class SpecialtyRolePersonRespVO {
|
||||
|
||||
@Schema(description = "员工 ID")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "人员比例")
|
||||
private BigDecimal personRatio;
|
||||
|
||||
@Schema(description = "人员金额")
|
||||
private BigDecimal personAmount;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 页面5角色人员保存 Item Request VO")
|
||||
@Data
|
||||
public class SpecialtyRolePersonSaveReqVO {
|
||||
|
||||
@Schema(description = "员工 ID", example = "1")
|
||||
private Long employeeId;
|
||||
|
||||
@Schema(description = "员工姓名", example = "张三")
|
||||
private String employeeName;
|
||||
|
||||
@Schema(description = "人员比例", example = "0.1000")
|
||||
private BigDecimal personRatio;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 页面5角色比例批量保存 Request VO")
|
||||
@Data
|
||||
public class SpecialtyRoleSplitBatchSaveReqVO {
|
||||
|
||||
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "合约规划 ID 不能为空")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "角色配置列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "角色配置列表不能为空")
|
||||
@Valid
|
||||
private List<SpecialtyRoleSplitSaveItemReqVO> items;
|
||||
|
||||
@Schema(description = "是否临时保存。true 时仅做基础格式处理,跳过角色合计、设计人员必填等强校验", example = "false")
|
||||
private Boolean temporarySave;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 页面5角色比例 Response VO")
|
||||
@Data
|
||||
public class SpecialtyRoleSplitRespVO {
|
||||
|
||||
@Schema(description = "角色比例 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "页面4拆分 ID", example = "1")
|
||||
private Long outputSplitId;
|
||||
|
||||
@Schema(description = "合约规划 ID", example = "1")
|
||||
private Long planningId;
|
||||
|
||||
@Schema(description = "项目名称")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目任务包")
|
||||
private String planningContent;
|
||||
|
||||
@Schema(description = "专业编码")
|
||||
private String specialtyCode;
|
||||
|
||||
@Schema(description = "专业名称")
|
||||
private String specialtyName;
|
||||
|
||||
@Schema(description = "专业金额")
|
||||
private BigDecimal specialtyAmount;
|
||||
|
||||
@Schema(description = "角色编码")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "角色名称")
|
||||
private String roleName;
|
||||
|
||||
@Schema(description = "角色比例")
|
||||
private BigDecimal roleRatio;
|
||||
|
||||
@Schema(description = "角色金额")
|
||||
private BigDecimal roleAmount;
|
||||
|
||||
@Schema(description = "人员配置列表")
|
||||
private List<SpecialtyRolePersonRespVO> persons;
|
||||
|
||||
@Schema(description = "排序号")
|
||||
private Integer sortNo;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 页面5角色比例保存 Item Request VO")
|
||||
@Data
|
||||
public class SpecialtyRoleSplitSaveItemReqVO {
|
||||
|
||||
@Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "arch")
|
||||
@NotBlank(message = "专业编码不能为空")
|
||||
private String specialtyCode;
|
||||
|
||||
@Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "director")
|
||||
@NotBlank(message = "角色编码不能为空")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "角色比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000")
|
||||
@NotNull(message = "角色比例不能为空")
|
||||
private BigDecimal roleRatio;
|
||||
|
||||
@Schema(description = "人员配置列表")
|
||||
@Valid
|
||||
private List<SpecialtyRolePersonSaveReqVO> persons;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValuePageReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO;
|
||||
import cn.iocoder.lyzsys.module.tjt.service.yearkvalue.YearKValueService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 年度 K 值")
|
||||
@RestController
|
||||
@RequestMapping("/tjt/year-k-value")
|
||||
@Validated
|
||||
public class YearKValueController {
|
||||
|
||||
@Resource
|
||||
private YearKValueService yearKValueService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建年度 K 值")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:create')")
|
||||
public CommonResult<Long> createYearKValue(@RequestBody Map<String, Object> requestBody) {
|
||||
YearKValueSaveReqVO createReqVO = buildSaveReqVO(requestBody, false);
|
||||
return success(yearKValueService.createYearKValue(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改年度 K 值")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:update')")
|
||||
public CommonResult<Boolean> updateYearKValue(@RequestBody Map<String, Object> requestBody) {
|
||||
YearKValueSaveReqVO updateReqVO = buildSaveReqVO(requestBody, true);
|
||||
yearKValueService.updateYearKValue(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除年度 K 值")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:delete')")
|
||||
public CommonResult<Boolean> deleteYearKValue(@RequestParam("id") Long id) {
|
||||
yearKValueService.deleteYearKValue(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得年度 K 值详情")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:query')")
|
||||
public CommonResult<YearKValueRespVO> getYearKValue(@RequestParam("id") Long id) {
|
||||
return success(yearKValueService.getYearKValue(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得年度 K 值分页")
|
||||
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:query')")
|
||||
public CommonResult<PageResult<YearKValueRespVO>> getYearKValuePage(@Valid YearKValuePageReqVO pageReqVO) {
|
||||
return success(yearKValueService.getYearKValuePage(pageReqVO));
|
||||
}
|
||||
|
||||
private YearKValueSaveReqVO buildSaveReqVO(Map<String, Object> requestBody, boolean requireId) {
|
||||
YearKValueSaveReqVO reqVO = BeanUtils.toBean(requestBody, YearKValueSaveReqVO.class);
|
||||
if (requireId && reqVO.getId() == null) {
|
||||
throw invalidParamException("编号不能为空");
|
||||
}
|
||||
if (reqVO.getKYear() == null) {
|
||||
throw invalidParamException("年度不能为空");
|
||||
}
|
||||
if (reqVO.getKValue() == null) {
|
||||
throw invalidParamException("K值不能为空");
|
||||
}
|
||||
if (reqVO.getKValue().compareTo(BigDecimal.ZERO) < 0
|
||||
|| reqVO.getKValue().compareTo(BigDecimal.ONE) > 0) {
|
||||
throw invalidParamException("K值必须在 0% 到 100% 之间");
|
||||
}
|
||||
if (reqVO.getRemark() != null && reqVO.getRemark().length() > 255) {
|
||||
throw invalidParamException("备注长度不能超过 255 个字符");
|
||||
}
|
||||
return reqVO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - 年度 K 值分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class YearKValuePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "年度", example = "2026")
|
||||
private Integer kYear;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 年度 K 值 Response VO")
|
||||
@Data
|
||||
public class YearKValueRespVO {
|
||||
|
||||
@Schema(description = "主键 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "年度", example = "2026")
|
||||
private Integer kYear;
|
||||
|
||||
@Schema(description = "K 值", example = "0.4000")
|
||||
private BigDecimal kValue;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.DecimalMax;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - 年度 K 值新增/修改 Request VO")
|
||||
@Data
|
||||
public class YearKValueSaveReqVO {
|
||||
|
||||
@Schema(description = "主键 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "年度不能为空")
|
||||
private Integer kYear;
|
||||
|
||||
@Schema(description = "K 值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.4000")
|
||||
@NotNull(message = "K 值不能为空")
|
||||
@DecimalMin(value = "0.0000", message = "K 值不能小于 0%")
|
||||
@DecimalMax(value = "1.0000", message = "K 值不能大于 100%")
|
||||
private BigDecimal kValue;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 255, message = "备注长度不能超过 255 个字符")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否启用", example = "true")
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@TableName("tjt_employee")
|
||||
@KeySequence("tjt_employee_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmployeeDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
private String employeeName;
|
||||
|
||||
private String gender;
|
||||
|
||||
private Long officeId;
|
||||
|
||||
private String registrationType;
|
||||
|
||||
private String jobTitle;
|
||||
|
||||
private String registrationSealNo;
|
||||
|
||||
private LocalDate entryDate;
|
||||
|
||||
private LocalDate leaveDate;
|
||||
|
||||
private String employeeStatus;
|
||||
|
||||
private String remark;
|
||||
|
||||
private Boolean officeLeaderFlag;
|
||||
|
||||
private Integer sortNo;
|
||||
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget;
|
||||
|
||||
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@TableName("tjt_employee_year_cost_budget")
|
||||
@KeySequence("tjt_employee_year_cost_budget_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmployeeYearCostBudgetDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
private Long employeeId;
|
||||
|
||||
private String employeeName;
|
||||
|
||||
private Integer budgetYear;
|
||||
|
||||
private BigDecimal expectedCostAmount;
|
||||
|
||||
private String remark;
|
||||
|
||||
private Integer sortNo;
|
||||
|
||||
private Boolean enabledFlag;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user