feat: 线索分配需求

This commit is contained in:
clunt 2025-10-28 09:40:09 +08:00
parent 0a9480d6e6
commit 238d17802d
21 changed files with 2894 additions and 4 deletions

View File

@ -0,0 +1,264 @@
# 线索值班配置系统技术架构文档
## 1. 架构设计
```mermaid
graph TD
A[用户浏览器] --> B[Spring Boot Web层]
B --> C[Service业务层]
C --> D[MyBatis数据访问层]
D --> E[MySQL数据库]
B --> F[Thymeleaf模板引擎]
C --> G[定时任务调度]
C --> H[缓存层Redis]
subgraph "前端层"
B
F
end
subgraph "业务层"
C
G
H
end
subgraph "数据层"
D
E
end
```
## 2. 技术描述
- **前端**Thymeleaf@3.0 + Bootstrap@4.6 + jQuery@3.6 + LayUI@2.6
- **后端**Spring Boot@2.5.14 + MyBatis@3.5 + Apache Shiro@1.10.1
- **数据库**MySQL@8.0
- **缓存**Redis@6.2(可选,用于分配算法优化)
## 3. 路由定义
| 路由 | 用途 |
|------|------|
| /system/dutyConfig | 值班配置管理主页面 |
| /system/dutyConfig/list | 获取值班配置列表数据 |
| /system/dutyConfig/add | 新增值班配置页面 |
| /system/dutyConfig/edit/{id} | 编辑值班配置页面 |
| /system/dutyConfig/save | 保存值班配置 |
| /system/dutyConfig/update | 更新值班配置 |
| /system/dutyConfig/remove/{ids} | 删除值班配置 |
| /system/dutyConfig/changeStatus | 修改配置状态 |
| /system/dutyConfig/getAvailableUsers | 获取可用用户列表 |
| /system/dutyConfig/statistics | 分配统计页面 |
## 4. API定义
### 4.1 核心API
值班配置管理相关接口
```
GET /system/dutyConfig/list
```
请求参数:
| 参数名称 | 参数类型 | 是否必填 | 描述 |
|----------|----------|----------|------|
| pageNum | int | false | 页码默认1 |
| pageSize | int | false | 每页大小默认10 |
| configName | string | false | 配置名称(模糊查询) |
| status | string | false | 状态0-禁用1-启用 |
响应数据:
| 参数名称 | 参数类型 | 描述 |
|----------|----------|------|
| code | int | 响应状态码 |
| msg | string | 响应消息 |
| rows | array | 配置列表数据 |
| total | int | 总记录数 |
```
POST /system/dutyConfig/save
```
请求参数:
| 参数名称 | 参数类型 | 是否必填 | 描述 |
|----------|----------|----------|------|
| configName | string | true | 配置名称 |
| startTime | string | true | 开始时间HH:mm格式 |
| endTime | string | true | 结束时间HH:mm格式 |
| userIds | string | true | 值班用户ID列表逗号分隔 |
| status | string | true | 状态0-禁用1-启用 |
| remark | string | false | 备注信息 |
响应数据:
| 参数名称 | 参数类型 | 描述 |
|----------|----------|------|
| code | int | 响应状态码 |
| msg | string | 响应消息 |
示例请求:
```json
{
"configName": "工作日白班",
"startTime": "09:00",
"endTime": "18:00",
"userIds": "1,2,3,4",
"status": "1",
"remark": "工作日白班值班安排"
}
```
```
GET /system/dutyConfig/getAvailableUsers
```
响应数据:
| 参数名称 | 参数类型 | 描述 |
|----------|----------|------|
| code | int | 响应状态码 |
| data | array | 可用用户列表 |
用户对象结构:
| 参数名称 | 参数类型 | 描述 |
|----------|----------|------|
| userId | long | 用户ID |
| userName | string | 用户姓名 |
| loginName | string | 登录名 |
| status | string | 用户状态 |
## 5. 服务器架构图
```mermaid
graph TD
A[Controller层] --> B[Service层]
B --> C[Mapper层]
C --> D[(MySQL数据库)]
B --> E[分配算法服务]
B --> F[缓存服务]
E --> G[轮询分配器]
E --> H[时间匹配器]
subgraph "Web层"
A
end
subgraph "业务层"
B
E
F
end
subgraph "数据访问层"
C
end
subgraph "算法组件"
G
H
end
```
## 6. 数据模型
### 6.1 数据模型定义
```mermaid
erDiagram
DUTY_CONFIG ||--o{ DUTY_CONFIG_USER : contains
SYS_USER ||--o{ DUTY_CONFIG_USER : assigned
DUTY_CONFIG ||--o{ CLEW_ASSIGN_LOG : generates
SYS_USER ||--o{ CLEW_ASSIGN_LOG : receives
DUTY_CONFIG {
bigint id PK
string config_name
string start_time
string end_time
string status
string remark
datetime create_time
datetime update_time
string create_by
string update_by
}
DUTY_CONFIG_USER {
bigint id PK
bigint config_id FK
bigint user_id FK
int sort_order
datetime create_time
}
SYS_USER {
bigint user_id PK
string user_name
string login_name
string status
datetime create_time
}
CLEW_ASSIGN_LOG {
bigint id PK
bigint clew_id
bigint config_id FK
bigint assigned_user_id FK
datetime assign_time
string assign_type
}
```
### 6.2 数据定义语言
值班配置表duty_config
```sql
-- 创建值班配置表
CREATE TABLE duty_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
start_time VARCHAR(5) NOT NULL COMMENT '开始时间HH:mm',
end_time VARCHAR(5) NOT NULL COMMENT '结束时间HH:mm',
status CHAR(1) DEFAULT '1' COMMENT '状态0-禁用1-启用)',
remark VARCHAR(500) COMMENT '备注',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(64) COMMENT '创建者',
update_by VARCHAR(64) COMMENT '更新者',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志0-正常1-删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班配置表';
-- 创建值班配置用户关联表
CREATE TABLE duty_config_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
config_id BIGINT NOT NULL COMMENT '配置ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
sort_order INT DEFAULT 0 COMMENT '排序顺序',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_config_id (config_id),
INDEX idx_user_id (user_id),
FOREIGN KEY (config_id) REFERENCES duty_config(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班配置用户关联表';
-- 创建线索分配日志表
CREATE TABLE clew_assign_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
clew_id BIGINT COMMENT '线索ID',
config_id BIGINT COMMENT '使用的配置ID',
assigned_user_id BIGINT NOT NULL COMMENT '分配的用户ID',
assign_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '分配时间',
assign_type VARCHAR(20) DEFAULT 'AUTO' COMMENT '分配类型AUTO-自动MANUAL-手动)',
remark VARCHAR(200) COMMENT '备注',
INDEX idx_clew_id (clew_id),
INDEX idx_assigned_user (assigned_user_id),
INDEX idx_assign_time (assign_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='线索分配日志表';
-- 创建索引
CREATE INDEX idx_duty_config_time ON duty_config(start_time, end_time, status);
CREATE INDEX idx_duty_config_status ON duty_config(status, del_flag);
-- 插入初始数据
INSERT INTO duty_config (config_name, start_time, end_time,{"file_path": "/Users/clunt/java/RuoYi/.trae/documents/线索值班配置系统技术架构文档.md", "content":

View File

@ -0,0 +1,95 @@
# 线索值班配置系统需求文档
## 1. 产品概述
线索值班配置系统是基于若依框架开发的线索自动分配管理模块,旨在解决当前线索只能固定分配给特定用户的问题。通过配置值班时间段和值班人员,实现线索的智能轮流分配,提高线索处理效率和公平性。
- 解决线索分配不均衡的问题,提升销售团队工作效率
- 支持灵活的时间段配置和多用户轮班管理
- 为线索跟踪系统提供智能化的分配策略
## 2. 核心功能
### 2.1 用户角色
| 角色 | 权限说明 | 核心权限 |
|------|----------|----------|
| 系统管理员 | 完整的值班配置管理权限 | 可以创建、修改、删除值班配置,管理所有用户的值班安排 |
| 部门主管 | 部门内值班配置管理 | 可以管理本部门用户的值班配置,查看分配统计 |
| 普通销售 | 查看权限 | 可以查看自己的值班安排和线索分配情况 |
### 2.2 功能模块
本系统包含以下核心页面:
1. **值班配置管理页面**:值班规则列表、配置表单、状态管理
2. **值班人员管理页面**:人员选择、轮班顺序设置、人员状态管理
3. **线索分配统计页面**:分配记录查看、统计报表、分配日志
### 2.3 页面详情
| 页面名称 | 模块名称 | 功能描述 |
|----------|----------|----------|
| 值班配置管理页面 | 配置列表 | 显示所有值班配置,支持搜索、筛选、分页查看 |
| 值班配置管理页面 | 新增配置 | 创建新的值班配置,设置时间段、选择值班人员、设置状态 |
| 值班配置管理页面 | 编辑配置 | 修改现有配置的时间段、人员安排、启用状态 |
| 值班配置管理页面 | 删除配置 | 删除不需要的值班配置,支持批量删除 |
| 值班人员管理页面 | 人员选择 | 从sys_user表获取可用用户支持多选和搜索 |
| 值班人员管理页面 | 轮班设置 | 设置人员轮班顺序,支持拖拽排序 |
| 线索分配统计页面 | 分配记录 | 查看线索分配历史记录,包括分配时间、分配人员、线索信息 |
| 线索分配统计页面 | 统计报表 | 显示各时间段的分配统计,人员工作量分析 |
## 3. 核心流程
### 管理员配置流程
1. 管理员登录系统 → 进入值班配置管理页面
2. 创建新的值班配置 → 设置时间段(开始时间-结束时间,精确到分钟)
3. 选择值班人员 → 从可用用户列表中多选值班人员
4. 设置配置状态 → 启用或禁用该配置
5. 保存配置 → 系统验证配置有效性并保存
### 线索自动分配流程
1. APP端提交新线索 → 调用appAddSave接口
2. 系统获取当前时间 → 查询匹配的值班配置
3. 获取当前值班人员列表 → 按轮流规则选择下一个分配人员
4. 分配线索给选中人员 → 更新线索记录和分配日志
5. 返回分配结果 → 记录分配统计信息
```mermaid
graph TD
A[APP提交线索] --> B[获取当前时间]
B --> C[查询值班配置]
C --> D{是否有匹配配置?}
D -->|是| E[获取值班人员列表]
D -->|否| F[分配给默认用户]
E --> G[按轮流规则选择人员]
G --> H[分配线索]
H --> I[记录分配日志]
F --> I
I --> J[返回结果]
```
## 4. 用户界面设计
### 4.1 设计风格
- **主色调**#409EFF蓝色、#67C23A绿色
- **辅助色**#E6A23C橙色、#F56C6C红色
- **按钮样式**:圆角按钮,支持悬停效果
- **字体**微软雅黑主要字号14px标题16px
- **布局风格**:卡片式布局,顶部导航栏
- **图标风格**使用FontAwesome图标库简洁现代
### 4.2 页面设计概览
| 页面名称 | 模块名称 | UI元素 |
|----------|----------|---------|
| 值班配置管理页面 | 配置列表 | 表格布局,蓝色标题栏,白色背景,分页组件,搜索框使用圆角设计 |
| 值班配置管理页面 | 配置表单 | 模态框弹窗,表单使用栅格布局,时间选择器精确到分钟,多选下拉框 |
| 值班人员管理页面 | 人员选择 | 穿梭框组件,左侧可选人员,右侧已选人员,支持搜索和批量操作 |
| 线索分配统计页面 | 统计图表 | ECharts图表组件柱状图和饼图展示响应式设计 |
### 4.3 响应式设计
- 采用桌面优先设计支持1920x1080及以上分辨率
- 表格在小屏幕下支持横向滚动
- 表单在移动端自动调整为单列布局
- 支持触摸操作优化,按钮间距适配手指点击

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}

View File

@ -7,8 +7,6 @@ import java.util.stream.Collectors;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
@ -19,6 +17,7 @@ import com.ruoyi.system.domain.CompanyApp;
import com.ruoyi.system.domain.OppoCheck;
import com.ruoyi.system.service.IClewPhoneService;
import com.ruoyi.system.service.ICompanyAppService;
import com.ruoyi.system.service.IDutyConfigService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.web.core.config.GlobalLogHelper;
import lombok.extern.slf4j.Slf4j;
@ -29,7 +28,6 @@ import okhttp3.Response;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.tomcat.jni.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
@ -71,6 +69,9 @@ public class ClewController extends BaseController
@Autowired
private ICompanyAppService companyAppService;
@Autowired
private IDutyConfigService dutyConfigService;
@RequiresPermissions("system:clew:view")
@GetMapping()
public String clew(ModelMap modelMap)
@ -140,7 +141,6 @@ public class ClewController extends BaseController
@ResponseBody
public TableDataInfo listPublic(Clew clew)
{
SysUser sysUser=getSysUser();
startPage();
clew.setSaleId(2L);
clew.setPoolStatus("01"); // 只显示公海池状态的线索
@ -252,9 +252,35 @@ public class ClewController extends BaseController
return AjaxResult.success();
}
// 根据值班配置自动分配客服
Long assignedUserId = null;
try {
assignedUserId = dutyConfigService.getNextDutyUserId();
if (assignedUserId != null) {
clew.setSaleId(assignedUserId);
clew.setSourceSaleId(assignedUserId); // 记录初始分配的销售
clew.setAssignTimes("01"); // 标记为首次分配
log.info("线索自动分配给值班用户, 手机号:{}, 用户ID:{}", clew.getPhone(), assignedUserId);
} else {
// 如果没有值班配置使用默认逻辑可以设置为公海或默认用户
clew.setSaleId(1L); // 默认分配给默认用户-保无忧
clew.setPoolStatus("01"); // 设置为公海池状态
log.info("未找到值班配置, 线索分配到公海, 手机号:{}", clew.getPhone());
}
} catch (Exception e) {
log.error("获取值班用户失败, 线索分配到公海, 手机号:{}, 错误:{}", clew.getPhone(), e.getMessage());
clew.setSaleId(1L); // 异常时分配给公海
clew.setPoolStatus("01"); // 设置为公海池状态
}
// 不存在手机号则入库线索
clewService.insertClew(clew);
// 如果分配给了具体客服发送短信通知
if (assignedUserId != null && assignedUserId != 2L) {
sendSmsNotification(assignedUserId, clew);
}
// 将初始线索设置成已跟踪
ClewPhone model = new ClewPhone();
model.setPhone(clew.getPhone());
@ -544,4 +570,49 @@ public class ClewController extends BaseController
return toAjax(clewService.catchClewToQuality(sourceClew));
}
/**
* 发送短信通知给值班客服
*/
private void sendSmsNotification(Long userId, Clew clew) {
try {
// 查询客服手机号
SysUser sysUser = sysUserService.selectUserById(userId);
if (sysUser == null || StringUtils.isEmpty(sysUser.getLoginName())) {
log.warn("未找到用户信息或手机号为空, 用户ID:{}", userId);
return;
}
// 检查是否是手机号长度大于10位
boolean isPhone = sysUser.getLoginName().length() > 10;
if (!isPhone) {
log.info("用户登录名不是手机号,跳过短信通知, 用户ID:{}, 登录名:{}", userId, sysUser.getLoginName());
return;
}
// 排除特定来源sourceApp == 8
if (ObjectUtil.equals(clew.getSourceApp(), 8L)) {
log.info("线索来源为8跳过短信通知, 用户ID:{}", userId);
return;
}
JSONObject smsContent = new JSONObject();
smsContent.put("uid", "12347");
smsContent.put("pwd", "wJgzaC0u");
smsContent.put("mobile", sysUser.getLoginName());
// 根据来源设置短信内容
if (clew.getSourceApp() != null && clew.getSourceApp() == 2L) {
smsContent.put("content", "【值班分配】你有一条来自大象,新线索生成,客户电话:" + clew.getPhone() + "(请注意及时跟进)");
} else {
smsContent.put("content", "【值班分配】你有一条来自黑猫,新线索生成,客户电话:" + clew.getPhone() + "(请注意及时跟进)");
}
log.info("值班分配短信通知, 用户ID:{}, 手机号:{}, 请求参数:{}", userId, sysUser.getLoginName(), smsContent.toJSONString());
String response = HttpUtils.sendPost("http://www.aozhongyun.com/Admin/index.php/Message/send", smsContent.toJSONString());
log.info("值班分配短信通知响应:{}", response);
} catch (Exception e) {
log.error("发送值班分配短信通知失败, 用户ID:{}, 错误:{}", userId, e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,253 @@
package com.ruoyi.web.controller.system;
import java.util.Date;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestParam;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.DutyConfig;
import com.ruoyi.system.service.IDutyConfigService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.entity.SysUser;
/**
* 值班配置Controller
*
* @author ruoyi
* @date 2024-12-19
*/
@Controller
@RequestMapping("/system/dutyConfig")
public class DutyConfigController extends BaseController
{
private String prefix = "system/dutyConfig";
@Autowired
private IDutyConfigService dutyConfigService;
@Autowired
private ISysUserService userService;
@RequiresPermissions("system:dutyConfig:view")
@GetMapping()
public String dutyConfig()
{
return prefix + "/dutyConfig";
}
/**
* 查询值班配置列表
*/
@RequiresPermissions("system:dutyConfig:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(DutyConfig dutyConfig)
{
startPage();
List<DutyConfig> list = dutyConfigService.selectDutyConfigList(dutyConfig);
return getDataTable(list);
}
/**
* 导出值班配置列表
*/
@RequiresPermissions("system:dutyConfig:export")
@Log(title = "值班配置", businessType = BusinessType.EXPORT)
@PostMapping("/export")
@ResponseBody
public AjaxResult export(DutyConfig dutyConfig)
{
List<DutyConfig> list = dutyConfigService.selectDutyConfigList(dutyConfig);
ExcelUtil<DutyConfig> util = new ExcelUtil<DutyConfig>(DutyConfig.class);
return util.exportExcel(list, "值班配置数据");
}
/**
* 新增值班配置
*/
@GetMapping("/add")
public String add(ModelMap mmap)
{
// 获取所有可用用户
SysUser user = new SysUser();
user.setStatus("0"); // 正常状态
List<SysUser> users = userService.selectUserList(user);
mmap.put("users", users);
return prefix + "/add";
}
/**
* 新增保存值班配置
*/
@RequiresPermissions("system:dutyConfig:add")
@Log(title = "值班配置", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(DutyConfig dutyConfig, @RequestParam("dutyUserIds") String dutyUserIds)
{
// 验证时间范围
if (dutyConfig.getStartTime() == null || dutyConfig.getEndTime() == null) {
return error("开始时间和结束时间不能为空");
}
if (dutyConfig.getStartTime().compareTo(dutyConfig.getEndTime()) >= 0) {
return error("开始时间必须早于结束时间");
}
// 检查时间段是否冲突
if (dutyConfigService.checkTimeConflict(dutyConfig.getStartTime(), dutyConfig.getEndTime(), null)) {
return error("该时间段与现有配置冲突,请重新选择时间");
}
// 验证值班用户
if (dutyUserIds == null || dutyUserIds.trim().isEmpty()) {
return error("值班用户不能为空");
}
dutyConfig.setCreateBy(getLoginName());
// Service层会处理dutyUserIds参数
return toAjax(dutyConfigService.insertDutyConfig(dutyConfig, dutyUserIds));
}
/**
* 修改值班配置
*/
@GetMapping("/edit/{id}")
public String edit(@PathVariable("id") Long id, ModelMap mmap)
{
DutyConfig dutyConfig = dutyConfigService.selectDutyConfigById(id);
mmap.put("dutyConfig", dutyConfig);
// 获取所有可用用户
SysUser user = new SysUser();
user.setStatus("0"); // 正常状态
List<SysUser> users = userService.selectUserList(user);
mmap.put("users", users);
return prefix + "/edit";
}
/**
* 修改保存值班配置
*/
@RequiresPermissions("system:dutyConfig:edit")
@Log(title = "值班配置", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(DutyConfig dutyConfig, @RequestParam("dutyUserIds") String dutyUserIds)
{
// 验证时间范围
if (dutyConfig.getStartTime() == null || dutyConfig.getEndTime() == null) {
return error("开始时间和结束时间不能为空");
}
if (dutyConfig.getStartTime().compareTo(dutyConfig.getEndTime()) >= 0) {
return error("开始时间必须早于结束时间");
}
// 检查时间段是否冲突排除自己
if (dutyConfigService.checkTimeConflict(dutyConfig.getStartTime(), dutyConfig.getEndTime(), dutyConfig.getId())) {
return error("该时间段与现有配置冲突,请重新选择时间");
}
// 验证值班用户
if (dutyUserIds == null || dutyUserIds.trim().isEmpty()) {
return error("值班用户不能为空");
}
dutyConfig.setUpdateBy(getLoginName());
// Service层会处理dutyUserIds参数
return toAjax(dutyConfigService.updateDutyConfig(dutyConfig, dutyUserIds));
}
/**
* 删除值班配置
*/
@RequiresPermissions("system:dutyConfig:remove")
@Log(title = "值班配置", businessType = BusinessType.DELETE)
@PostMapping( "/remove")
@ResponseBody
public AjaxResult remove(String ids)
{
return toAjax(dutyConfigService.deleteDutyConfigByIds(ids));
}
/**
* 修改值班配置状态
*/
@RequiresPermissions("system:dutyConfig:edit")
@Log(title = "值班配置", businessType = BusinessType.UPDATE)
@PostMapping("/changeStatus")
@ResponseBody
public AjaxResult changeStatus(DutyConfig dutyConfig)
{
dutyConfig.setUpdateBy(getLoginName());
return toAjax(dutyConfigService.changeStatus(dutyConfig));
}
/**
* 获取可用用户列表
*/
@GetMapping("/getAvailableUsers")
@ResponseBody
public AjaxResult getAvailableUsers()
{
SysUser user = new SysUser();
user.setStatus("0"); // 正常状态
List<SysUser> users = userService.selectUserList(user);
return AjaxResult.success(users);
}
/**
* 获取当前值班用户
*/
@GetMapping("/getCurrentDutyUser")
@ResponseBody
public AjaxResult getCurrentDutyUser()
{
Long dutyUserId = dutyConfigService.getNextDutyUserId();
if (dutyUserId != null) {
SysUser user = userService.selectUserById(dutyUserId);
return AjaxResult.success(user);
}
return AjaxResult.error("当前没有值班配置");
}
/**
* 查看值班配置详情
*/
@RequiresPermissions("system:dutyConfig:view")
@GetMapping("/detail/{id}")
public String detail(@PathVariable("id") Long id, ModelMap mmap)
{
DutyConfig dutyConfig = dutyConfigService.selectDutyConfigById(id);
// 设置是否在当前时间范围内
if (dutyConfig != null) {
Date now = new Date();
if ("1".equals(dutyConfig.getStatus()) &&
dutyConfig.getStartTime() != null && dutyConfig.getEndTime() != null) {
dutyConfig.setIsActive(now.compareTo(dutyConfig.getStartTime()) >= 0 &&
now.compareTo(dutyConfig.getEndTime()) <= 0);
} else {
dutyConfig.setIsActive(false);
}
}
mmap.put("dutyConfig", dutyConfig);
return prefix + "/detail";
}
}

View File

@ -0,0 +1,194 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('新增值班配置')" />
<th:block th:include="include :: select2-css" />
<th:block th:include="include :: datetimepicker-css" />
</head>
<body>
<div class="main-content">
<form id="form-dutyConfig-add" class="form-horizontal">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">配置名称:</label>
<div class="col-sm-8">
<input name="configName" placeholder="请输入配置名称" class="form-control" type="text" maxlength="100" required>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">状态:</label>
<div class="col-sm-8">
<label class="toggle-switch switch-solid">
<input type="checkbox" id="status" checked>
<span></span>
</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">开始时间:</label>
<div class="col-sm-8">
<div class="input-group date">
<input name="startTime" class="form-control" placeholder="yyyy-MM-dd HH:mm:ss" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">结束时间:</label>
<div class="col-sm-8">
<div class="input-group date">
<input name="endTime" class="form-control" placeholder="yyyy-MM-dd HH:mm:ss" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-xs-2 control-label is-required">值班人员:</label>
<div class="col-xs-10">
<select id="dutyUserIds" name="dutyUserIds" class="form-control select2-multiple" multiple required>
<option th:each="user:${users}" th:value="${user.userId}" th:text="${user.userName}" th:disabled="${user.status == '1'}"></option>
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-xs-2 control-label">备注:</label>
<div class="col-xs-10">
<textarea name="remark" maxlength="500" class="form-control" rows="3" placeholder="请输入备注信息"></textarea>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="row">
<div class="col-sm-offset-5 col-sm-10">
<button type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i>保 存</button>&nbsp;
<button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭 </button>
</div>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: select2-js" />
<th:block th:include="include :: datetimepicker-js" />
<script>
var prefix = ctx + "system/dutyConfig";
$("#form-dutyConfig-add").validate({
onkeyup: false,
rules:{
configName:{
required: true,
minlength: 2,
maxlength: 100
},
startTime:{
required: true
},
endTime:{
required: true
},
dutyUserIds:{
required: true
}
},
messages: {
"configName": {
required: "请输入配置名称",
minlength: "配置名称不能小于2个字符",
maxlength: "配置名称不能超过100个字符"
},
"startTime": {
required: "请选择开始时间"
},
"endTime": {
required: "请选择结束时间"
},
"dutyUserIds": {
required: "请选择值班人员"
}
},
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
// 验证时间范围
var startTime = $("input[name='startTime']").val();
var endTime = $("input[name='endTime']").val();
if (startTime && endTime) {
var start = new Date(startTime);
var end = new Date(endTime);
if (start >= end) {
$.modal.alertWarning("开始时间必须早于结束时间");
return;
}
}
// 处理状态值0停用 1启用
var status = $("#status").is(':checked') == true ? "1" : "0";
// 处理值班人员 - 转换为逗号分隔的字符串
var dutyUserIds = $("#dutyUserIds").val();
if (dutyUserIds && dutyUserIds.length > 0) {
dutyUserIds = dutyUserIds.join(',');
} else {
dutyUserIds = '';
}
// 移除表单中的 dutyUserIds避免重复提交
var data = $("#form-dutyConfig-add").serializeArray().filter(function(item) {
return item.name !== 'dutyUserIds';
});
data.push({"name": "status", "value": status});
data.push({"name": "dutyUserIds", "value": dutyUserIds});
$.operate.saveTab(prefix + "/add", data);
}
}
$(function() {
// 初始化Select2
$('.select2-multiple').select2({
placeholder: "请选择值班人员",
allowClear: true
});
// 初始化日期时间选择器
$(".form-control[name='startTime']").datetimepicker({
format: "yyyy-mm-dd hh:ii:ss",
autoclose: true,
todayBtn: true,
language: 'zh-CN'
});
$(".form-control[name='endTime']").datetimepicker({
format: "yyyy-mm-dd hh:ii:ss",
autoclose: true,
todayBtn: true,
language: 'zh-CN'
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('值班配置详情')" />
</head>
<body>
<div class="main-content">
<form class="form-horizontal">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">配置名称:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${dutyConfig.configName}"></p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">状态:</label>
<div class="col-sm-8">
<p class="form-control-static">
<span th:if="${dutyConfig.status == '1'}" class="label label-success">启用</span>
<span th:if="${dutyConfig.status == '0'}" class="label label-danger">停用</span>
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">开始时间:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${#dates.format(dutyConfig.startTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">结束时间:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${#dates.format(dutyConfig.endTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">是否生效:</label>
<div class="col-sm-8">
<p class="form-control-static">
<span th:if="${dutyConfig.isActive}" class="label label-success">生效中</span>
<span th:if="${!dutyConfig.isActive}" class="label label-default">未生效</span>
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-xs-2 control-label">值班人员:</label>
<div class="col-xs-10">
<p class="form-control-static" th:text="${dutyConfig.dutyUserNames}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-xs-2 control-label">备注:</label>
<div class="col-xs-10">
<p class="form-control-static" th:text="${dutyConfig.remark}"></p>
</div>
</div>
</div>
</div>
<h4 class="form-header h4">其他信息</h4>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">创建者:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${dutyConfig.createBy}"></p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">创建时间:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${dutyConfig.createTime != null ? #dates.format(dutyConfig.createTime, 'yyyy-MM-dd HH:mm:ss') : ''}"></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">更新者:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${dutyConfig.updateBy}"></p>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">更新时间:</label>
<div class="col-sm-8">
<p class="form-control-static" th:text="${dutyConfig.updateTime != null ? #dates.format(dutyConfig.updateTime, 'yyyy-MM-dd HH:mm:ss') : ''}"></p>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="row">
<div class="col-sm-offset-5 col-sm-10">
<button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭 </button>
</div>
</div>
<th:block th:include="include :: footer" />
</body>
</html>

View File

@ -0,0 +1,183 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('值班配置列表')" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="dutyConfig-form">
<div class="select-list">
<ul>
<li>
配置名称:<input type="text" name="configName"/>
</li>
<li>
状态:<select name="status" th:with="type=${@dict.getType('sys_normal_disable')}">
<option value="">所有</option>
<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
</select>
</li>
<li class="select-time">
<label>开始时间: </label>
<input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginStartTime]"/>
<span>-</span>
<input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endStartTime]"/>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.addTab()" shiro:hasPermission="system:dutyConfig:add">
<i class="fa fa-plus"></i> 新增
</a>
<a class="btn btn-primary single disabled" onclick="$.operate.editTab()" shiro:hasPermission="system:dutyConfig:edit">
<i class="fa fa-edit"></i> 修改
</a>
<a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:dutyConfig:remove">
<i class="fa fa-remove"></i> 删除
</a>
<a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="system:dutyConfig:export">
<i class="fa fa-download"></i> 导出
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var editFlag = [[${@permission.hasPermi('system:dutyConfig:edit')}]];
var removeFlag = [[${@permission.hasPermi('system:dutyConfig:remove')}]];
var prefix = ctx + "system/dutyConfig";
$(function() {
var options = {
url: prefix + "/list",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove",
exportUrl: prefix + "/export",
sortName: "createTime",
sortOrder: "desc",
modalName: "值班配置",
columns: [{
checkbox: true
},
{
field: 'id',
title: '配置ID',
visible: false
},
{
field: 'configName',
title: '配置名称',
sortable: true
},
{
field: 'startTime',
title: '开始时间',
sortable: true,
formatter: function(value, row, index) {
return $.table.tooltip(value);
}
},
{
field: 'endTime',
title: '结束时间',
sortable: true,
formatter: function(value, row, index) {
return $.table.tooltip(value);
}
},
{
field: 'dutyUserNames',
title: '值班人员',
formatter: function(value, row, index) {
if (value && value.length > 20) {
return '<span title="' + value + '">' + value.substring(0, 20) + '...</span>';
}
return value || '';
}
},
{
field: 'isActive',
title: '是否生效',
align: 'center',
formatter: function(value, row, index) {
if (value) {
return '<span class="label label-success">生效中</span>';
} else {
return '<span class="label label-default">未生效</span>';
}
}
},
{
visible: editFlag == 'hidden' ? false : true,
title: '状态',
align: 'center',
formatter: function (value, row, index) {
return statusTools(row);
}
},
{
field: 'createTime',
title: '创建时间',
sortable: true
},
{
title: '操作',
align: 'center',
formatter: function(value, row, index) {
var actions = [];
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="detail(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a>');
return actions.join('');
}
}]
};
$.table.init(options);
});
/* 值班配置状态显示 */
function statusTools(row) {
if (row.status == '0') {
return '<i class=\"fa fa-toggle-off text-info fa-2x\" onclick="enable(\'' + row.id + '\')"></i> ';
} else {
return '<i class=\"fa fa-toggle-on text-info fa-2x\" onclick="disable(\'' + row.id + '\')"></i> ';
}
}
/* 值班配置管理-停用 */
function disable(id) {
$.modal.confirm("确认要停用该值班配置吗?", function() {
$.operate.post(prefix + "/changeStatus", { "id": id, "status": "0" });
})
}
/* 值班配置管理-启用 */
function enable(id) {
$.modal.confirm("确认要启用该值班配置吗?", function() {
$.operate.post(prefix + "/changeStatus", { "id": id, "status": "1" });
})
}
/* 详情 */
function detail(id) {
var url = prefix + '/detail/' + id;
$.modal.openTab("值班配置详情", url);
}
</script>
</body>
</html>

View File

@ -0,0 +1,197 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改值班配置')" />
<th:block th:include="include :: select2-css" />
<th:block th:include="include :: datetimepicker-css" />
</head>
<body>
<div class="main-content">
<form id="form-dutyConfig-edit" class="form-horizontal">
<input name="id" th:value="${dutyConfig.id}" type="hidden">
<h4 class="form-header h4">基本信息</h4>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">配置名称:</label>
<div class="col-sm-8">
<input name="configName" th:value="${dutyConfig.configName}" placeholder="请输入配置名称" class="form-control" type="text" maxlength="100" required>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label">状态:</label>
<div class="col-sm-8">
<label class="toggle-switch switch-solid">
<input type="checkbox" id="status" th:checked="${dutyConfig.status == '1'}">
<span></span>
</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">开始时间:</label>
<div class="col-sm-8">
<div class="input-group date">
<input name="startTime" th:value="${#dates.format(dutyConfig.startTime, 'yyyy-MM-dd HH:mm:ss')}" class="form-control" placeholder="yyyy-MM-dd HH:mm:ss" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-sm-4 control-label is-required">结束时间:</label>
<div class="col-sm-8">
<div class="input-group date">
<input name="endTime" th:value="${#dates.format(dutyConfig.endTime, 'yyyy-MM-dd HH:mm:ss')}" class="form-control" placeholder="yyyy-MM-dd HH:mm:ss" type="text" required>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-xs-2 control-label is-required">值班人员:</label>
<div class="col-xs-10">
<select id="dutyUserIds" name="dutyUserIds" class="form-control select2-multiple" multiple required>
<option th:each="user:${users}" th:value="${user.userId}" th:text="${user.userName}"
th:disabled="${user.status == '1'}"
th:selected="${dutyConfig.dutyUsers != null and #lists.contains(dutyConfig.dutyUsers.![userId], user.userId)}"></option>
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<label class="col-xs-2 control-label">备注:</label>
<div class="col-xs-10">
<textarea name="remark" th:text="${dutyConfig.remark}" maxlength="500" class="form-control" rows="3" placeholder="请输入备注信息"></textarea>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="row">
<div class="col-sm-offset-5 col-sm-10">
<button type="button" class="btn btn-sm btn-primary" onclick="submitHandler()"><i class="fa fa-check"></i>保 存</button>&nbsp;
<button type="button" class="btn btn-sm btn-danger" onclick="closeItem()"><i class="fa fa-reply-all"></i>关 闭 </button>
</div>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: select2-js" />
<th:block th:include="include :: datetimepicker-js" />
<script>
var prefix = ctx + "system/dutyConfig";
$("#form-dutyConfig-edit").validate({
onkeyup: false,
rules:{
configName:{
required: true,
minlength: 2,
maxlength: 100
},
startTime:{
required: true
},
endTime:{
required: true
},
dutyUserIds:{
required: true
}
},
messages: {
"configName": {
required: "请输入配置名称",
minlength: "配置名称不能小于2个字符",
maxlength: "配置名称不能超过100个字符"
},
"startTime": {
required: "请选择开始时间"
},
"endTime": {
required: "请选择结束时间"
},
"dutyUserIds": {
required: "请选择值班人员"
}
},
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
// 验证时间范围
var startTime = $("input[name='startTime']").val();
var endTime = $("input[name='endTime']").val();
if (startTime && endTime) {
var start = new Date(startTime);
var end = new Date(endTime);
if (start >= end) {
$.modal.alertWarning("开始时间必须早于结束时间");
return;
}
}
// 处理状态值0停用 1启用
var status = $("#status").is(':checked') == true ? "1" : "0";
// 处理值班人员 - 转换为逗号分隔的字符串
var dutyUserIds = $("#dutyUserIds").val();
if (dutyUserIds && dutyUserIds.length > 0) {
dutyUserIds = dutyUserIds.join(',');
} else {
dutyUserIds = '';
}
// 移除表单中的 dutyUserIds避免重复提交
var data = $("#form-dutyConfig-edit").serializeArray().filter(function(item) {
return item.name !== 'dutyUserIds';
});
data.push({"name": "status", "value": status});
data.push({"name": "dutyUserIds", "value": dutyUserIds});
$.operate.saveTab(prefix + "/edit", data);
}
}
$(function() {
// 初始化Select2
$('.select2-multiple').select2({
placeholder: "请选择值班人员",
allowClear: true
});
// 初始化日期时间选择器
$(".form-control[name='startTime']").datetimepicker({
format: "yyyy-mm-dd hh:ii:ss",
autoclose: true,
todayBtn: true,
language: 'zh-CN'
});
$(".form-control[name='endTime']").datetimepicker({
format: "yyyy-mm-dd hh:ii:ss",
autoclose: true,
todayBtn: true,
language: 'zh-CN'
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,143 @@
package com.ruoyi.system.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* 线索分配日志对象 clew_assign_log
*
* @author ruoyi
* @date 2024-12-19
*/
@Data
public class ClewAssignLog extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 线索ID */
@Excel(name = "线索ID")
private Long clewId;
/** 使用的配置ID */
@Excel(name = "配置ID")
private Long configId;
/** 分配的用户ID */
@Excel(name = "分配用户ID")
private Long assignedUserId;
/** 分配时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "分配时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date assignTime;
/** 分配规则ROUND_ROBIN轮询/DEFAULT默认 */
@Excel(name = "分配规则")
private String assignRule;
/** 备注 */
@Excel(name = "备注")
private String remark;
/** 分配用户名称(用于显示,不存储到数据库) */
private String assignedUserName;
/** 配置名称(用于显示,不存储到数据库) */
private String configName;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setClewId(Long clewId)
{
this.clewId = clewId;
}
public Long getClewId()
{
return clewId;
}
public void setConfigId(Long configId)
{
this.configId = configId;
}
public Long getConfigId()
{
return configId;
}
public void setAssignedUserId(Long assignedUserId)
{
this.assignedUserId = assignedUserId;
}
public Long getAssignedUserId()
{
return assignedUserId;
}
public void setAssignTime(Date assignTime)
{
this.assignTime = assignTime;
}
public Date getAssignTime()
{
return assignTime;
}
public void setAssignRule(String assignRule)
{
this.assignRule = assignRule;
}
public String getAssignRule()
{
return assignRule;
}
public void setRemark(String remark)
{
this.remark = remark;
}
public String getRemark()
{
return remark;
}
public void setAssignedUserName(String assignedUserName)
{
this.assignedUserName = assignedUserName;
}
public String getAssignedUserName()
{
return assignedUserName;
}
public void setConfigName(String configName)
{
this.configName = configName;
}
public String getConfigName()
{
return configName;
}
}

View File

@ -0,0 +1,115 @@
package com.ruoyi.system.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* 分配计数器对象 duty_assign_counter
*
* @author ruoyi
* @date 2024-12-19
*/
@Data
public class DutyAssignCounter extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 配置ID */
@Excel(name = "配置ID")
private Long configId;
/** 上次分配的用户ID */
@Excel(name = "上次分配用户ID")
private Long lastAssignedUserId;
/** 分配次数 */
@Excel(name = "分配次数")
private Integer assignCount;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/** 配置名称(用于显示,不存储到数据库) */
private String configName;
/** 上次分配用户名称(用于显示,不存储到数据库) */
private String lastAssignedUserName;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setConfigId(Long configId)
{
this.configId = configId;
}
public Long getConfigId()
{
return configId;
}
public void setLastAssignedUserId(Long lastAssignedUserId)
{
this.lastAssignedUserId = lastAssignedUserId;
}
public Long getLastAssignedUserId()
{
return lastAssignedUserId;
}
public void setAssignCount(Integer assignCount)
{
this.assignCount = assignCount;
}
public Integer getAssignCount()
{
return assignCount;
}
public void setUpdateTime(Date updateTime)
{
this.updateTime = updateTime;
}
public Date getUpdateTime()
{
return updateTime;
}
public void setConfigName(String configName)
{
this.configName = configName;
}
public String getConfigName()
{
return configName;
}
public void setLastAssignedUserName(String lastAssignedUserName)
{
this.lastAssignedUserName = lastAssignedUserName;
}
public String getLastAssignedUserName()
{
return lastAssignedUserName;
}
}

View File

@ -0,0 +1,138 @@
package com.ruoyi.system.domain;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
/**
* 值班配置对象 duty_config
*
* @author ruoyi
* @date 2024-01-15
*/
@Data
public class DutyConfig extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 配置名称 */
@Excel(name = "配置名称")
private String configName;
/** 开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/** 结束时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/** 状态0停用 1启用 */
@Excel(name = "状态", readConverterExp = "0=停用,1=启用")
private String status;
/** 备注 */
@Excel(name = "备注")
private String remark;
/** 删除标志0正常 1删除 */
private String delFlag;
/** 值班用户列表(用于显示和操作,不存储到数据库) */
private List<DutyUser> dutyUsers;
/** 值班用户名称列表(用于显示,不存储到数据库) */
private String dutyUserNames;
/** 是否在当前时间范围内(用于前端显示,不存储到数据库) */
private Boolean isActive;
public void setConfigName(String configName)
{
this.configName = configName;
}
public String getConfigName()
{
return configName;
}
public void setStartTime(Date startTime)
{
this.startTime = startTime;
}
public Date getStartTime()
{
return startTime;
}
public void setEndTime(Date endTime)
{
this.endTime = endTime;
}
public Date getEndTime()
{
return endTime;
}
public void setStatus(String status)
{
this.status = status;
}
public String getStatus()
{
return status;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}
public String getDelFlag()
{
return delFlag;
}
public void setDutyUsers(List<DutyUser> dutyUsers)
{
this.dutyUsers = dutyUsers;
}
public List<DutyUser> getDutyUsers()
{
return dutyUsers;
}
public void setDutyUserNames(String dutyUserNames)
{
this.dutyUserNames = dutyUserNames;
}
public String getDutyUserNames()
{
return dutyUserNames;
}
public void setIsActive(Boolean isActive)
{
this.isActive = isActive;
}
public Boolean getIsActive()
{
return isActive;
}
}

View File

@ -0,0 +1,91 @@
package com.ruoyi.system.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
* 值班用户关联对象 duty_user
*
* @author ruoyi
* @date 2024-12-19
*/
@Data
public class DutyUser extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 配置ID */
@Excel(name = "配置ID")
private Long configId;
/** 用户ID */
@Excel(name = "用户ID")
private Long userId;
/** 排序顺序 */
@Excel(name = "排序顺序")
private Integer sortOrder;
/** 用户名称(用于显示,不存储到数据库) */
private String userName;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setConfigId(Long configId)
{
this.configId = configId;
}
public Long getConfigId()
{
return configId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public Long getUserId()
{
return userId;
}
public void setSortOrder(Integer sortOrder)
{
this.sortOrder = sortOrder;
}
public Integer getSortOrder()
{
return sortOrder;
}
public void setUserName(String userName)
{
this.userName = userName;
}
public String getUserName()
{
return userName;
}
}

View File

@ -0,0 +1,90 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.DutyAssignCounter;
import org.apache.ibatis.annotations.Param;
/**
* 分配计数器Mapper接口
*
* @author ruoyi
* @date 2024-12-19
*/
public interface DutyAssignCounterMapper
{
/**
* 查询分配计数器
*
* @param id 分配计数器主键
* @return 分配计数器
*/
public DutyAssignCounter selectDutyAssignCounterById(Long id);
/**
* 根据配置ID查询分配计数器
*
* @param configId 配置ID
* @return 分配计数器
*/
public DutyAssignCounter selectDutyAssignCounterByConfigId(Long configId);
/**
* 查询分配计数器列表
*
* @param dutyAssignCounter 分配计数器
* @return 分配计数器集合
*/
public List<DutyAssignCounter> selectDutyAssignCounterList(DutyAssignCounter dutyAssignCounter);
/**
* 新增分配计数器
*
* @param dutyAssignCounter 分配计数器
* @return 结果
*/
public int insertDutyAssignCounter(DutyAssignCounter dutyAssignCounter);
/**
* 修改分配计数器
*
* @param dutyAssignCounter 分配计数器
* @return 结果
*/
public int updateDutyAssignCounter(DutyAssignCounter dutyAssignCounter);
/**
* 删除分配计数器
*
* @param id 分配计数器主键
* @return 结果
*/
public int deleteDutyAssignCounterById(Long id);
/**
* 批量删除分配计数器
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteDutyAssignCounterByIds(String[] ids);
/**
* 根据配置ID删除分配计数器
*
* @param configId 配置ID
* @return 结果
*/
public int deleteDutyAssignCounterByConfigId(Long configId);
/**
* 更新分配计数器用于轮询算法
*
* @param configId 配置ID
* @param lastAssignedUserId 上次分配的用户ID
* @param assignCount 分配次数
* @return 结果
*/
public int updateAssignCounter(@Param("configId") Long configId,
@Param("lastAssignedUserId") Long lastAssignedUserId,
@Param("assignCount") Integer assignCount);
}

View File

@ -0,0 +1,83 @@
package com.ruoyi.system.mapper;
import java.util.Date;
import java.util.List;
import com.ruoyi.system.domain.DutyConfig;
import org.apache.ibatis.annotations.Param;
/**
* 值班配置Mapper接口
*
* @author ruoyi
* @date 2024-01-15
*/
public interface DutyConfigMapper
{
/**
* 查询值班配置
*
* @param id 值班配置主键
* @return 值班配置
*/
public DutyConfig selectDutyConfigById(Long id);
/**
* 查询值班配置列表
*
* @param dutyConfig 值班配置
* @return 值班配置集合
*/
public List<DutyConfig> selectDutyConfigList(DutyConfig dutyConfig);
/**
* 根据当前时间查询有效的值班配置
*
* @param currentTime 当前时间
* @return 值班配置
*/
public DutyConfig selectActiveDutyConfig(@Param("currentTime") Date currentTime);
/**
* 新增值班配置
*
* @param dutyConfig 值班配置
* @return 结果
*/
public int insertDutyConfig(DutyConfig dutyConfig);
/**
* 修改值班配置
*
* @param dutyConfig 值班配置
* @return 结果
*/
public int updateDutyConfig(DutyConfig dutyConfig);
/**
* 删除值班配置
*
* @param id 值班配置主键
* @return 结果
*/
public int deleteDutyConfigById(Long id);
/**
* 批量删除值班配置
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteDutyConfigByIds(String[] ids);
/**
* 检查时间段是否冲突
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param excludeId 排除的配置ID用于编辑时排除自己
* @return 冲突的配置数量
*/
public int checkTimeConflict(@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("excludeId") Long excludeId);
}

View File

@ -0,0 +1,103 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.DutyUser;
import org.apache.ibatis.annotations.Param;
/**
* 值班用户关联Mapper接口
*
* @author ruoyi
* @date 2024-12-19
*/
public interface DutyUserMapper
{
/**
* 查询值班用户关联
*
* @param id 值班用户关联主键
* @return 值班用户关联
*/
public DutyUser selectDutyUserById(Long id);
/**
* 查询值班用户关联列表
*
* @param dutyUser 值班用户关联
* @return 值班用户关联集合
*/
public List<DutyUser> selectDutyUserList(DutyUser dutyUser);
/**
* 根据配置ID查询值班用户列表
*
* @param configId 配置ID
* @return 值班用户关联集合
*/
public List<DutyUser> selectDutyUsersByConfigId(Long configId);
/**
* 根据配置ID查询值班用户列表按排序顺序
*
* @param configId 配置ID
* @return 值班用户关联集合
*/
public List<DutyUser> selectDutyUsersByConfigIdOrderBySort(Long configId);
/**
* 新增值班用户关联
*
* @param dutyUser 值班用户关联
* @return 结果
*/
public int insertDutyUser(DutyUser dutyUser);
/**
* 批量新增值班用户关联
*
* @param dutyUsers 值班用户关联列表
* @return 结果
*/
public int insertDutyUserBatch(List<DutyUser> dutyUsers);
/**
* 修改值班用户关联
*
* @param dutyUser 值班用户关联
* @return 结果
*/
public int updateDutyUser(DutyUser dutyUser);
/**
* 删除值班用户关联
*
* @param id 值班用户关联主键
* @return 结果
*/
public int deleteDutyUserById(Long id);
/**
* 批量删除值班用户关联
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteDutyUserByIds(String ids);
/**
* 根据配置ID删除值班用户关联
*
* @param configId 配置ID
* @return 结果
*/
public int deleteDutyUserByConfigId(Long configId);
/**
* 检查用户是否已经在配置中
*
* @param configId 配置ID
* @param userId 用户ID
* @return 结果
*/
public int checkUserExists(@Param("configId") Long configId, @Param("userId") Long userId);
}

View File

@ -0,0 +1,97 @@
package com.ruoyi.system.service;
import java.util.Date;
import java.util.List;
import com.ruoyi.system.domain.DutyConfig;
/**
* 值班配置Service接口
*
* @author ruoyi
* @date 2024-12-19
*/
public interface IDutyConfigService
{
/**
* 查询值班配置
*
* @param id 值班配置主键
* @return 值班配置
*/
public DutyConfig selectDutyConfigById(Long id);
/**
* 查询值班配置列表
*
* @param dutyConfig 值班配置
* @return 值班配置集合
*/
public List<DutyConfig> selectDutyConfigList(DutyConfig dutyConfig);
/**
* 根据当前时间获取下一个值班用户ID
*
* @return 值班用户ID如果没有匹配的配置则返回null
*/
public Long getNextDutyUserId();
/**
* 根据当前时间查询有效的值班配置
*
* @param currentTime 当前时间
* @return 值班配置
*/
public DutyConfig selectActiveDutyConfig(Date currentTime);
/**
* 新增值班配置
*
* @param dutyConfig 值班配置
* @param dutyUserIds 值班用户ID字符串逗号分隔
* @return 结果
*/
public int insertDutyConfig(DutyConfig dutyConfig, String dutyUserIds);
/**
* 修改值班配置
*
* @param dutyConfig 值班配置
* @param dutyUserIds 值班用户ID字符串逗号分隔
* @return 结果
*/
public int updateDutyConfig(DutyConfig dutyConfig, String dutyUserIds);
/**
* 批量删除值班配置
*
* @param ids 需要删除的值班配置主键集合
* @return 结果
*/
public int deleteDutyConfigByIds(String ids);
/**
* 删除值班配置信息
*
* @param id 值班配置主键
* @return 结果
*/
public int deleteDutyConfigById(Long id);
/**
* 检查时间段是否冲突
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param excludeId 排除的配置ID用于修改时排除自己
* @return 是否冲突
*/
public boolean checkTimeConflict(Date startTime, Date endTime, Long excludeId);
/**
* 修改值班配置状态
*
* @param dutyConfig 值班配置
* @return 结果
*/
public int changeStatus(DutyConfig dutyConfig);
}

View File

@ -0,0 +1,322 @@
package com.ruoyi.system.service.impl;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.system.mapper.DutyConfigMapper;
import com.ruoyi.system.mapper.DutyUserMapper;
import com.ruoyi.system.mapper.DutyAssignCounterMapper;
import com.ruoyi.system.domain.DutyConfig;
import com.ruoyi.system.domain.DutyUser;
import com.ruoyi.system.domain.DutyAssignCounter;
import com.ruoyi.system.service.IDutyConfigService;
import com.ruoyi.common.core.text.Convert;
/**
* 值班配置Service业务层处理
*
* @author ruoyi
* @date 2024-01-15
*/
@Service
public class DutyConfigServiceImpl implements IDutyConfigService
{
@Autowired
private DutyConfigMapper dutyConfigMapper;
@Autowired
private DutyUserMapper dutyUserMapper;
@Autowired
private DutyAssignCounterMapper dutyAssignCounterMapper;
/**
* 查询值班配置
*
* @param id 值班配置主键
* @return 值班配置
*/
@Override
public DutyConfig selectDutyConfigById(Long id)
{
DutyConfig dutyConfig = dutyConfigMapper.selectDutyConfigById(id);
if (dutyConfig != null) {
// 设置值班用户名称
List<DutyUser> dutyUsers = dutyConfig.getDutyUsers();
if (dutyUsers != null && !dutyUsers.isEmpty()) {
String userNames = dutyUsers.stream()
.map(DutyUser::getUserName)
.collect(Collectors.joining(","));
dutyConfig.setDutyUserNames(userNames);
}
}
return dutyConfig;
}
/**
* 查询值班配置列表
*
* @param dutyConfig 值班配置
* @return 值班配置
*/
@Override
public List<DutyConfig> selectDutyConfigList(DutyConfig dutyConfig)
{
List<DutyConfig> list = dutyConfigMapper.selectDutyConfigList(dutyConfig);
// 为每个配置设置用户名称和是否激活状态
Date now = new Date();
for (DutyConfig config : list) {
// 设置值班用户名称
List<DutyUser> dutyUsers = config.getDutyUsers();
if (dutyUsers != null && !dutyUsers.isEmpty()) {
String userNames = dutyUsers.stream()
.map(DutyUser::getUserName)
.collect(Collectors.joining(","));
config.setDutyUserNames(userNames);
}
// 设置是否在当前时间范围内
if ("1".equals(config.getStatus()) &&
config.getStartTime() != null && config.getEndTime() != null) {
config.setIsActive(now.compareTo(config.getStartTime()) >= 0 &&
now.compareTo(config.getEndTime()) <= 0);
} else {
config.setIsActive(false);
}
}
return list;
}
/**
* 根据当前时间获取下一个值班用户ID轮询分配
*
* @return 值班用户ID如果没有匹配的配置则返回null
*/
@Override
@Transactional
public Long getNextDutyUserId()
{
Date currentTime = new Date();
DutyConfig activeDutyConfig = dutyConfigMapper.selectActiveDutyConfig(currentTime);
if (activeDutyConfig == null) {
return null;
}
// 获取值班用户列表按排序顺序
List<DutyUser> dutyUsers = dutyUserMapper.selectDutyUsersByConfigIdOrderBySort(activeDutyConfig.getId());
if (dutyUsers == null || dutyUsers.isEmpty()) {
return null;
}
// 如果只有一个用户直接返回
if (dutyUsers.size() == 1) {
updateAssignCounter(activeDutyConfig.getId(), dutyUsers.get(0).getUserId());
return dutyUsers.get(0).getUserId();
}
// 查询分配计数器
DutyAssignCounter counter = dutyAssignCounterMapper.selectDutyAssignCounterByConfigId(activeDutyConfig.getId());
Long nextUserId = null;
if (counter == null) {
// 第一次分配返回第一个用户
nextUserId = dutyUsers.get(0).getUserId();
// 创建计数器
counter = new DutyAssignCounter();
counter.setConfigId(activeDutyConfig.getId());
counter.setLastAssignedUserId(nextUserId);
counter.setAssignCount(1);
counter.setUpdateTime(currentTime);
dutyAssignCounterMapper.insertDutyAssignCounter(counter);
} else {
// 轮询找到上次分配用户的下一个用户
Long lastUserId = counter.getLastAssignedUserId();
int lastIndex = -1;
// 找到上次分配用户的索引
for (int i = 0; i < dutyUsers.size(); i++) {
if (dutyUsers.get(i).getUserId().equals(lastUserId)) {
lastIndex = i;
break;
}
}
// 计算下一个用户的索引循环轮询
int nextIndex = (lastIndex + 1) % dutyUsers.size();
nextUserId = dutyUsers.get(nextIndex).getUserId();
// 更新计数器
Integer assignCount = counter.getAssignCount() == null ? 1 : counter.getAssignCount() + 1;
dutyAssignCounterMapper.updateAssignCounter(activeDutyConfig.getId(), nextUserId, assignCount);
}
return nextUserId;
}
/**
* 更新分配计数器内部方法
*/
private void updateAssignCounter(Long configId, Long userId) {
DutyAssignCounter counter = dutyAssignCounterMapper.selectDutyAssignCounterByConfigId(configId);
if (counter == null) {
counter = new DutyAssignCounter();
counter.setConfigId(configId);
counter.setLastAssignedUserId(userId);
counter.setAssignCount(1);
counter.setUpdateTime(new Date());
dutyAssignCounterMapper.insertDutyAssignCounter(counter);
} else {
Integer assignCount = counter.getAssignCount() == null ? 1 : counter.getAssignCount() + 1;
dutyAssignCounterMapper.updateAssignCounter(configId, userId, assignCount);
}
}
/**
* 根据当前时间查询有效的值班配置
*
* @param currentTime 当前时间
* @return 值班配置
*/
@Override
public DutyConfig selectActiveDutyConfig(Date currentTime)
{
return dutyConfigMapper.selectActiveDutyConfig(currentTime);
}
/**
* 新增值班配置
*
* @param dutyConfig 值班配置
* @param dutyUserIds 值班用户ID字符串逗号分隔
* @return 结果
*/
@Override
@Transactional
public int insertDutyConfig(DutyConfig dutyConfig, String dutyUserIds)
{
// 创建时间由数据库 sysdate() 自动设置无需手动设置
int result = dutyConfigMapper.insertDutyConfig(dutyConfig);
// 处理值班用户关联
if (result > 0 && dutyUserIds != null && !dutyUserIds.trim().isEmpty()) {
String[] userIdArray = dutyUserIds.split(",");
List<DutyUser> dutyUsers = new ArrayList<>();
for (int i = 0; i < userIdArray.length; i++) {
DutyUser dutyUser = new DutyUser();
dutyUser.setConfigId(dutyConfig.getId());
dutyUser.setUserId(Long.parseLong(userIdArray[i].trim()));
dutyUser.setSortOrder(i + 1);
dutyUser.setCreateTime(DateUtils.getNowDate());
dutyUsers.add(dutyUser);
}
dutyUserMapper.insertDutyUserBatch(dutyUsers);
}
return result;
}
/**
* 修改值班配置
*
* @param dutyConfig 值班配置
* @param dutyUserIds 值班用户ID字符串逗号分隔
* @return 结果
*/
@Override
@Transactional
public int updateDutyConfig(DutyConfig dutyConfig, String dutyUserIds)
{
dutyConfig.setUpdateTime(DateUtils.getNowDate());
int result = dutyConfigMapper.updateDutyConfig(dutyConfig);
// 先删除原有的值班用户关联
dutyUserMapper.deleteDutyUserByConfigId(dutyConfig.getId());
// 重新添加值班用户关联
if (result > 0 && dutyUserIds != null && !dutyUserIds.trim().isEmpty()) {
String[] userIdArray = dutyUserIds.split(",");
List<DutyUser> dutyUsers = new ArrayList<>();
for (int i = 0; i < userIdArray.length; i++) {
DutyUser dutyUser = new DutyUser();
dutyUser.setConfigId(dutyConfig.getId());
dutyUser.setUserId(Long.parseLong(userIdArray[i].trim()));
dutyUser.setSortOrder(i + 1);
dutyUser.setCreateTime(DateUtils.getNowDate());
dutyUsers.add(dutyUser);
}
dutyUserMapper.insertDutyUserBatch(dutyUsers);
}
return result;
}
/**
* 批量删除值班配置
*
* @param ids 需要删除的值班配置主键
* @return 结果
*/
@Override
@Transactional
public int deleteDutyConfigByIds(String ids)
{
String[] configIds = Convert.toStrArray(ids);
// 删除值班用户关联关系
for (String configId : configIds) {
dutyUserMapper.deleteDutyUserByConfigId(Long.parseLong(configId));
}
return dutyConfigMapper.deleteDutyConfigByIds(configIds);
}
/**
* 删除值班配置信息
*
* @param id 值班配置主键
* @return 结果
*/
@Override
@Transactional
public int deleteDutyConfigById(Long id)
{
// 删除值班用户关联关系
dutyUserMapper.deleteDutyUserByConfigId(id);
return dutyConfigMapper.deleteDutyConfigById(id);
}
/**
* 检查时间段是否冲突
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param excludeId 排除的配置ID用于编辑时排除自己
* @return 是否冲突
*/
@Override
public boolean checkTimeConflict(Date startTime, Date endTime, Long excludeId)
{
int count = dutyConfigMapper.checkTimeConflict(startTime, endTime, excludeId);
return count > 0;
}
/**
* 修改值班配置状态
*
* @param dutyConfig 值班配置
* @return 结果
*/
@Override
public int changeStatus(DutyConfig dutyConfig)
{
dutyConfig.setUpdateTime(DateUtils.getNowDate());
return dutyConfigMapper.updateDutyConfig(dutyConfig);
}
}

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.DutyAssignCounterMapper">
<resultMap type="DutyAssignCounter" id="DutyAssignCounterResult">
<result property="id" column="id" />
<result property="configId" column="config_id" />
<result property="lastAssignedUserId" column="last_assigned_user_id" />
<result property="assignCount" column="assign_count" />
<result property="updateTime" column="update_time" />
<result property="configName" column="config_name" />
<result property="lastAssignedUserName" column="last_assigned_user_name" />
</resultMap>
<sql id="selectDutyAssignCounterVo">
select dac.id, dac.config_id, dac.last_assigned_user_id, dac.assign_count, dac.update_time,
dc.config_name, u.user_name as last_assigned_user_name
from duty_assign_counter dac
left join duty_config dc on dac.config_id = dc.id
left join sys_user u on dac.last_assigned_user_id = u.user_id
</sql>
<select id="selectDutyAssignCounterList" parameterType="DutyAssignCounter" resultMap="DutyAssignCounterResult">
<include refid="selectDutyAssignCounterVo"/>
<where>
<if test="configId != null "> and dac.config_id = #{configId}</if>
<if test="lastAssignedUserId != null "> and dac.last_assigned_user_id = #{lastAssignedUserId}</if>
</where>
order by dac.update_time desc
</select>
<select id="selectDutyAssignCounterById" parameterType="Long" resultMap="DutyAssignCounterResult">
<include refid="selectDutyAssignCounterVo"/>
where dac.id = #{id}
</select>
<select id="selectDutyAssignCounterByConfigId" parameterType="Long" resultMap="DutyAssignCounterResult">
<include refid="selectDutyAssignCounterVo"/>
where dac.config_id = #{configId}
</select>
<insert id="insertDutyAssignCounter" parameterType="DutyAssignCounter" useGeneratedKeys="true" keyProperty="id">
insert into duty_assign_counter
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="configId != null">config_id,</if>
<if test="lastAssignedUserId != null">last_assigned_user_id,</if>
<if test="assignCount != null">assign_count,</if>
update_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="configId != null">#{configId},</if>
<if test="lastAssignedUserId != null">#{lastAssignedUserId},</if>
<if test="assignCount != null">#{assignCount},</if>
sysdate()
</trim>
</insert>
<update id="updateDutyAssignCounter" parameterType="DutyAssignCounter">
update duty_assign_counter
<trim prefix="SET" suffixOverrides=",">
<if test="configId != null">config_id = #{configId},</if>
<if test="lastAssignedUserId != null">last_assigned_user_id = #{lastAssignedUserId},</if>
<if test="assignCount != null">assign_count = #{assignCount},</if>
update_time = sysdate()
</trim>
where id = #{id}
</update>
<update id="updateAssignCounter">
update duty_assign_counter
set last_assigned_user_id = #{lastAssignedUserId},
assign_count = #{assignCount},
update_time = sysdate()
where config_id = #{configId}
</update>
<delete id="deleteDutyAssignCounterById" parameterType="Long">
delete from duty_assign_counter where id = #{id}
</delete>
<delete id="deleteDutyAssignCounterByIds" parameterType="String">
delete from duty_assign_counter where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<delete id="deleteDutyAssignCounterByConfigId" parameterType="Long">
delete from duty_assign_counter where config_id = #{configId}
</delete>
</mapper>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.DutyConfigMapper">
<resultMap type="DutyConfig" id="DutyConfigResult">
<result property="id" column="id" />
<result property="configName" column="config_name" />
<result property="startTime" column="start_time" />
<result property="endTime" column="end_time" />
<result property="status" column="status" />
<result property="remark" column="remark" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="delFlag" column="del_flag" />
<collection property="dutyUsers" ofType="DutyUser" column="id" select="com.ruoyi.system.mapper.DutyUserMapper.selectDutyUsersByConfigIdOrderBySort"/>
</resultMap>
<sql id="selectDutyConfigVo">
select id, config_name, start_time, end_time, status, remark, create_by, create_time, update_by, update_time, del_flag from duty_config
</sql>
<select id="selectDutyConfigList" parameterType="DutyConfig" resultMap="DutyConfigResult">
<include refid="selectDutyConfigVo"/>
<where>
del_flag = '0'
<if test="configName != null and configName != ''"> and config_name like concat('%', #{configName}, '%')</if>
<if test="startTime != null "> and start_time = #{startTime}</if>
<if test="endTime != null "> and end_time = #{endTime}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
</where>
order by create_time desc
</select>
<select id="selectDutyConfigById" parameterType="Long" resultMap="DutyConfigResult">
<include refid="selectDutyConfigVo"/>
where id = #{id}
</select>
<select id="selectActiveDutyConfig" parameterType="java.util.Date" resultMap="DutyConfigResult">
<include refid="selectDutyConfigVo"/>
where status = '1'
and del_flag = '0'
and #{currentTime} between start_time and end_time
order by create_time desc
limit 1
</select>
<select id="checkTimeConflict" resultType="int">
select count(1) from duty_config
where status = '1'
and del_flag = '0'
and (
(#{startTime} between start_time and end_time)
or (#{endTime} between start_time and end_time)
or (start_time between #{startTime} and #{endTime})
or (end_time between #{startTime} and #{endTime})
)
<if test="excludeId != null">
and id != #{excludeId}
</if>
</select>
<insert id="insertDutyConfig" parameterType="DutyConfig" useGeneratedKeys="true" keyProperty="id">
insert into duty_config
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="configName != null and configName != ''">config_name,</if>
<if test="startTime != null">start_time,</if>
<if test="endTime != null">end_time,</if>
<if test="status != null">status,</if>
<if test="remark != null">remark,</if>
<if test="createBy != null">create_by,</if>
<if test="delFlag != null">del_flag,</if>
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="configName != null and configName != ''">#{configName},</if>
<if test="startTime != null">#{startTime},</if>
<if test="endTime != null">#{endTime},</if>
<if test="status != null">#{status},</if>
<if test="remark != null">#{remark},</if>
<if test="createBy != null">#{createBy},</if>
<if test="delFlag != null">#{delFlag},</if>
sysdate()
</trim>
</insert>
<update id="updateDutyConfig" parameterType="DutyConfig">
update duty_config
<trim prefix="SET" suffixOverrides=",">
<if test="configName != null and configName != ''">config_name = #{configName},</if>
<if test="startTime != null">start_time = #{startTime},</if>
<if test="endTime != null">end_time = #{endTime},</if>
<if test="status != null">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
update_time = sysdate()
</trim>
where id = #{id}
</update>
<delete id="deleteDutyConfigById" parameterType="Long">
update duty_config set del_flag = '1' where id = #{id}
</delete>
<delete id="deleteDutyConfigByIds" parameterType="java.lang.String">
update duty_config set del_flag = '1' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.DutyUserMapper">
<resultMap type="DutyUser" id="DutyUserResult">
<result property="id" column="id" />
<result property="configId" column="config_id" />
<result property="userId" column="user_id" />
<result property="sortOrder" column="sort_order" />
<result property="createTime" column="create_time" />
<result property="userName" column="user_name" />
</resultMap>
<sql id="selectDutyUserVo">
select du.id, du.config_id, du.user_id, du.sort_order, du.create_time,
u.user_name
from duty_user du
left join sys_user u on du.user_id = u.user_id
</sql>
<select id="selectDutyUserList" parameterType="DutyUser" resultMap="DutyUserResult">
<include refid="selectDutyUserVo"/>
<where>
<if test="configId != null "> and du.config_id = #{configId}</if>
<if test="userId != null "> and du.user_id = #{userId}</if>
</where>
order by du.config_id, du.sort_order
</select>
<select id="selectDutyUserById" parameterType="Long" resultMap="DutyUserResult">
<include refid="selectDutyUserVo"/>
where du.id = #{id}
</select>
<select id="selectDutyUsersByConfigId" parameterType="Long" resultMap="DutyUserResult">
<include refid="selectDutyUserVo"/>
where du.config_id = #{configId}
order by du.sort_order
</select>
<select id="selectDutyUsersByConfigIdOrderBySort" parameterType="Long" resultMap="DutyUserResult">
<include refid="selectDutyUserVo"/>
where du.config_id = #{configId}
order by du.sort_order asc
</select>
<select id="checkUserExists" resultType="int">
select count(1) from duty_user
where config_id = #{configId} and user_id = #{userId}
</select>
<insert id="insertDutyUser" parameterType="DutyUser" useGeneratedKeys="true" keyProperty="id">
insert into duty_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="configId != null">config_id,</if>
<if test="userId != null">user_id,</if>
<if test="sortOrder != null">sort_order,</if>
<if test="createTime != null">create_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="configId != null">#{configId},</if>
<if test="userId != null">#{userId},</if>
<if test="sortOrder != null">#{sortOrder},</if>
<if test="createTime != null">#{createTime},</if>
</trim>
</insert>
<insert id="insertDutyUserBatch" parameterType="java.util.List">
insert into duty_user(config_id, user_id, sort_order, create_time) values
<foreach collection="list" item="item" separator=",">
(#{item.configId}, #{item.userId}, #{item.sortOrder}, #{item.createTime})
</foreach>
</insert>
<update id="updateDutyUser" parameterType="DutyUser">
update duty_user
<trim prefix="SET" suffixOverrides=",">
<if test="configId != null">config_id = #{configId},</if>
<if test="userId != null">user_id = #{userId},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteDutyUserById" parameterType="Long">
delete from duty_user where id = #{id}
</delete>
<delete id="deleteDutyUserByIds" parameterType="String">
delete from duty_user where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<delete id="deleteDutyUserByConfigId" parameterType="Long">
delete from duty_user where config_id = #{configId}
</delete>
</mapper>