diff --git a/.trae/documents/线索值班配置系统技术架构文档.md b/.trae/documents/线索值班配置系统技术架构文档.md new file mode 100644 index 00000000..5994932e --- /dev/null +++ b/.trae/documents/线索值班配置系统技术架构文档.md @@ -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": \ No newline at end of file diff --git a/.trae/documents/线索值班配置系统需求文档.md b/.trae/documents/线索值班配置系统需求文档.md new file mode 100644 index 00000000..2fd1abb4 --- /dev/null +++ b/.trae/documents/线索值班配置系统需求文档.md @@ -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及以上分辨率 +- 表格在小屏幕下支持横向滚动 +- 表单在移动端自动调整为单列布局 +- 支持触摸操作优化,按钮间距适配手指点击 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7b016a89 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/ClewController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/ClewController.java index 8d8f5340..1b65d332 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/ClewController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/ClewController.java @@ -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,8 +252,34 @@ 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(); @@ -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); + } + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DutyConfigController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DutyConfigController.java new file mode 100644 index 00000000..0ebbf8ce --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/DutyConfigController.java @@ -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 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 list = dutyConfigService.selectDutyConfigList(dutyConfig); + ExcelUtil util = new ExcelUtil(DutyConfig.class); + return util.exportExcel(list, "值班配置数据"); + } + + /** + * 新增值班配置 + */ + @GetMapping("/add") + public String add(ModelMap mmap) + { + // 获取所有可用用户 + SysUser user = new SysUser(); + user.setStatus("0"); // 正常状态 + List 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 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 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"; + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/dutyConfig/add.html b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/add.html new file mode 100644 index 00000000..ac85b6f1 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/add.html @@ -0,0 +1,194 @@ + + + + + + + + +
+
+

基本信息

+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+   + +
+
+ + + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/dutyConfig/detail.html b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/detail.html new file mode 100644 index 00000000..cff1e7fc --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/detail.html @@ -0,0 +1,129 @@ + + + + + + +
+
+

基本信息

+
+
+
+ +
+

+
+
+
+
+
+ +
+

+ 启用 + 停用 +

+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+ +
+

+ 生效中 + 未生效 +

+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+ +
+

+
+
+
+
+

其他信息

+
+
+
+ +
+

+
+
+
+
+
+ +
+

+
+
+
+
+
+
+
+ +
+

+
+
+
+
+
+ +
+

+
+
+
+
+
+
+ +
+
+ +
+
+ + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/dutyConfig/dutyConfig.html b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/dutyConfig.html new file mode 100644 index 00000000..5f22ae1e --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/dutyConfig.html @@ -0,0 +1,183 @@ + + + + + + +
+
+
+
+
+
    +
  • + 配置名称: +
  • +
  • + 状态: +
  • +
  • + + + - + +
  • +
  • +  搜索 +  重置 +
  • +
+
+
+
+ + + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/system/dutyConfig/edit.html b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/edit.html new file mode 100644 index 00000000..b7ce544a --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/system/dutyConfig/edit.html @@ -0,0 +1,197 @@ + + + + + + + + +
+
+ +

基本信息

