完善会议
This commit is contained in:
1 parent
06d2bc5feb
commit
169a2735d9
16 files changed
+1259
-1115
No files matched your search
+171
@@ -0,0 +1,171 @@
|
||||
package com.bsd.pjcloud.integrated.vo.meeting.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会议室预约详情页面VO
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "会议室预约详情页面VO")
|
||||
public class MeetingRoomBookingDetailVo {
|
||||
|
||||
/**
|
||||
* 会议室ID
|
||||
*/
|
||||
@Schema(description = "会议室ID")
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 会议室名称
|
||||
*/
|
||||
@Schema(description = "会议室名称")
|
||||
private String roomName;
|
||||
|
||||
/**
|
||||
* 查询日期
|
||||
*/
|
||||
@Schema(description = "查询日期")
|
||||
private LocalDate queryDate;
|
||||
|
||||
/**
|
||||
* 时间段状态列表
|
||||
*/
|
||||
@Schema(description = "时间段状态列表")
|
||||
private List<TimeSlotStatusVo> timeSlots;
|
||||
|
||||
/**
|
||||
* 当日预约列表
|
||||
*/
|
||||
@Schema(description = "当日预约列表")
|
||||
private List<DailyBookingVo> dailyBookings;
|
||||
|
||||
/**
|
||||
* 时间段状态VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "时间段状态VO")
|
||||
public static class TimeSlotStatusVo {
|
||||
/**
|
||||
* 开始时间(如:08:00)
|
||||
*/
|
||||
@Schema(description = "开始时间")
|
||||
private String startTime;
|
||||
|
||||
/**
|
||||
* 结束时间(如:08:30)
|
||||
*/
|
||||
@Schema(description = "结束时间")
|
||||
private String endTime;
|
||||
|
||||
/**
|
||||
* 状态:available-可预约,booked-已预约,reviewing-审核中
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
@Schema(description = "状态描述")
|
||||
private String statusText;
|
||||
|
||||
/**
|
||||
* 预约信息(当状态为已预约或审核中时)
|
||||
*/
|
||||
@Schema(description = "预约信息")
|
||||
private BookingInfoVo bookingInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预约信息VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "预约信息VO")
|
||||
public static class BookingInfoVo {
|
||||
/**
|
||||
* 会议主题
|
||||
*/
|
||||
@Schema(description = "会议主题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 申请人
|
||||
*/
|
||||
@Schema(description = "申请人")
|
||||
private String applicantName;
|
||||
|
||||
/**
|
||||
* 审核状态
|
||||
*/
|
||||
@Schema(description = "审核状态")
|
||||
private String checkStatus;
|
||||
|
||||
/**
|
||||
* 审核状态描述
|
||||
*/
|
||||
@Schema(description = "审核状态描述")
|
||||
private String checkStatusText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当日预约VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "当日预约VO")
|
||||
public static class DailyBookingVo {
|
||||
/**
|
||||
* 预约ID
|
||||
*/
|
||||
@Schema(description = "预约ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会议主题
|
||||
*/
|
||||
@Schema(description = "会议主题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 申请人
|
||||
*/
|
||||
@Schema(description = "申请人")
|
||||
private String applicantName;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@Schema(description = "开始时间")
|
||||
private String startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
@Schema(description = "结束时间")
|
||||
private String endTime;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
@Schema(description = "状态描述")
|
||||
private String statusText;
|
||||
|
||||
/**
|
||||
* 会议说明
|
||||
*/
|
||||
@Schema(description = "会议说明")
|
||||
private String description;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ public class TimeSlotVo {
|
||||
@Schema(description = "结束时间")
|
||||
private String endTime;
|
||||
|
||||
@Schema(description = "状态:free-空闲,booked-已预约,disabled-不可用")
|
||||
@Schema(description = "状态:free-空闲,booked-已预约,reviewing-审核中,disabled-不可用")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "预约人信息")
|
||||
|
||||
@@ -1,399 +0,0 @@
|
||||
# 外部系统滚动通知接口集成指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档介绍如何集成使用 PJCloud 系统的滚动通知接口,该接口提供安全的 API 访问,供外部系统获取滚动通知信息。
|
||||
|
||||
## 🔐 认证方式
|
||||
|
||||
接口采用 **AppId + AppSecret** 的认证方式,通过 HTTP 请求头进行身份验证。
|
||||
|
||||
### 认证参数
|
||||
|
||||
| 参数名 | 位置 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| X-App-Id | 请求头 | 是 | 应用ID,由系统管理员分配 |
|
||||
| X-App-Secret | 请求头 | 是 | 应用密钥,由系统管理员分配 |
|
||||
|
||||
## 🌐 接口信息
|
||||
|
||||
### 滚动通知接口
|
||||
|
||||
- **接口地址**: `GET /notice/external/scroll`
|
||||
- **请求方式**: GET
|
||||
- **认证方式**: 请求头认证
|
||||
- **数据格式**: JSON
|
||||
|
||||
### 请求参数
|
||||
|
||||
#### 查询参数
|
||||
| 参数名 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| limit | 否 | 20 | 返回条数,最大50条 |
|
||||
|
||||
#### 请求头示例
|
||||
```http
|
||||
X-App-Id: 202501270001
|
||||
X-App-Secret: 550e8400-e29b-41d4-a716-446655440001
|
||||
```
|
||||
|
||||
## 📡 响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "系统维护通知",
|
||||
"createTime": "2025-01-27 10:00:00",
|
||||
"description": "系统将于今晚进行维护升级"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "新功能上线",
|
||||
"createTime": "2025-01-26 15:30:00",
|
||||
"description": "新增用户管理功能模块"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "无效的应用ID或密钥",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## ⏰ 过期时间功能
|
||||
|
||||
系统支持为公告设置过期时间,到期后公告将自动下架不再显示:
|
||||
|
||||
- **过期时间字段**: `expireTime`
|
||||
- **自动处理**: 每天凌晨2点自动检查并处理过期公告
|
||||
- **处理逻辑**: 过期公告状态自动更新为"已下架"
|
||||
- **查询过滤**: 所有查询接口自动过滤已过期公告
|
||||
- **灵活设置**: 支持设置具体过期时间,也支持永不过期(不设置过期时间)
|
||||
|
||||
## 💻 集成示例
|
||||
|
||||
### JavaScript 调用示例
|
||||
|
||||
#### 基础调用
|
||||
```javascript
|
||||
// 获取滚动通知
|
||||
async function getScrollNotices(limit = 20) {
|
||||
try {
|
||||
const response = await fetch('/notice/external/scroll?limit=' + limit, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-App-Id': '202501270001',
|
||||
'X-App-Secret': '550e8400-e29b-41d4-a716-446655440001',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
return result.data;
|
||||
} else {
|
||||
throw new Error(result.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取通知失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
getScrollNotices(15)
|
||||
.then(notices => {
|
||||
console.log('获取到通知:', notices);
|
||||
// 处理通知数据
|
||||
displayNotices(notices);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('错误:', error.message);
|
||||
});
|
||||
```
|
||||
|
||||
#### 滚动展示组件
|
||||
```javascript
|
||||
// 滚动通知展示组件
|
||||
class ScrollNoticeDisplay {
|
||||
constructor(containerId, appId, appSecret) {
|
||||
this.container = document.getElementById(containerId);
|
||||
this.appId = appId;
|
||||
this.appSecret = appSecret;
|
||||
this.notices = [];
|
||||
this.currentIndex = 0;
|
||||
this.interval = null;
|
||||
}
|
||||
|
||||
// 获取通知数据
|
||||
async fetchNotices() {
|
||||
try {
|
||||
const response = await fetch('/notice/external/scroll?limit=10', {
|
||||
headers: {
|
||||
'X-App-Id': this.appId,
|
||||
'X-App-Secret': this.appSecret
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.code === 0) {
|
||||
this.notices = result.data;
|
||||
this.startScroll();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取通知失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始滚动
|
||||
startScroll() {
|
||||
if (this.notices.length === 0) return;
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
this.showNextNotice();
|
||||
}, 3000); // 每3秒切换一次
|
||||
|
||||
this.showNextNotice(); // 立即显示第一条
|
||||
}
|
||||
|
||||
// 显示下一条通知
|
||||
showNextNotice() {
|
||||
if (this.notices.length === 0) return;
|
||||
|
||||
const notice = this.notices[this.currentIndex];
|
||||
this.container.innerHTML = `
|
||||
<div class="notice-item">
|
||||
<span class="notice-title">${notice.title}</span>
|
||||
<span class="notice-time">${notice.createTime}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.currentIndex = (this.currentIndex + 1) % this.notices.length;
|
||||
}
|
||||
|
||||
// 停止滚动
|
||||
stopScroll() {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const noticeDisplay = new ScrollNoticeDisplay(
|
||||
'notice-container',
|
||||
'202501270001',
|
||||
'550e8400-e29b-41d4-a716-446655440001'
|
||||
);
|
||||
|
||||
noticeDisplay.fetchNotices();
|
||||
```
|
||||
|
||||
### cURL 调用示例
|
||||
|
||||
#### 基础调用
|
||||
```bash
|
||||
curl -X GET "http://your-domain/notice/external/scroll?limit=15" \
|
||||
-H "X-App-Id: 202501270001" \
|
||||
-H "X-App-Secret: 550e8400-e29b-41d4-a716-446655440001"
|
||||
```
|
||||
|
||||
#### 获取最大条数
|
||||
```bash
|
||||
curl -X GET "http://your-domain/notice/external/scroll?limit=50" \
|
||||
-H "X-App-Id: 202501270001" \
|
||||
-H "X-App-Secret: 550e8400-e29b-41d4-a716-446655440001"
|
||||
```
|
||||
|
||||
### Java 调用示例
|
||||
|
||||
```java
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
public class NoticeApiClient {
|
||||
|
||||
private final String baseUrl;
|
||||
private final String appId;
|
||||
private final String appSecret;
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
public NoticeApiClient(String baseUrl, String appId, String appSecret) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.appId = appId;
|
||||
this.appSecret = appSecret;
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
public List<NoticeScreenVo> getScrollNotices(int limit) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-App-Id", appId);
|
||||
headers.set("X-App-Secret", appSecret);
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
String url = baseUrl + "/notice/external/scroll?limit=" + limit;
|
||||
|
||||
ResponseEntity<ApiResponse> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
ApiResponse.class
|
||||
);
|
||||
|
||||
if (response.getBody() != null && response.getBody().getCode() == 0) {
|
||||
return response.getBody().getData();
|
||||
}
|
||||
|
||||
throw new RuntimeException("获取通知失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
NoticeApiClient client = new NoticeApiClient(
|
||||
"http://your-domain",
|
||||
"202501270001",
|
||||
"550e8400-e29b-41d4-a716-446655440001"
|
||||
);
|
||||
|
||||
List<NoticeScreenVo> notices = client.getScrollNotices(20);
|
||||
```
|
||||
|
||||
## 🎨 CSS 样式示例
|
||||
|
||||
```css
|
||||
/* 滚动通知容器 */
|
||||
.notice-container {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: linear-gradient(90deg, #f8f9fa, #e9ecef);
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 通知项 */
|
||||
.notice-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
height: 100%;
|
||||
animation: slideIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* 通知标题 */
|
||||
.notice-title {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 通知时间 */
|
||||
.notice-time {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
/* 滑动动画 */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.notice-container {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.notice-time {
|
||||
display: none; /* 移动端隐藏时间 */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 安全要求
|
||||
1. **密钥保护**: 请妥善保管 AppSecret,不要泄露给第三方
|
||||
2. **HTTPS 传输**: 建议使用 HTTPS 协议进行数据传输
|
||||
3. **访问频率**: 建议控制调用频率,避免频繁请求
|
||||
|
||||
### 技术限制
|
||||
1. **返回条数**: 单次请求最多返回 50 条通知
|
||||
2. **数据时效**: 只返回已发布的公告信息
|
||||
3. **排序规则**: 按置顶状态和创建时间倒序排列
|
||||
|
||||
### 错误处理
|
||||
1. **认证失败**: 检查 AppId 和 AppSecret 是否正确
|
||||
2. **网络异常**: 实现重试机制和超时处理
|
||||
3. **数据异常**: 对返回数据进行有效性验证
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 1. 认证失败
|
||||
**错误信息**: "无效的应用ID或密钥"
|
||||
**解决方案**:
|
||||
- 检查 AppId 和 AppSecret 是否正确
|
||||
- 确认应用是否已启用
|
||||
- 联系系统管理员验证配置
|
||||
|
||||
#### 2. 网络超时
|
||||
**错误信息**: 请求超时或无响应
|
||||
**解决方案**:
|
||||
- 检查网络连接
|
||||
- 确认服务器地址是否正确
|
||||
- 实现请求超时和重试机制
|
||||
|
||||
#### 3. 数据为空
|
||||
**可能原因**:
|
||||
- 当前没有已发布的公告
|
||||
- 查询参数设置不当
|
||||
- 系统维护中
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如果在集成过程中遇到问题,请联系技术支持:
|
||||
|
||||
- **技术文档**: 本文档
|
||||
- **API 地址**: `/notice/external/scroll`
|
||||
- **认证方式**: AppId + AppSecret
|
||||
- **数据格式**: JSON
|
||||
|
||||
---
|
||||
|
||||
*最后更新时间: 2025-01-27*
|
||||
*版本: v1.0*
|
||||
@@ -1,230 +0,0 @@
|
||||
# 会议室预约流程动态审批人实现示例
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档展示如何在会议室预约的save方法中实现根据会议室管理员字段动态确定审批人的功能。
|
||||
|
||||
## 🔧 核心修改
|
||||
|
||||
### 1. 添加必要的依赖注入
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class MeetingBookingController {
|
||||
|
||||
private final MeetingBookingService meetingBookingService;
|
||||
private final MeetingRoomService meetingRoomService;
|
||||
private final RemoteFlowApiFlowService remoteFlowApiFlowService;
|
||||
private final RemoteUserService remoteUserService; // 新增:用于获取用户信息
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 修改save方法中的流程启动部分
|
||||
|
||||
```java
|
||||
@PostMapping
|
||||
@Transactional
|
||||
public R save(@RequestBody MeetingBookingSaveDto dto) {
|
||||
// ... 前面的验证逻辑保持不变 ...
|
||||
|
||||
// 7. 发起流程
|
||||
var processByBusinessKey = remoteFlowApiFlowService.getProcessByBusinessKey(IntegratedFlowConstant.INTEGRATED_MEETING_BOOKING);
|
||||
if (!processByBusinessKey.isOk() || processByBusinessKey.getData() == null) {
|
||||
throw new ServiceException("流程配置获取失败: " + processByBusinessKey.getMsg());
|
||||
}
|
||||
Process process = processByBusinessKey.getData();
|
||||
|
||||
// 构建流程参数
|
||||
MeetingFlowDto meetingFlowDto = new MeetingFlowDto();
|
||||
meetingFlowDto.setRoomName(room.getRoomName());
|
||||
meetingFlowDto.setApplicantName(SecurityUtils.getUser().getUsername());
|
||||
meetingFlowDto.setMeetingTime(bookingDate + " " + startTime + "-" + endTime);
|
||||
Map<String, Object> stringObjectMap = BeanUtil.beanToMap(meetingFlowDto);
|
||||
|
||||
// 🔥 关键:动态设置审批人 - 根据会议室管理员
|
||||
List<NodeUser> approvers = determineApproversByRoomAdmin(room);
|
||||
if (!approvers.isEmpty()) {
|
||||
// 注意:这里的 "approval_node_1" 需要根据实际流程设计中的节点ID调整
|
||||
stringObjectMap.put("approval_node_1_assignee_select", approvers);
|
||||
}
|
||||
|
||||
ProcessInstanceParamDto processInstanceParamDto = new ProcessInstanceParamDto();
|
||||
processInstanceParamDto.setFlowId(process.getFlowId());
|
||||
processInstanceParamDto.setParamMap(stringObjectMap);
|
||||
var stringR = remoteFlowApiFlowService.startProcessInstance(processInstanceParamDto);
|
||||
|
||||
// ... 后续逻辑保持不变 ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 实现动态审批人确定方法
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据会议室管理员确定审批人
|
||||
* @param room 会议室信息
|
||||
* @return 审批人列表
|
||||
*/
|
||||
private List<NodeUser> determineApproversByRoomAdmin(MeetingRoomEntity room) {
|
||||
List<NodeUser> approvers = new ArrayList<>();
|
||||
|
||||
// 1. 优先使用会议室指定的管理员
|
||||
if (room.getAdminId() != null) {
|
||||
try {
|
||||
// 获取管理员用户信息
|
||||
R<List<SysUser>> userResult = remoteUserService.getUserById(Arrays.asList(room.getAdminId()));
|
||||
if (userResult.isOk() && !userResult.getData().isEmpty()) {
|
||||
SysUser adminUser = userResult.getData().get(0);
|
||||
NodeUser approver = NodeUser.builder()
|
||||
.id(adminUser.getUserId())
|
||||
.name(adminUser.getName())
|
||||
.type("user")
|
||||
.selected(true)
|
||||
.avatar(adminUser.getAvatar())
|
||||
.build();
|
||||
approvers.add(approver);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取会议室管理员信息失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 如果会议室没有管理员,使用部门经理
|
||||
if (approvers.isEmpty() && room.getDeptId() != null) {
|
||||
approvers.addAll(getDeptManagerAsApprovers(room.getDeptId()));
|
||||
}
|
||||
|
||||
// 3. 如果仍然没有审批人,使用默认审批人
|
||||
if (approvers.isEmpty()) {
|
||||
approvers.addAll(getDefaultApprovers());
|
||||
}
|
||||
|
||||
return approvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门经理作为审批人
|
||||
*/
|
||||
private List<NodeUser> getDeptManagerAsApprovers(Long deptId) {
|
||||
List<NodeUser> approvers = new ArrayList<>();
|
||||
try {
|
||||
// 这里需要根据实际的部门服务接口获取部门经理
|
||||
// 示例:假设有部门服务可以获取部门经理
|
||||
/*
|
||||
R<SysDept> deptResult = remoteDeptService.getDeptById(deptId);
|
||||
if (deptResult.isOk() && deptResult.getData() != null) {
|
||||
SysDept dept = deptResult.getData();
|
||||
Long managerId = dept.getManagerId();
|
||||
if (managerId != null) {
|
||||
R<List<SysUser>> userResult = remoteUserService.getUserById(Arrays.asList(managerId));
|
||||
if (userResult.isOk() && !userResult.getData().isEmpty()) {
|
||||
SysUser manager = userResult.getData().get(0);
|
||||
NodeUser approver = NodeUser.builder()
|
||||
.id(manager.getUserId())
|
||||
.name(manager.getName())
|
||||
.type("user")
|
||||
.selected(true)
|
||||
.avatar(manager.getAvatar())
|
||||
.build();
|
||||
approvers.add(approver);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (Exception e) {
|
||||
log.error("获取部门经理信息失败: {}", e.getMessage());
|
||||
}
|
||||
return approvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认审批人
|
||||
*/
|
||||
private List<NodeUser> getDefaultApprovers() {
|
||||
List<NodeUser> approvers = new ArrayList<>();
|
||||
try {
|
||||
// 设置系统管理员作为默认审批人
|
||||
Long systemAdminId = 1L; // 需要替换为实际的系统管理员ID
|
||||
NodeUser defaultApprover = NodeUser.builder()
|
||||
.id(systemAdminId)
|
||||
.name("系统管理员")
|
||||
.type("user")
|
||||
.selected(true)
|
||||
.build();
|
||||
approvers.add(defaultApprover);
|
||||
} catch (Exception e) {
|
||||
log.error("获取默认审批人失败: {}", e.getMessage());
|
||||
}
|
||||
return approvers;
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 关键要点
|
||||
|
||||
### 1. 流程节点ID配置
|
||||
```java
|
||||
// 这个key的格式是:{节点ID}_assignee_select
|
||||
stringObjectMap.put("approval_node_1_assignee_select", approvers);
|
||||
```
|
||||
- `approval_node_1` 需要替换为实际流程设计中的审批节点ID
|
||||
- 节点ID可以在流程设计器中查看
|
||||
|
||||
### 2. NodeUser对象构建
|
||||
```java
|
||||
NodeUser approver = NodeUser.builder()
|
||||
.id(adminUser.getUserId()) // 用户ID
|
||||
.name(adminUser.getName()) // 用户姓名
|
||||
.type("user") // 用户类型,固定为"user"
|
||||
.selected(true) // 是否选中
|
||||
.avatar(adminUser.getAvatar()) // 用户头像(可选)
|
||||
.build();
|
||||
```
|
||||
|
||||
### 3. 审批人优先级
|
||||
1. **会议室管理员** - 最高优先级
|
||||
2. **部门经理** - 次优先级
|
||||
3. **默认审批人** - 兜底方案
|
||||
|
||||
### 4. 错误处理
|
||||
- 所有获取用户信息的操作都有异常处理
|
||||
- 确保即使获取审批人失败,流程也能正常启动
|
||||
- 使用日志记录错误信息,便于排查问题
|
||||
|
||||
## 🔄 流程设计配置
|
||||
|
||||
在流程设计器中,需要将审批节点配置为:
|
||||
- **分配类型**: `SELF_SELECT` (发起人自选)
|
||||
- **节点ID**: 与代码中的key保持一致
|
||||
- **多实例模式**: 根据需要选择会签/或签/顺签
|
||||
|
||||
## 📝 使用示例
|
||||
|
||||
```java
|
||||
// 会议室预约时,系统会自动:
|
||||
// 1. 检查会议室是否有管理员
|
||||
// 2. 如果有管理员,使用管理员作为审批人
|
||||
// 3. 如果没有管理员,使用部门经理
|
||||
// 4. 如果都没有,使用系统管理员
|
||||
|
||||
// 流程启动后,审批人会收到待办任务
|
||||
// 审批人可以在流程中看到会议预约的详细信息
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **节点ID一致性**: 确保代码中的节点ID与流程设计中的节点ID一致
|
||||
2. **用户服务可用性**: 确保用户服务接口可用,避免获取用户信息失败
|
||||
3. **权限控制**: 确保选择的审批人有相应的审批权限
|
||||
4. **测试验证**: 建议在测试环境先验证流程是否正常工作
|
||||
|
||||
## 🚀 扩展功能
|
||||
|
||||
可以根据业务需求进一步扩展:
|
||||
|
||||
1. **多级审批**: 根据会议室等级设置多级审批
|
||||
2. **时间限制**: 根据预约时间长度确定审批人
|
||||
3. **部门规则**: 根据部门特殊规则确定审批人
|
||||
4. **用户角色**: 根据用户角色确定审批人
|
||||
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
# 移动端摆放形式选择接口使用说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档介绍移动端预约课程时选择摆放形式的接口使用方法。
|
||||
|
||||
## 🌐 接口信息
|
||||
|
||||
### 获取摆放形式列表接口
|
||||
|
||||
- **接口地址**: `GET /meetingDeskLayout/app/list`
|
||||
- **请求方式**: GET
|
||||
- **认证方式**: 无需认证(移动端公开接口)
|
||||
- **数据格式**: JSON
|
||||
|
||||
## 📡 响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"layoutName": "U型摆放",
|
||||
"layoutImage": "https://example.com/images/u-layout.jpg",
|
||||
"capacityMin": 8,
|
||||
"capacityMax": 12,
|
||||
"description": "适合小组讨论的U型摆放方式"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"layoutName": "课桌式摆放",
|
||||
"layoutImage": "https://example.com/images/classroom-layout.jpg",
|
||||
"capacityMin": 20,
|
||||
"capacityMax": 50,
|
||||
"description": "传统课桌式摆放,适合培训课程"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"layoutName": "圆桌式摆放",
|
||||
"layoutImage": "https://example.com/images/round-table-layout.jpg",
|
||||
"capacityMin": 6,
|
||||
"capacityMax": 10,
|
||||
"description": "圆桌式摆放,促进互动交流"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | Long | 摆放形式ID |
|
||||
| layoutName | String | 摆放形式名称 |
|
||||
| layoutImage | String | 摆放形式示意图URL |
|
||||
| capacityMin | Integer | 最小容纳人数 |
|
||||
| capacityMax | Integer | 最大容纳人数 |
|
||||
| description | String | 摆放形式描述 |
|
||||
|
||||
## 🔍 业务逻辑说明
|
||||
|
||||
1. **数据过滤**: 只返回状态为"可用"(isAvailable = "0")的摆放形式
|
||||
2. **排序规则**: 按照sortOrder字段升序排列
|
||||
3. **数据精简**: 只返回移动端需要的核心字段,减少数据传输量
|
||||
|
||||
## 📱 使用示例
|
||||
|
||||
### JavaScript 调用示例
|
||||
```javascript
|
||||
fetch('/meetingDeskLayout/app/list', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.code === 0) {
|
||||
// 处理摆放形式数据
|
||||
const layouts = data.data;
|
||||
layouts.forEach(layout => {
|
||||
console.log(`摆放形式: ${layout.layoutName}, 容纳人数: ${layout.capacityMin}-${layout.capacityMax}人`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取摆放形式失败:', error);
|
||||
});
|
||||
```
|
||||
|
||||
### React Native 调用示例
|
||||
```javascript
|
||||
const fetchDeskLayouts = async () => {
|
||||
try {
|
||||
const response = await fetch('/meetingDeskLayout/app/list');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
return result.data;
|
||||
} else {
|
||||
throw new Error(result.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取摆放形式失败:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### cURL 调用示例
|
||||
```bash
|
||||
curl -X GET "http://your-domain/meetingDeskLayout/app/list" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
## 🎯 应用场景
|
||||
|
||||
1. **课程预约**: 用户在预约课程时选择适合的摆放形式
|
||||
2. **会议室配置**: 根据参会人数推荐合适的摆放形式
|
||||
3. **活动策划**: 根据活动类型选择合适的桌椅摆放方式
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **数据一致性**: 接口返回的数据与后台管理系统的摆放形式数据保持一致
|
||||
2. **图片加载**: layoutImage字段可能为空,前端需要处理图片加载失败的情况
|
||||
3. **容量限制**: 建议根据实际参会人数选择合适的摆放形式
|
||||
4. **缓存策略**: 建议前端对摆放形式数据进行适当缓存,减少重复请求
|
||||
|
||||
## 🔄 版本更新
|
||||
|
||||
- **v1.0.0** (2025-01-27): 初始版本,提供基本的摆放形式选择功能
|
||||
|
||||
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
# 通知公告过期时间功能使用指南
|
||||
|
||||
## 📋 功能概述
|
||||
|
||||
通知公告系统新增过期时间功能,支持为公告设置具体的过期时间。到期后,公告将自动下架,不再在前端显示,无需手动操作。
|
||||
|
||||
## ✨ 主要特性
|
||||
|
||||
- **自动过期**: 支持设置具体过期时间,到期自动下架
|
||||
- **灵活配置**: 可选择设置过期时间或永不过期
|
||||
- **自动处理**: 系统定时任务自动处理过期公告
|
||||
- **智能过滤**: 所有查询接口自动过滤已过期公告
|
||||
- **性能优化**: 添加数据库索引,提高查询效率
|
||||
|
||||
## 🗄️ 数据库变更
|
||||
|
||||
### 新增字段
|
||||
```sql
|
||||
ALTER TABLE notice ADD COLUMN expire_time DATETIME COMMENT '过期时间,到期后公告自动下架';
|
||||
```
|
||||
|
||||
### 新增索引
|
||||
```sql
|
||||
-- 过期时间索引
|
||||
CREATE INDEX idx_notice_expire_time ON notice(expire_time);
|
||||
|
||||
-- 状态+过期时间复合索引
|
||||
CREATE INDEX idx_notice_status_expire ON notice(status, expire_time);
|
||||
```
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 1. 启用定时任务
|
||||
在启动类或配置类上添加注解:
|
||||
```java
|
||||
@EnableScheduling
|
||||
```
|
||||
|
||||
### 2. 定时任务配置
|
||||
系统默认每天凌晨2点执行过期处理:
|
||||
```java
|
||||
@Scheduled(cron = "0 0 2 * * ?")
|
||||
```
|
||||
|
||||
如需调整执行时间,可修改 `NoticeExpirationTask` 中的 cron 表达式。
|
||||
|
||||
## 💻 使用方法
|
||||
|
||||
### 1. 创建公告时设置过期时间
|
||||
|
||||
#### 前端表单示例
|
||||
```html
|
||||
<div class="form-group">
|
||||
<label>过期时间</label>
|
||||
<input type="datetime-local" name="expireTime" class="form-control">
|
||||
<small class="form-text text-muted">
|
||||
不设置表示永不过期,设置后到期自动下架
|
||||
</small>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 后端实体字段
|
||||
```java
|
||||
@Schema(description="过期时间,到期后公告自动下架")
|
||||
private LocalDateTime expireTime;
|
||||
```
|
||||
|
||||
### 2. 过期时间设置建议
|
||||
|
||||
#### 短期公告(1-7天)
|
||||
- 活动通知
|
||||
- 临时维护公告
|
||||
- 节日祝福
|
||||
|
||||
#### 中期公告(1-30天)
|
||||
- 功能更新通知
|
||||
- 政策变更公告
|
||||
- 培训通知
|
||||
|
||||
#### 长期公告(30天以上)
|
||||
- 规章制度
|
||||
- 长期政策
|
||||
- 重要通知
|
||||
|
||||
#### 永不过期
|
||||
- 系统说明文档
|
||||
- 基础操作指南
|
||||
- 重要规章制度
|
||||
|
||||
## 🔍 查询逻辑
|
||||
|
||||
### 自动过滤规则
|
||||
所有查询接口都会自动过滤已过期公告:
|
||||
|
||||
```java
|
||||
// 过滤已过期的公告
|
||||
wrapper.and(w -> w.isNull(NoticeEntity::getExpireTime)
|
||||
.or()
|
||||
.gt(NoticeEntity::getExpireTime, LocalDateTime.now()));
|
||||
```
|
||||
|
||||
### 查询逻辑说明
|
||||
- `expireTime IS NULL`: 永不过期的公告
|
||||
- `expireTime > NOW()`: 未过期的公告
|
||||
- `expireTime <= NOW()`: 已过期的公告(自动过滤)
|
||||
|
||||
## ⏰ 定时任务详情
|
||||
|
||||
### 执行时间
|
||||
- **频率**: 每天执行一次
|
||||
- **时间**: 凌晨2点(可配置)
|
||||
- **时区**: 系统默认时区
|
||||
|
||||
### 处理逻辑
|
||||
1. 查询已过期但状态仍为"已发布"的公告
|
||||
2. 批量更新状态为"已下架"
|
||||
3. 记录处理日志
|
||||
4. 异常处理和错误日志
|
||||
|
||||
### 日志示例
|
||||
```
|
||||
2025-01-27 02:00:00 INFO - 开始执行通知公告过期处理任务
|
||||
2025-01-27 02:00:01 INFO - 发现 3 条过期公告,开始处理
|
||||
2025-01-27 02:00:01 INFO - 公告 [系统维护通知] 已过期,状态已更新为已下架
|
||||
2025-01-27 02:00:01 INFO - 通知公告过期处理任务执行完成,共处理 3 条过期公告
|
||||
```
|
||||
|
||||
## 🚀 性能优化
|
||||
|
||||
### 数据库索引
|
||||
- 过期时间字段索引,提高过期查询性能
|
||||
- 状态+过期时间复合索引,优化常用查询
|
||||
|
||||
### 查询优化
|
||||
- 使用 MyBatis-Plus 的 Lambda 查询
|
||||
- 批量更新过期公告状态
|
||||
- 避免 N+1 查询问题
|
||||
|
||||
## 📊 监控和维护
|
||||
|
||||
### 1. 日志监控
|
||||
- 定时任务执行日志
|
||||
- 过期公告处理数量
|
||||
- 异常错误日志
|
||||
|
||||
### 2. 数据统计
|
||||
```sql
|
||||
-- 统计过期公告数量
|
||||
SELECT COUNT(*) FROM notice
|
||||
WHERE expire_time IS NOT NULL
|
||||
AND expire_time <= NOW()
|
||||
AND status = '0';
|
||||
|
||||
-- 统计即将过期的公告
|
||||
SELECT COUNT(*) FROM notice
|
||||
WHERE expire_time IS NOT NULL
|
||||
AND expire_time BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 7 DAY)
|
||||
AND status = '0';
|
||||
```
|
||||
|
||||
### 3. 定期维护
|
||||
- 清理过期的过期时间记录
|
||||
- 监控定时任务执行情况
|
||||
- 优化数据库查询性能
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 时区问题
|
||||
- 确保系统时区设置正确
|
||||
- 过期时间以系统时区为准
|
||||
- 考虑跨时区部署的情况
|
||||
|
||||
### 2. 性能考虑
|
||||
- 大量公告时,定时任务执行时间可能较长
|
||||
- 建议在业务低峰期执行
|
||||
- 监控数据库性能影响
|
||||
|
||||
### 3. 数据一致性
|
||||
- 定时任务执行期间,公告状态可能短暂不一致
|
||||
- 查询接口始终过滤过期公告,保证显示一致性
|
||||
- 建议在定时任务执行期间避免重要操作
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 1. 定时任务未执行
|
||||
**检查项**:
|
||||
- 是否启用 `@EnableScheduling`
|
||||
- 定时任务类是否被 Spring 管理
|
||||
- cron 表达式是否正确
|
||||
- 系统时间是否正确
|
||||
|
||||
### 2. 过期公告仍显示
|
||||
**检查项**:
|
||||
- 过期时间字段是否正确设置
|
||||
- 数据库索引是否创建
|
||||
- 查询逻辑是否正确
|
||||
- 定时任务是否正常执行
|
||||
|
||||
### 3. 性能问题
|
||||
**优化建议**:
|
||||
- 检查数据库索引使用情况
|
||||
- 优化查询语句
|
||||
- 调整定时任务执行时间
|
||||
- 考虑分页处理大量数据
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如遇到问题,请联系技术支持并提供以下信息:
|
||||
- 错误日志
|
||||
- 数据库表结构
|
||||
- 定时任务配置
|
||||
- 系统环境信息
|
||||
|
||||
---
|
||||
|
||||
*最后更新时间: 2025-01-27*
|
||||
*版本: v1.0*
|
||||
@@ -1,98 +0,0 @@
|
||||
# 外部系统滚动通知接口使用说明
|
||||
|
||||
## 接口概述
|
||||
|
||||
提供安全的API访问,供其他系统获取滚动通知信息。
|
||||
|
||||
## 接口信息
|
||||
|
||||
- **接口地址**: `GET /notice/external/scroll`
|
||||
- **请求方式**: GET
|
||||
- **认证方式**: 请求头认证
|
||||
|
||||
## 请求参数
|
||||
|
||||
### 请求头
|
||||
| 参数名 | 必填 | 说明 |
|
||||
|--------|------|------|
|
||||
| X-App-Id | 是 | 应用ID,在配置文件中配置 |
|
||||
| X-App-Secret | 是 | 应用密钥,在配置文件中配置 |
|
||||
|
||||
### 查询参数
|
||||
| 参数名 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| limit | 否 | 20 | 返回条数,最大50条 |
|
||||
|
||||
## 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"title": "通知标题",
|
||||
"createTime": "2025-01-27 10:00:00",
|
||||
"description": "通知描述"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JavaScript 调用示例
|
||||
```javascript
|
||||
fetch('/notice/external/scroll?limit=15', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-App-Id': 'external_app_001',
|
||||
'X-App-Secret': 'secret_2024_001'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.code === 0) {
|
||||
// 处理通知数据
|
||||
console.log(data.data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取通知失败:', error);
|
||||
});
|
||||
```
|
||||
|
||||
### cURL 调用示例
|
||||
```bash
|
||||
curl -X GET "http://your-domain/notice/external/scroll?limit=10" \
|
||||
-H "X-App-Id: external_app_001" \
|
||||
-H "X-App-Secret: secret_2024_001"
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
在 `application.yml` 中添加以下配置:
|
||||
|
||||
```yaml
|
||||
pjcloud:
|
||||
external-app:
|
||||
apps:
|
||||
- appId: "your_app_id"
|
||||
appSecret: "your_app_secret"
|
||||
appName: "你的应用名称"
|
||||
enabled: true
|
||||
```
|
||||
|
||||
## 安全特性
|
||||
|
||||
1. **应用认证**: 通过 appId 和 appSecret 验证调用方身份
|
||||
2. **访问控制**: 只返回已发布的公告
|
||||
3. **数据限制**: 限制最大返回条数,防止恶意调用
|
||||
4. **信息脱敏**: 只返回必要的滚动信息
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 请妥善保管应用密钥,不要泄露给第三方
|
||||
2. 建议使用HTTPS协议进行传输
|
||||
3. 可以根据需要调整返回条数限制
|
||||
4. 支持动态启用/禁用应用访问权限
|
||||
+15
-2
@@ -30,16 +30,16 @@ import com.bsd.pjcloud.integrated.constant.IntegratedFlowConstant;
|
||||
import com.bsd.pjcloud.integrated.dto.flow.MeetingFlowDto;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
|
||||
import com.bsd.pjcloud.common.core.exception.ServiceException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.bsd.pjcloud.integrated.dto.meeting.MeetingBookingSaveDto;
|
||||
import com.bsd.pjcloud.integrated.dto.meeting.MeetingStatsQueryDto;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.MeetingBookingPageVo;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.MeetingStatsVo;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
@@ -264,4 +264,17 @@ public class MeetingBookingController {
|
||||
// meetingBookingService.noticeVisitDesk(bookingId);
|
||||
// return R.ok();
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取会议统计信息
|
||||
* @param queryDto 查询参数
|
||||
* @return 统计结果
|
||||
*/
|
||||
@Operation(summary = "获取会议统计信息", description = "获取会议统计信息,支持按时间、部门、负责人等维度统计")
|
||||
@GetMapping("/stats")
|
||||
@PreAuthorize("@pms.hasPermission('integrated_meetingBooking_view')")
|
||||
public R<MeetingStatsVo> getMeetingStats(@ParameterObject MeetingStatsQueryDto queryDto) {
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
return R.ok(stats);
|
||||
}
|
||||
}
|
||||
+182
-21
@@ -6,16 +6,19 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysUser;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteUserService;
|
||||
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
|
||||
import com.bsd.pjcloud.common.core.exception.ServiceException;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.idempotent.annotation.Idempotent;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.common.security.annotation.Inner;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.flow.task.api.feign.RemoteFlowApiFlowService;
|
||||
import com.bsd.pjcloud.flow.task.api.feign.RemoteFlowEngineService;
|
||||
import com.bsd.pjcloud.flow.task.constant.TaskCheckStatusEnum;
|
||||
import com.bsd.pjcloud.flow.task.dto.ProcessInstanceParamDto;
|
||||
import com.bsd.pjcloud.flow.task.dto.TaskParamDto;
|
||||
import com.bsd.pjcloud.flow.task.dto.ProcessNodeRecordParamDto;
|
||||
import com.bsd.pjcloud.flow.task.entity.Process;
|
||||
import com.bsd.pjcloud.flow.task.utils.GetCheckReasonUtils;
|
||||
@@ -35,6 +38,7 @@ import com.bsd.pjcloud.integrated.service.MeetingRoomService;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.app.MeetingBookingDetailVo;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.app.MeetingBookingListVo;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.app.MeetingBookingReqVo;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.app.MeetingRoomBookingDetailVo;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.app.MeetingRoomPageVo;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.app.TimeSlotVo;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -48,15 +52,10 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.time.LocalTime;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 会议室信息表
|
||||
@@ -81,6 +80,11 @@ public class MeetingRoomController {
|
||||
|
||||
private final RemoteFlowApiFlowService remoteFlowApiFlowService;
|
||||
|
||||
private final RemoteUserService remoteUserService;
|
||||
|
||||
private final RemoteFlowEngineService remoteFlowEngineService;
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
@@ -472,6 +476,17 @@ public class MeetingRoomController {
|
||||
if (!Objects.equals(booking.getApplicantId(), userId)) {
|
||||
return R.failed("无权取消他人预约");
|
||||
}
|
||||
// 先尝试终止流程实例(如果存在且在审批中)
|
||||
String processInstanceId = booking.getProcessInstanceId();
|
||||
if (processInstanceId != null && !processInstanceId.isEmpty()) {
|
||||
TaskParamDto dto = new TaskParamDto();
|
||||
dto.setProcessInstanceIdList(java.util.List.of(processInstanceId));
|
||||
var stopRet = remoteFlowEngineService.stopProcessInstance(dto);
|
||||
if (!stopRet.isOk()) {
|
||||
return R.failed("取消审批流程失败: " + stopRet.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置checkStatus为4(已取消)
|
||||
booking.setCheckStatus("4");
|
||||
boolean updated = meetingBookingService.updateById(booking);
|
||||
@@ -603,24 +618,170 @@ public class MeetingRoomController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 会议室下拉选择(仅返回可用会议室id和名称)
|
||||
* 会议室预约详情页面 - 包含时间段状态和当日预约列表
|
||||
* @param roomId 会议室ID
|
||||
* @param date 查询日期
|
||||
* @return 会议室预约详情
|
||||
*/
|
||||
@GetMapping("/app/select")
|
||||
public R<List<Map<String, Object>>> selectMeetingRooms() {
|
||||
LambdaQueryWrapper<MeetingRoomEntity> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(MeetingRoomEntity::getStatus, YesNoEnum.NO.getCode()); // 只查可用
|
||||
wrapper.select(MeetingRoomEntity::getId, MeetingRoomEntity::getRoomName);
|
||||
List<MeetingRoomEntity> list = meetingRoomService.list(wrapper);
|
||||
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (MeetingRoomEntity room : list) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", room.getId());
|
||||
map.put("name", room.getRoomName());
|
||||
result.add(map);
|
||||
@GetMapping("/app/booking-detail-page/{roomId}")
|
||||
@Operation(summary = "会议室预约详情页面", description = "获取指定会议室在指定日期的预约详情,包含时间段状态和预约列表")
|
||||
public R<MeetingRoomBookingDetailVo> getMeetingRoomBookingDetail(@PathVariable("roomId") Long roomId,
|
||||
@RequestParam("date") String date) {
|
||||
// 1. 验证会议室是否存在
|
||||
MeetingRoomEntity room = meetingRoomService.getById(roomId);
|
||||
if (room == null) {
|
||||
return R.failed("会议室不存在");
|
||||
}
|
||||
|
||||
// 2. 解析日期
|
||||
LocalDate queryDate = LocalDate.parse(date);
|
||||
|
||||
// 3. 生成时间段状态(8:00-18:00,每小时一个时间段)
|
||||
List<MeetingRoomBookingDetailVo.TimeSlotStatusVo> timeSlots = generateTimeSlots(queryDate, roomId);
|
||||
|
||||
// 4. 获取当日预约列表
|
||||
List<MeetingRoomBookingDetailVo.DailyBookingVo> dailyBookings = getDailyBookings(queryDate, roomId);
|
||||
|
||||
// 5. 构建返回结果
|
||||
MeetingRoomBookingDetailVo result = new MeetingRoomBookingDetailVo();
|
||||
result.setRoomId(roomId);
|
||||
result.setRoomName(room.getRoomName());
|
||||
result.setQueryDate(queryDate);
|
||||
result.setTimeSlots(timeSlots);
|
||||
result.setDailyBookings(dailyBookings);
|
||||
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成时间段状态(30分钟间隔)
|
||||
*/
|
||||
private List<MeetingRoomBookingDetailVo.TimeSlotStatusVo> generateTimeSlots(LocalDate date, Long roomId) {
|
||||
List<MeetingRoomBookingDetailVo.TimeSlotStatusVo> timeSlots = new ArrayList<>();
|
||||
|
||||
// 生成30分钟间隔的时间段(8:00-18:00)
|
||||
LocalTime startTime = LocalTime.of(8, 0);
|
||||
LocalTime endTime = LocalTime.of(18, 0);
|
||||
|
||||
while (startTime.isBefore(endTime)) {
|
||||
MeetingRoomBookingDetailVo.TimeSlotStatusVo slot = new MeetingRoomBookingDetailVo.TimeSlotStatusVo();
|
||||
slot.setStartTime(startTime.format(DateTimeFormatter.ofPattern("HH:mm")));
|
||||
slot.setEndTime(startTime.plusMinutes(30).format(DateTimeFormatter.ofPattern("HH:mm")));
|
||||
slot.setStatus("available");
|
||||
slot.setStatusText("可预约");
|
||||
timeSlots.add(slot);
|
||||
startTime = startTime.plusMinutes(30);
|
||||
}
|
||||
|
||||
// 获取当天的所有预约记录
|
||||
List<MeetingBookingEntity> bookings = meetingBookingService.list(
|
||||
Wrappers.<MeetingBookingEntity>lambdaQuery()
|
||||
.eq(MeetingBookingEntity::getRoomId, roomId)
|
||||
.eq(MeetingBookingEntity::getBookingDate, date)
|
||||
);
|
||||
|
||||
// 标记已预约的时间段
|
||||
for (MeetingBookingEntity booking : bookings) {
|
||||
LocalTime bookingStart = booking.getStartTime();
|
||||
LocalTime bookingEnd = booking.getEndTime();
|
||||
|
||||
// 找到对应的时间段并更新状态
|
||||
for (MeetingRoomBookingDetailVo.TimeSlotStatusVo slot : timeSlots) {
|
||||
LocalTime slotStart = LocalTime.parse(slot.getStartTime(), DateTimeFormatter.ofPattern("HH:mm"));
|
||||
LocalTime slotEnd = LocalTime.parse(slot.getEndTime(), DateTimeFormatter.ofPattern("HH:mm"));
|
||||
|
||||
// 修改判断逻辑:只有当时间段完全在预约时间范围内时才标记为已预约
|
||||
if (!slotStart.isBefore(bookingStart) && !slotEnd.isAfter(bookingEnd)) {
|
||||
slot.setStatus(getBookingStatus(booking.getCheckStatus()));
|
||||
slot.setStatusText(getBookingStatusText(booking.getCheckStatus()));
|
||||
|
||||
// 设置预约信息
|
||||
MeetingRoomBookingDetailVo.BookingInfoVo bookingInfo = new MeetingRoomBookingDetailVo.BookingInfoVo();
|
||||
bookingInfo.setTitle(booking.getTitle());
|
||||
bookingInfo.setApplicantName(getApplicantName(booking.getApplicantId()));
|
||||
bookingInfo.setCheckStatus(booking.getCheckStatus());
|
||||
bookingInfo.setCheckStatusText(getBookingStatusText(booking.getCheckStatus()));
|
||||
slot.setBookingInfo(bookingInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timeSlots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当日预约列表
|
||||
*/
|
||||
private List<MeetingRoomBookingDetailVo.DailyBookingVo> getDailyBookings(LocalDate date, Long roomId) {
|
||||
List<MeetingBookingEntity> bookings = meetingBookingService.list(
|
||||
Wrappers.<MeetingBookingEntity>lambdaQuery()
|
||||
.eq(MeetingBookingEntity::getRoomId, roomId)
|
||||
.eq(MeetingBookingEntity::getBookingDate, date)
|
||||
.orderByAsc(MeetingBookingEntity::getStartTime)
|
||||
);
|
||||
|
||||
return bookings.stream().map(booking -> {
|
||||
MeetingRoomBookingDetailVo.DailyBookingVo vo = new MeetingRoomBookingDetailVo.DailyBookingVo();
|
||||
vo.setId(booking.getId());
|
||||
vo.setTitle(booking.getTitle());
|
||||
vo.setApplicantName(getApplicantName(booking.getApplicantId()));
|
||||
vo.setStartTime(booking.getStartTime().format(DateTimeFormatter.ofPattern("HH:mm")));
|
||||
vo.setEndTime(booking.getEndTime().format(DateTimeFormatter.ofPattern("HH:mm")));
|
||||
vo.setStatus(booking.getCheckStatus());
|
||||
vo.setStatusText(getBookingStatusText(booking.getCheckStatus()));
|
||||
vo.setDescription(booking.getDescription());
|
||||
return vo;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审核状态获取预约状态
|
||||
*/
|
||||
private String getBookingStatus(String checkStatus) {
|
||||
if (checkStatus == null) return "available";
|
||||
switch (checkStatus) {
|
||||
case "1": return "reviewing"; // 审核中
|
||||
case "2": return "booked"; // 审核通过
|
||||
case "3": return "available"; // 审核拒绝,重新可预约
|
||||
case "4": return "available"; // 已取消,重新可预约
|
||||
case "5": return "available"; // 取消,重新可预约
|
||||
case "0": return "booked"; // 已到访
|
||||
default: return "available";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审核状态获取状态描述
|
||||
*/
|
||||
private String getBookingStatusText(String checkStatus) {
|
||||
if (checkStatus == null) return "可预约";
|
||||
switch (checkStatus) {
|
||||
case "1": return "审核中";
|
||||
case "2": return "审核通过";
|
||||
case "3": return "审核拒绝";
|
||||
case "4": return "已取消";
|
||||
case "5": return "取消";
|
||||
case "0": return "已到访";
|
||||
default: return "可预约";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请人姓名(这里简化处理,实际应该调用用户服务)
|
||||
*/
|
||||
private String getApplicantName(Long applicantId) {
|
||||
// TODO: 实际应该调用用户服务获取真实姓名
|
||||
|
||||
R<List<SysUser>> userById = remoteUserService.getUserById(Arrays.asList(applicantId));
|
||||
if (userById.isOk()) {
|
||||
List<SysUser> data = userById.getData();
|
||||
if (data != null && !data.isEmpty()) {
|
||||
SysUser user = data.get(0);
|
||||
return user.getName();
|
||||
}
|
||||
}
|
||||
return "用户" + applicantId;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.bsd.pjcloud.integrated.dto.meeting;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 会议统计查询参数
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "会议统计查询参数")
|
||||
public class MeetingStatsQueryDto {
|
||||
|
||||
/**
|
||||
* 开始日期
|
||||
*/
|
||||
@Schema(description = "开始日期")
|
||||
private LocalDate startDate;
|
||||
|
||||
/**
|
||||
* 结束日期
|
||||
*/
|
||||
@Schema(description = "结束日期")
|
||||
private LocalDate endDate;
|
||||
|
||||
/**
|
||||
* 统计维度:dept-按部门统计,assignee-按负责人统计,time-按时间统计
|
||||
*/
|
||||
@Schema(description = "统计维度:dept-按部门统计,assignee-按负责人统计,time-按时间统计")
|
||||
private String dimension;
|
||||
|
||||
/**
|
||||
* 时间粒度:day-按天,week-按周,month-按月
|
||||
*/
|
||||
@Schema(description = "时间粒度:day-按天,week-按周,month-按月")
|
||||
private String timeGranularity;
|
||||
|
||||
/**
|
||||
* 部门ID(可选,用于筛选特定部门)
|
||||
*/
|
||||
@Schema(description = "部门ID(可选,用于筛选特定部门)")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 会议室ID(可选,用于筛选特定会议室)
|
||||
*/
|
||||
@Schema(description = "会议室ID(可选,用于筛选特定会议室)")
|
||||
private Long roomId;
|
||||
}
|
||||
|
||||
+48
-3
@@ -7,12 +7,57 @@ import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface MeetingBookingMapper extends PjcloudBaseMapper<MeetingBookingEntity> {
|
||||
|
||||
|
||||
|
||||
|
||||
List<MeetingBookingEntity> findByRoomIdAndDate(Long roomId, @Param("date") LocalDate date);
|
||||
|
||||
/**
|
||||
* 获取总体统计信息
|
||||
*/
|
||||
Map<String, Object> getOverallStats(@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
@Param("deptId") Long deptId,
|
||||
@Param("roomId") Long roomId);
|
||||
|
||||
/**
|
||||
* 按部门统计
|
||||
*/
|
||||
List<Map<String, Object>> getDeptStats(@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
@Param("roomId") Long roomId);
|
||||
|
||||
/**
|
||||
* 按负责人统计
|
||||
*/
|
||||
List<Map<String, Object>> getAssigneeStats(@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
@Param("deptId") Long deptId,
|
||||
@Param("roomId") Long roomId);
|
||||
|
||||
/**
|
||||
* 按时间统计
|
||||
*/
|
||||
List<Map<String, Object>> getTimeStats(@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
@Param("timeGranularity") String timeGranularity,
|
||||
@Param("deptId") Long deptId,
|
||||
@Param("roomId") Long roomId);
|
||||
|
||||
/**
|
||||
* 按状态统计
|
||||
*/
|
||||
List<Map<String, Object>> getStatusStats(@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
@Param("deptId") Long deptId,
|
||||
@Param("roomId") Long roomId);
|
||||
|
||||
/**
|
||||
* 按会议室统计
|
||||
*/
|
||||
List<Map<String, Object>> getRoomStats(@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate,
|
||||
@Param("deptId") Long deptId);
|
||||
}
|
||||
+9
@@ -3,7 +3,9 @@ package com.bsd.pjcloud.integrated.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.integrated.dto.meeting.MeetingStatsQueryDto;
|
||||
import com.bsd.pjcloud.integrated.entity.MeetingBookingEntity;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.MeetingStatsVo;
|
||||
|
||||
public interface MeetingBookingService extends IService<MeetingBookingEntity> {
|
||||
|
||||
@@ -37,4 +39,11 @@ public interface MeetingBookingService extends IService<MeetingBookingEntity> {
|
||||
* @param
|
||||
*/
|
||||
void noticeVisitor(MeetingBookingEntity meetingBookingEntity);
|
||||
|
||||
/**
|
||||
* 获取会议统计信息
|
||||
* @param queryDto 查询参数
|
||||
* @return 统计结果
|
||||
*/
|
||||
MeetingStatsVo getMeetingStats(MeetingStatsQueryDto queryDto);
|
||||
}
|
||||
+119
-7
@@ -3,21 +3,31 @@ package com.bsd.pjcloud.integrated.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.core.util.WebUtils;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysDept;
|
||||
import com.bsd.pjcloud.admin.api.feign.RemoteDeptService;
|
||||
import com.bsd.pjcloud.integrated.dto.meeting.MeetingStatsQueryDto;
|
||||
import com.bsd.pjcloud.integrated.entity.*;
|
||||
import com.bsd.pjcloud.integrated.mapper.CleanUserMapper;
|
||||
import com.bsd.pjcloud.integrated.mapper.MeetingBookingMapper;
|
||||
import com.bsd.pjcloud.integrated.mapper.MeetingRoomMapper;
|
||||
import com.bsd.pjcloud.integrated.service.MeetingBookingService;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.MeetingStatsVo;
|
||||
import com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartySmsService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 会议室预约
|
||||
@@ -34,6 +44,8 @@ public class MeetingBookingServiceImpl extends ServiceImpl<MeetingBookingMapper,
|
||||
private final MeetingRoomMapper meetingRoomMapper;
|
||||
|
||||
private final CleanUserMapper cleanUserMapper;
|
||||
|
||||
private final RemoteDeptService remoteDeptService;
|
||||
@Override
|
||||
public Page pageScope(Page page, LambdaQueryWrapper<MeetingBookingEntity> wrapper) {
|
||||
return baseMapper.selectPageByScope(page, wrapper, DataScope.of());
|
||||
@@ -42,18 +54,19 @@ public class MeetingBookingServiceImpl extends ServiceImpl<MeetingBookingMapper,
|
||||
@Override
|
||||
public Boolean confirmEndMeeting(Long bookingId) {
|
||||
Long currentUserId = SecurityUtils.getUser().getId();
|
||||
MeetingBookingEntity booking = getById(bookingId);
|
||||
//更新会议预约结束状态
|
||||
MeetingBookingEntity booking = new MeetingBookingEntity();
|
||||
MeetingBookingEntity updateBooking = new MeetingBookingEntity();
|
||||
if (!booking.getApplicantId().equals(currentUserId)){
|
||||
throw new RuntimeException("无权限操作");
|
||||
}
|
||||
booking.setId(bookingId);
|
||||
booking.setIsConfirmEnd(1);
|
||||
boolean updated = updateById(booking);
|
||||
updateBooking.setId(bookingId);
|
||||
updateBooking.setIsConfirmEnd(1);
|
||||
boolean updated = updateById(updateBooking);
|
||||
//查询会议室阿姨信息
|
||||
MeetingRoomEntity meetingRoomEntity = meetingRoomMapper.selectById(booking.getRoomId());
|
||||
Long cleanUserId = meetingRoomEntity.getCleanUserId();
|
||||
if (cleanUserId != null){
|
||||
MeetingRoomEntity meetingRoomEntity = meetingRoomMapper.selectById(updateBooking.getRoomId());
|
||||
if (meetingRoomEntity != null){
|
||||
Long cleanUserId = meetingRoomEntity.getCleanUserId();
|
||||
CleanUserEntity cleanUserEntity = cleanUserMapper.selectById(cleanUserId);
|
||||
//发送短信通知阿姨
|
||||
Map<String, String> stringStringHashMap = new HashMap<>();
|
||||
@@ -102,6 +115,105 @@ public class MeetingBookingServiceImpl extends ServiceImpl<MeetingBookingMapper,
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public MeetingStatsVo getMeetingStats(MeetingStatsQueryDto queryDto) {
|
||||
MeetingStatsVo statsVo = new MeetingStatsVo();
|
||||
|
||||
// 设置默认查询条件
|
||||
LocalDate startDate = queryDto.getStartDate();
|
||||
LocalDate endDate = queryDto.getEndDate();
|
||||
Long deptId = queryDto.getDeptId();
|
||||
Long roomId = queryDto.getRoomId();
|
||||
|
||||
// 如果没有设置时间范围,默认查询最近30天
|
||||
if (startDate == null) {
|
||||
startDate = LocalDate.now().minusDays(30);
|
||||
}
|
||||
if (endDate == null) {
|
||||
endDate = LocalDate.now();
|
||||
}
|
||||
|
||||
// 1. 获取总体统计
|
||||
Map<String, Object> overallStatsMap = baseMapper.getOverallStats(startDate, endDate, deptId, roomId);
|
||||
MeetingStatsVo.OverallStats overallStats = BeanUtil.toBean(overallStatsMap, MeetingStatsVo.OverallStats.class);
|
||||
statsVo.setOverallStats(overallStats);
|
||||
|
||||
// 2. 按部门统计
|
||||
List<Map<String, Object>> deptStatsMapList = baseMapper.getDeptStats(startDate, endDate, roomId);
|
||||
|
||||
// 批量获取部门信息
|
||||
List<Long> deptIds = deptStatsMapList.stream()
|
||||
.map(map -> (Long) map.get("deptId"))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<Long, String> deptNameMap = new HashMap<>();
|
||||
if (!deptIds.isEmpty()) {
|
||||
R<Map<Long, SysDept>> deptMapR = remoteDeptService.getDeptByDeptIds(deptIds);
|
||||
if (deptMapR.isOk() && deptMapR.getData() != null) {
|
||||
for (Map.Entry<Long, SysDept> entry : deptMapR.getData().entrySet()) {
|
||||
deptNameMap.put(entry.getKey(), entry.getValue().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换部门统计结果
|
||||
List<MeetingStatsVo.DeptStats> deptStats = deptStatsMapList.stream()
|
||||
.map(map -> {
|
||||
MeetingStatsVo.DeptStats deptStat = BeanUtil.toBean(map, MeetingStatsVo.DeptStats.class);
|
||||
Long deptIdValue = (Long) map.get("deptId");
|
||||
deptStat.setDeptName(deptNameMap.getOrDefault(deptIdValue, "未知部门"));
|
||||
return deptStat;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
statsVo.setDeptStats(deptStats);
|
||||
|
||||
// 3. 按负责人统计
|
||||
List<Map<String, Object>> assigneeStatsMapList = baseMapper.getAssigneeStats(startDate, endDate, deptId, roomId);
|
||||
List<MeetingStatsVo.AssigneeStats> assigneeStats = assigneeStatsMapList.stream()
|
||||
.map(map -> BeanUtil.toBean(map, MeetingStatsVo.AssigneeStats.class))
|
||||
.collect(Collectors.toList());
|
||||
statsVo.setAssigneeStats(assigneeStats);
|
||||
|
||||
// 4. 按时间统计
|
||||
final String timeGranularity = queryDto.getTimeGranularity() != null && !queryDto.getTimeGranularity().isEmpty()
|
||||
? queryDto.getTimeGranularity() : "day";
|
||||
List<Map<String, Object>> timeStatsMapList = baseMapper.getTimeStats(startDate, endDate, timeGranularity, deptId, roomId);
|
||||
List<MeetingStatsVo.TimeStats> timeStats = timeStatsMapList.stream()
|
||||
.map(map -> {
|
||||
MeetingStatsVo.TimeStats timeStat = BeanUtil.toBean(map, MeetingStatsVo.TimeStats.class);
|
||||
// 处理日期字段
|
||||
if (map.get("date") != null) {
|
||||
if (timeGranularity.equals("day")) {
|
||||
timeStat.setDate(LocalDate.parse(map.get("date").toString()));
|
||||
} else {
|
||||
// 对于周和月,date字段是字符串格式
|
||||
timeStat.setDate(null);
|
||||
}
|
||||
}
|
||||
return timeStat;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
statsVo.setTimeStats(timeStats);
|
||||
|
||||
// 5. 按状态统计
|
||||
List<Map<String, Object>> statusStatsMapList = baseMapper.getStatusStats(startDate, endDate, deptId, roomId);
|
||||
List<MeetingStatsVo.StatusStats> statusStats = statusStatsMapList.stream()
|
||||
.map(map -> BeanUtil.toBean(map, MeetingStatsVo.StatusStats.class))
|
||||
.collect(Collectors.toList());
|
||||
statsVo.setStatusStats(statusStats);
|
||||
|
||||
// 6. 按会议室统计
|
||||
List<Map<String, Object>> roomStatsMapList = baseMapper.getRoomStats(startDate, endDate, deptId);
|
||||
List<MeetingStatsVo.RoomStats> roomStats = roomStatsMapList.stream()
|
||||
.map(map -> BeanUtil.toBean(map, MeetingStatsVo.RoomStats.class))
|
||||
.collect(Collectors.toList());
|
||||
statsVo.setRoomStats(roomStats);
|
||||
|
||||
return statsVo;
|
||||
}
|
||||
|
||||
// private void sendConfirmEndMeetingSms(VisitorUserEntity visitorUserEntity, VisitorsEntity dataVisitors) {
|
||||
// Map<String, String> stringStringHashMap = new HashMap<>();
|
||||
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
package com.bsd.pjcloud.integrated.vo.meeting;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会议统计结果
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "会议统计结果")
|
||||
public class MeetingStatsVo {
|
||||
|
||||
/**
|
||||
* 总体统计
|
||||
*/
|
||||
@Schema(description = "总体统计")
|
||||
private OverallStats overallStats;
|
||||
|
||||
/**
|
||||
* 按部门统计
|
||||
*/
|
||||
@Schema(description = "按部门统计")
|
||||
private List<DeptStats> deptStats;
|
||||
|
||||
/**
|
||||
* 按负责人统计
|
||||
*/
|
||||
@Schema(description = "按负责人统计")
|
||||
private List<AssigneeStats> assigneeStats;
|
||||
|
||||
/**
|
||||
* 按时间统计
|
||||
*/
|
||||
@Schema(description = "按时间统计")
|
||||
private List<TimeStats> timeStats;
|
||||
|
||||
/**
|
||||
* 按状态统计
|
||||
*/
|
||||
@Schema(description = "按状态统计")
|
||||
private List<StatusStats> statusStats;
|
||||
|
||||
/**
|
||||
* 按会议室统计
|
||||
*/
|
||||
@Schema(description = "按会议室统计")
|
||||
private List<RoomStats> roomStats;
|
||||
|
||||
@Data
|
||||
@Schema(description = "总体统计")
|
||||
public static class OverallStats {
|
||||
/**
|
||||
* 总预约数
|
||||
*/
|
||||
@Schema(description = "总预约数")
|
||||
private Long totalBookings;
|
||||
|
||||
/**
|
||||
* 审核通过数
|
||||
*/
|
||||
@Schema(description = "审核通过数")
|
||||
private Long approvedBookings;
|
||||
|
||||
/**
|
||||
* 审核中数
|
||||
*/
|
||||
@Schema(description = "审核中数")
|
||||
private Long pendingBookings;
|
||||
|
||||
/**
|
||||
* 审核拒绝数
|
||||
*/
|
||||
@Schema(description = "审核拒绝数")
|
||||
private Long rejectedBookings;
|
||||
|
||||
/**
|
||||
* 已取消数
|
||||
*/
|
||||
@Schema(description = "已取消数")
|
||||
private Long cancelledBookings;
|
||||
|
||||
/**
|
||||
* 通过率
|
||||
*/
|
||||
@Schema(description = "通过率")
|
||||
private Double approvalRate;
|
||||
|
||||
/**
|
||||
* 平均会议时长(分钟)
|
||||
*/
|
||||
@Schema(description = "平均会议时长(分钟)")
|
||||
private Double avgDuration;
|
||||
|
||||
/**
|
||||
* 总会议时长(分钟)
|
||||
*/
|
||||
@Schema(description = "总会议时长(分钟)")
|
||||
private Long totalDuration;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "部门统计")
|
||||
public static class DeptStats {
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@Schema(description = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@Schema(description = "部门名称")
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 预约数量
|
||||
*/
|
||||
@Schema(description = "预约数量")
|
||||
private Long bookingCount;
|
||||
|
||||
/**
|
||||
* 通过数量
|
||||
*/
|
||||
@Schema(description = "通过数量")
|
||||
private Long approvedCount;
|
||||
|
||||
/**
|
||||
* 通过率
|
||||
*/
|
||||
@Schema(description = "通过率")
|
||||
private Double approvalRate;
|
||||
|
||||
/**
|
||||
* 总会议时长(分钟)
|
||||
*/
|
||||
@Schema(description = "总会议时长(分钟)")
|
||||
private Long totalDuration;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "负责人统计")
|
||||
public static class AssigneeStats {
|
||||
/**
|
||||
* 负责人姓名
|
||||
*/
|
||||
@Schema(description = "负责人姓名")
|
||||
private String assigneeName;
|
||||
|
||||
/**
|
||||
* 预约数量
|
||||
*/
|
||||
@Schema(description = "预约数量")
|
||||
private Long bookingCount;
|
||||
|
||||
/**
|
||||
* 通过数量
|
||||
*/
|
||||
@Schema(description = "通过数量")
|
||||
private Long approvedCount;
|
||||
|
||||
/**
|
||||
* 通过率
|
||||
*/
|
||||
@Schema(description = "通过率")
|
||||
private Double approvalRate;
|
||||
|
||||
/**
|
||||
* 总会议时长(分钟)
|
||||
*/
|
||||
@Schema(description = "总会议时长(分钟)")
|
||||
private Long totalDuration;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "时间统计")
|
||||
public static class TimeStats {
|
||||
/**
|
||||
* 时间标签
|
||||
*/
|
||||
@Schema(description = "时间标签")
|
||||
private String timeLabel;
|
||||
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
@Schema(description = "日期")
|
||||
private LocalDate date;
|
||||
|
||||
/**
|
||||
* 预约数量
|
||||
*/
|
||||
@Schema(description = "预约数量")
|
||||
private Long bookingCount;
|
||||
|
||||
/**
|
||||
* 通过数量
|
||||
*/
|
||||
@Schema(description = "通过数量")
|
||||
private Long approvedCount;
|
||||
|
||||
/**
|
||||
* 总会议时长(分钟)
|
||||
*/
|
||||
@Schema(description = "总会议时长(分钟)")
|
||||
private Long totalDuration;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "状态统计")
|
||||
public static class StatusStats {
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
@Schema(description = "状态码")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 状态名称
|
||||
*/
|
||||
@Schema(description = "状态名称")
|
||||
private String statusName;
|
||||
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
@Schema(description = "数量")
|
||||
private Long count;
|
||||
|
||||
/**
|
||||
* 占比
|
||||
*/
|
||||
@Schema(description = "占比")
|
||||
private Double percentage;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Schema(description = "会议室统计")
|
||||
public static class RoomStats {
|
||||
/**
|
||||
* 会议室ID
|
||||
*/
|
||||
@Schema(description = "会议室ID")
|
||||
private Long roomId;
|
||||
|
||||
/**
|
||||
* 会议室名称
|
||||
*/
|
||||
@Schema(description = "会议室名称")
|
||||
private String roomName;
|
||||
|
||||
/**
|
||||
* 预约数量
|
||||
*/
|
||||
@Schema(description = "预约数量")
|
||||
private Long bookingCount;
|
||||
|
||||
/**
|
||||
* 通过数量
|
||||
*/
|
||||
@Schema(description = "通过数量")
|
||||
private Long approvedCount;
|
||||
|
||||
/**
|
||||
* 使用率
|
||||
*/
|
||||
@Schema(description = "使用率")
|
||||
private Double usageRate;
|
||||
|
||||
/**
|
||||
* 总会议时长(分钟)
|
||||
*/
|
||||
@Schema(description = "总会议时长(分钟)")
|
||||
private Long totalDuration;
|
||||
}
|
||||
}
|
||||
+212
@@ -30,4 +30,216 @@
|
||||
<select id="findByRoomIdAndDate" resultType="com.bsd.pjcloud.integrated.entity.MeetingBookingEntity">
|
||||
SELECT * FROM meeting_booking WHERE room_id = #{roomId} AND start_time >= #{date} AND start_time <= #{date}
|
||||
</select>
|
||||
|
||||
<!-- 总体统计 -->
|
||||
<select id="getOverallStats" resultType="map">
|
||||
SELECT
|
||||
COUNT(*) as totalBookings,
|
||||
SUM(CASE WHEN check_status = '2' THEN 1 ELSE 0 END) as approvedBookings,
|
||||
SUM(CASE WHEN check_status = '1' THEN 1 ELSE 0 END) as pendingBookings,
|
||||
SUM(CASE WHEN check_status = '3' THEN 1 ELSE 0 END) as rejectedBookings,
|
||||
SUM(CASE WHEN check_status = '4' OR check_status = '5' THEN 1 ELSE 0 END) as cancelledBookings,
|
||||
ROUND(SUM(CASE WHEN check_status = '2' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as approvalRate,
|
||||
ROUND(AVG(TIMESTAMPDIFF(MINUTE, start_time, end_time)), 2) as avgDuration,
|
||||
SUM(TIMESTAMPDIFF(MINUTE, start_time, end_time)) as totalDuration
|
||||
FROM meeting_booking mb
|
||||
WHERE 1=1
|
||||
<if test="startDate != null">
|
||||
AND mb.booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND mb.booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND mb.dept_id = #{deptId}
|
||||
</if>
|
||||
<if test="roomId != null">
|
||||
AND mb.room_id = #{roomId}
|
||||
</if>
|
||||
AND mb.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<!-- 按部门统计 -->
|
||||
<select id="getDeptStats" resultType="map">
|
||||
SELECT
|
||||
mb.dept_id as deptId,
|
||||
COUNT(*) as bookingCount,
|
||||
SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) as approvedCount,
|
||||
ROUND(SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as approvalRate,
|
||||
SUM(TIMESTAMPDIFF(MINUTE, mb.start_time, mb.end_time)) as totalDuration
|
||||
FROM meeting_booking mb
|
||||
WHERE 1=1
|
||||
<if test="startDate != null">
|
||||
AND mb.booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND mb.booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="roomId != null">
|
||||
AND mb.room_id = #{roomId}
|
||||
</if>
|
||||
AND mb.del_flag = '0'
|
||||
AND mb.dept_id IS NOT NULL
|
||||
GROUP BY mb.dept_id
|
||||
ORDER BY bookingCount DESC
|
||||
</select>
|
||||
|
||||
<!-- 按负责人统计 -->
|
||||
<select id="getAssigneeStats" resultType="map">
|
||||
SELECT
|
||||
mb.assignee_name as assigneeName,
|
||||
COUNT(*) as bookingCount,
|
||||
SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) as approvedCount,
|
||||
ROUND(SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as approvalRate,
|
||||
SUM(TIMESTAMPDIFF(MINUTE, mb.start_time, mb.end_time)) as totalDuration
|
||||
FROM meeting_booking mb
|
||||
WHERE 1=1
|
||||
<if test="startDate != null">
|
||||
AND mb.booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND mb.booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND mb.dept_id = #{deptId}
|
||||
</if>
|
||||
<if test="roomId != null">
|
||||
AND mb.room_id = #{roomId}
|
||||
</if>
|
||||
AND mb.del_flag = '0'
|
||||
AND mb.assignee_name IS NOT NULL
|
||||
GROUP BY mb.assignee_name
|
||||
ORDER BY bookingCount DESC
|
||||
</select>
|
||||
|
||||
<!-- 按时间统计 -->
|
||||
<select id="getTimeStats" resultType="map">
|
||||
SELECT
|
||||
<choose>
|
||||
<when test="timeGranularity == 'day'">
|
||||
mb.booking_date as date,
|
||||
DATE_FORMAT(mb.booking_date, '%Y-%m-%d') as timeLabel
|
||||
</when>
|
||||
<when test="timeGranularity == 'week'">
|
||||
DATE_FORMAT(mb.booking_date, '%Y-%u') as date,
|
||||
CONCAT(YEAR(mb.booking_date), '年第', WEEK(mb.booking_date), '周') as timeLabel
|
||||
</when>
|
||||
<when test="timeGranularity == 'month'">
|
||||
DATE_FORMAT(mb.booking_date, '%Y-%m') as date,
|
||||
DATE_FORMAT(mb.booking_date, '%Y年%m月') as timeLabel
|
||||
</when>
|
||||
<otherwise>
|
||||
mb.booking_date as date,
|
||||
DATE_FORMAT(mb.booking_date, '%Y-%m-%d') as timeLabel
|
||||
</otherwise>
|
||||
</choose>,
|
||||
COUNT(*) as bookingCount,
|
||||
SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) as approvedCount,
|
||||
SUM(TIMESTAMPDIFF(MINUTE, mb.start_time, mb.end_time)) as totalDuration
|
||||
FROM meeting_booking mb
|
||||
WHERE 1=1
|
||||
<if test="startDate != null">
|
||||
AND mb.booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND mb.booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND mb.dept_id = #{deptId}
|
||||
</if>
|
||||
<if test="roomId != null">
|
||||
AND mb.room_id = #{roomId}
|
||||
</if>
|
||||
AND mb.del_flag = '0'
|
||||
GROUP BY
|
||||
<choose>
|
||||
<when test="timeGranularity == 'day'">
|
||||
mb.booking_date
|
||||
</when>
|
||||
<when test="timeGranularity == 'week'">
|
||||
YEAR(mb.booking_date), WEEK(mb.booking_date)
|
||||
</when>
|
||||
<when test="timeGranularity == 'month'">
|
||||
YEAR(mb.booking_date), MONTH(mb.booking_date)
|
||||
</when>
|
||||
<otherwise>
|
||||
mb.booking_date
|
||||
</otherwise>
|
||||
</choose>
|
||||
ORDER BY date ASC
|
||||
</select>
|
||||
|
||||
<!-- 按状态统计 -->
|
||||
<select id="getStatusStats" resultType="map">
|
||||
SELECT
|
||||
mb.check_status as status,
|
||||
CASE mb.check_status
|
||||
WHEN '1' THEN '审核中'
|
||||
WHEN '2' THEN '审核通过'
|
||||
WHEN '3' THEN '审核拒绝'
|
||||
WHEN '4' THEN '已取消'
|
||||
WHEN '5' THEN '取消'
|
||||
WHEN '0' THEN '已到访'
|
||||
ELSE '未知状态'
|
||||
END as statusName,
|
||||
COUNT(*) as count,
|
||||
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM meeting_booking WHERE del_flag = '0'
|
||||
<if test="startDate != null">
|
||||
AND booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND dept_id = #{deptId}
|
||||
</if>
|
||||
<if test="roomId != null">
|
||||
AND room_id = #{roomId}
|
||||
</if>
|
||||
), 2) as percentage
|
||||
FROM meeting_booking mb
|
||||
WHERE 1=1
|
||||
<if test="startDate != null">
|
||||
AND mb.booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND mb.booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND mb.dept_id = #{deptId}
|
||||
</if>
|
||||
<if test="roomId != null">
|
||||
AND mb.room_id = #{roomId}
|
||||
</if>
|
||||
AND mb.del_flag = '0'
|
||||
GROUP BY mb.check_status
|
||||
ORDER BY count DESC
|
||||
</select>
|
||||
|
||||
<!-- 按会议室统计 -->
|
||||
<select id="getRoomStats" resultType="map">
|
||||
SELECT
|
||||
mb.room_id as roomId,
|
||||
mr.room_name as roomName,
|
||||
COUNT(*) as bookingCount,
|
||||
SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) as approvedCount,
|
||||
ROUND(SUM(CASE WHEN mb.check_status = '2' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as usageRate,
|
||||
SUM(TIMESTAMPDIFF(MINUTE, mb.start_time, mb.end_time)) as totalDuration
|
||||
FROM meeting_booking mb
|
||||
LEFT JOIN meeting_room mr ON mb.room_id = mr.id
|
||||
WHERE 1=1
|
||||
<if test="startDate != null">
|
||||
AND mb.booking_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND mb.booking_date <= #{endDate}
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND mb.dept_id = #{deptId}
|
||||
</if>
|
||||
AND mb.del_flag = '0'
|
||||
GROUP BY mb.room_id, mr.room_name
|
||||
ORDER BY bookingCount DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package com.bsd.pjcloud.integrated.service;
|
||||
|
||||
import com.bsd.pjcloud.integrated.dto.meeting.MeetingStatsQueryDto;
|
||||
import com.bsd.pjcloud.integrated.vo.meeting.MeetingStatsVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 会议室预约统计功能测试
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-20
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
public class MeetingBookingStatsTest {
|
||||
|
||||
@Resource
|
||||
private MeetingBookingService meetingBookingService;
|
||||
|
||||
/**
|
||||
* 测试获取总体统计信息
|
||||
*/
|
||||
@Test
|
||||
public void testGetOverallStats() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(30));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("总体统计信息: {}", stats.getOverallStats());
|
||||
assert stats.getOverallStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按部门统计
|
||||
*/
|
||||
@Test
|
||||
public void testGetDeptStats() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(30));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
queryDto.setDimension("dept");
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("部门统计信息: {}", stats.getDeptStats());
|
||||
assert stats.getDeptStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按时间统计(按天)
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeStatsByDay() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(7));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
queryDto.setTimeGranularity("day");
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("按天统计信息: {}", stats.getTimeStats());
|
||||
assert stats.getTimeStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按时间统计(按周)
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeStatsByWeek() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(30));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
queryDto.setTimeGranularity("week");
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("按周统计信息: {}", stats.getTimeStats());
|
||||
assert stats.getTimeStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按时间统计(按月)
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeStatsByMonth() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(90));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
queryDto.setTimeGranularity("month");
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("按月统计信息: {}", stats.getTimeStats());
|
||||
assert stats.getTimeStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按状态统计
|
||||
*/
|
||||
@Test
|
||||
public void testGetStatusStats() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(30));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("状态统计信息: {}", stats.getStatusStats());
|
||||
assert stats.getStatusStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按会议室统计
|
||||
*/
|
||||
@Test
|
||||
public void testGetRoomStats() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(30));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("会议室统计信息: {}", stats.getRoomStats());
|
||||
assert stats.getRoomStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按负责人统计
|
||||
*/
|
||||
@Test
|
||||
public void testGetAssigneeStats() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
queryDto.setStartDate(LocalDate.now().minusDays(30));
|
||||
queryDto.setEndDate(LocalDate.now());
|
||||
queryDto.setDimension("assignee");
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("负责人统计信息: {}", stats.getAssigneeStats());
|
||||
assert stats.getAssigneeStats() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试无参数查询(使用默认时间范围)
|
||||
*/
|
||||
@Test
|
||||
public void testGetStatsWithoutParams() {
|
||||
MeetingStatsQueryDto queryDto = new MeetingStatsQueryDto();
|
||||
|
||||
MeetingStatsVo stats = meetingBookingService.getMeetingStats(queryDto);
|
||||
|
||||
log.info("默认统计信息: {}", stats);
|
||||
assert stats != null;
|
||||
assert stats.getOverallStats() != null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user