+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+   + +
+
+ + + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/ClewAssignLog.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/ClewAssignLog.java new file mode 100644 index 00000000..f1657384 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/ClewAssignLog.java @@ -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; + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyAssignCounter.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyAssignCounter.java new file mode 100644 index 00000000..78d7d3f1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyAssignCounter.java @@ -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; + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyConfig.java new file mode 100644 index 00000000..55bc8763 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyConfig.java @@ -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 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 dutyUsers) + { + this.dutyUsers = dutyUsers; + } + + public List 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; + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyUser.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyUser.java new file mode 100644 index 00000000..8ca880ad --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/DutyUser.java @@ -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; + } + + +} + \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyAssignCounterMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyAssignCounterMapper.java new file mode 100644 index 00000000..5da02a7a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyAssignCounterMapper.java @@ -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 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); +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyConfigMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyConfigMapper.java new file mode 100644 index 00000000..24723338 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyConfigMapper.java @@ -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 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); +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyUserMapper.java new file mode 100644 index 00000000..af548bb8 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DutyUserMapper.java @@ -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 selectDutyUserList(DutyUser dutyUser); + + /** + * 根据配置ID查询值班用户列表 + * + * @param configId 配置ID + * @return 值班用户关联集合 + */ + public List selectDutyUsersByConfigId(Long configId); + + /** + * 根据配置ID查询值班用户列表(按排序顺序) + * + * @param configId 配置ID + * @return 值班用户关联集合 + */ + public List selectDutyUsersByConfigIdOrderBySort(Long configId); + + /** + * 新增值班用户关联 + * + * @param dutyUser 值班用户关联 + * @return 结果 + */ + public int insertDutyUser(DutyUser dutyUser); + + /** + * 批量新增值班用户关联 + * + * @param dutyUsers 值班用户关联列表 + * @return 结果 + */ + public int insertDutyUserBatch(List 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); +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDutyConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDutyConfigService.java new file mode 100644 index 00000000..cfd20a55 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDutyConfigService.java @@ -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 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); +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DutyConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DutyConfigServiceImpl.java new file mode 100644 index 00000000..5607f4af --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DutyConfigServiceImpl.java @@ -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 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 selectDutyConfigList(DutyConfig dutyConfig) + { + List list = dutyConfigMapper.selectDutyConfigList(dutyConfig); + // 为每个配置设置用户名称和是否激活状态 + Date now = new Date(); + for (DutyConfig config : list) { + // 设置值班用户名称 + List 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 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 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 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); + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/DutyAssignCounterMapper.xml b/ruoyi-system/src/main/resources/mapper/system/DutyAssignCounterMapper.xml new file mode 100644 index 00000000..bb6b9453 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/DutyAssignCounterMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + 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 + + + + + + + + + + insert into duty_assign_counter + + config_id, + last_assigned_user_id, + assign_count, + update_time + + + #{configId}, + #{lastAssignedUserId}, + #{assignCount}, + sysdate() + + + + + update duty_assign_counter + + config_id = #{configId}, + last_assigned_user_id = #{lastAssignedUserId}, + assign_count = #{assignCount}, + update_time = sysdate() + + where id = #{id} + + + + update duty_assign_counter + set last_assigned_user_id = #{lastAssignedUserId}, + assign_count = #{assignCount}, + update_time = sysdate() + where config_id = #{configId} + + + + delete from duty_assign_counter where id = #{id} + + + + delete from duty_assign_counter where id in + + #{id} + + + + + delete from duty_assign_counter where config_id = #{configId} + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/DutyConfigMapper.xml b/ruoyi-system/src/main/resources/mapper/system/DutyConfigMapper.xml new file mode 100644 index 00000000..31a007b9 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/DutyConfigMapper.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + select id, config_name, start_time, end_time, status, remark, create_by, create_time, update_by, update_time, del_flag from duty_config + + + + + + + + + + + + insert into duty_config + + config_name, + start_time, + end_time, + status, + remark, + create_by, + del_flag, + create_time + + + #{configName}, + #{startTime}, + #{endTime}, + #{status}, + #{remark}, + #{createBy}, + #{delFlag}, + sysdate() + + + + + update duty_config + + config_name = #{configName}, + start_time = #{startTime}, + end_time = #{endTime}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + del_flag = #{delFlag}, + update_time = sysdate() + + where id = #{id} + + + + update duty_config set del_flag = '1' where id = #{id} + + + + update duty_config set del_flag = '1' where id in + + #{id} + + + + \ No newline at end of file diff --git a/ruoyi-system/src/main/resources/mapper/system/DutyUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/DutyUserMapper.xml new file mode 100644 index 00000000..36d78733 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/DutyUserMapper.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + insert into duty_user + + config_id, + user_id, + sort_order, + create_time, + + + #{configId}, + #{userId}, + #{sortOrder}, + #{createTime}, + + + + + insert into duty_user(config_id, user_id, sort_order, create_time) values + + (#{item.configId}, #{item.userId}, #{item.sortOrder}, #{item.createTime}) + + + + + update duty_user + + config_id = #{configId}, + user_id = #{userId}, + sort_order = #{sortOrder}, + + where id = #{id} + + + + delete from duty_user where id = #{id} + + + + delete from duty_user where id in + + #{id} + + + + + delete from duty_user where config_id = #{configId} + + + \ No newline at end of file