[Java]RuoYi帝可得-3工单管理
工单管理准备工作业务场景: 管理员在后台创建工单后工作人员可在运营管理App中查看并根据情况选择执行或取消分配给自己的任务。工单管理主要涉及到二个功能模块业务流程如下:工单是一种专业名词是指用于记录、处理、跟踪一项工作的完成情况。管理人员登录后台系统选择创建工单在工单类型里选择合适的工单类型在设备编号里输入正确的设备编号。工作人员在运营管理App可以看到分配给自己的工单根据实际情况选择接收工单并完成或者拒绝/取消工单。立可得工单分为两大类 运营工单运营人员来维护售货机商品即补货工单。运维工单运维人员来维护售货机设备即投放工单、撤机工单、维修工单。工单有四种状态1 待处理 2 进行中 3 已取消 4 已完成对于工单和其他管理数据下面是示意图关系字段task_id、 product_type_id、inner_code、user_id、assignor_id、region_id数据字典task_status1待办、2进行、3取消、4完成数据字典create_type0自动、1手动PS运营的工单包含补货信息运维工单没有所以运营工单需要单独创建补货工单其他说明创建所有工单都会在工单表和工单明细表插入记录吗创建运维类工单只会在工单表插入数据。创建运营类工单补货工单会在工单表和工单明细表插入数据。task_code和task_id有什么区别task_code是工单编号具有业务规则 格式为年月日当日序号。task_id 为工单表数据唯一标识。工单表的user_id和assignor_id分别是做什么的user_id是工单执行人的id运维或运营assignor_id是工单指派人的id创建工单的人生成代码使用若依代码生成器生成工管理前后端基础代码并导入到项目中创建目录菜单添加数据字典先创建工单状态的字典类型再创建工单状态的字典数据先创建工单创建类型的字典类型再创建工单创建类型的字典数据配置代码生成信息配置工单表运维、运营配置工单类型表工单原型配置工单详情表工单原型创建自动补货任务表工单原型下载代码并导入项目解压ruoyi.zip得到前后端代码和动态菜单sql注意工单管理只需要后端代码前端使用资料中的文件菜单手动创建配置工单前端代码从资料中(工单前端)复制工单api请求js文件到api/manage目录下从资料中(工单前端)复制货道的视图组件到views/manage目录下创建运营工单二级菜单创建运维工单二级菜单修复bug在工单表中有一个desc备注字段这个desc是一个数据库关键字所以我们在执行查询时报了语法错误所以要给所有的desc增加反引号表示一个普通的sql字段sql idselectTaskVo select task_id, task_code, task_status, create_type, inner_code, user_id, user_name, region_id, desc, product_type_id, assignor_id, addr, create_time, update_time from tb_task /sql使用资料中的文件覆盖工程文件逐个处理desc字段麻烦查看页面查询工单列表运营和运营工单共享一套后端接口通过特定的查询条件区分工单类型并在返回结果中包含工单类型的详细信息Data public class TaskVo extends Task { // 工单类型 private TaskType taskType; }/** * 查询运维工单列表 * * param task 运维工单 * return TaskVo集合 */ ListTaskVo selectTaskVoList(Task task);resultMap typetaskVo idTaskVoResult result propertytaskId columntask_id/ result propertytaskCode columntask_code/ result propertytaskStatus columntask_status/ result propertycreateType columncreate_type/ result propertyinnerCode columninner_code/ result propertyuserId columnuser_id/ result propertyuserName columnuser_name/ result propertyregionId columnregion_id/ result propertydesc columndesc/ result propertyproductTypeId columnproduct_type_id/ result propertyassignorId columnassignor_id/ result propertyaddr columnaddr/ result propertycreateTime columncreate_time/ result propertyupdateTime columnupdate_time/ association propertytaskType javaTypeTaskType columnproduct_type_id selectcom.dkd.manage.mapper.TaskTypeMapper.selectTaskTypeByTypeId/ /resultMap select idselectTaskVoList resultMapTaskVoResult include refidselectTaskVo/ where if testtaskCode ! null and taskCode ! and task_code #{taskCode}/if if testtaskStatus ! null and task_status #{taskStatus}/if if testcreateType ! null and create_type #{createType}/if if testinnerCode ! null and innerCode ! and inner_code #{innerCode}/if if testuserId ! null and user_id #{userId}/if if testuserName ! null and userName ! and user_name like concat(%, #{userName}, %)/if if testregionId ! null and region_id #{regionId}/if if testdesc ! null and desc ! and desc #{desc}/if if testproductTypeId ! null and product_type_id #{productTypeId}/if if testassignorId ! null and assignor_id #{assignorId}/if if testaddr ! null and addr ! and addr #{addr}/if if testparams.isRepair ! null and params.isRepairtrue and product_type_id in (1,3,4) /if if testparams.isRepair ! null and params.isRepairfalse and product_type_id 2 /if order by create_time desc /where /select/** * 查询运维工单列表 * param task * return TaskVo集合 */ ListTaskVo selectTaskVoList(Task task);/** * 查询运维工单列表 * param task * return TaskVo集合 */ Override public ListTaskVo selectTaskVoList(Task task) { return taskMapper.selectTaskVoList(task); }/** * 查询工单列表 */ PreAuthorize(ss.hasPermi(manage:task:list)) GetMapping(/list) public TableDataInfo list(Task task) { startPage(); ListTaskVo voList taskService.selectTaskVoList(task); return getDataTable(voList); }获取人员列表根据售货机编号获取负责当前区域下的运营人员列表/** * 根据设备编号查询设备信息 * * param innerCode * return VendingMachine */ Select(select * from tb_vending_machine where inner_code#{innerCode}) VendingMachine selectVendingMachineByInnerCode(String innerCode);/** * 根据设备编号查询设备信息 * * param innerCode * return VendingMachine */ VendingMachine selectVendingMachineByInnerCode(String innerCode);/** * 根据设备编号查询设备信息 * * param innerCode * return VendingMachine */ Override public VendingMachine selectVendingMachineByInnerCode(String innerCode) { return vendingMachineMapper.selectVendingMachineByInnerCode(innerCode); }/** * 员工启用 */ public static final Long EMP_STATUS_NORMAL 1L; /** * 员工禁用 */ public static final Long EMP_STATUS_DISABLE 0L;Autowired private IVendingMachineService vendingMachineService; /** * 根据售货机获取运营人员列表 */ PreAuthorize(ss.hasPermi(manage:emp:list)) GetMapping(/businessList/{innerCode}) public AjaxResult businessList(PathVariable(innerCode) String innerCode) { // 1.查询售货机信息 VendingMachine vm vendingMachineService.selectVendingMachineByInnerCode(innerCode); if (vm null) { return error(); } // 2.根据区域id、角色编号、员工状态查询运营人员列表 Emp empParam new Emp(); empParam.setRegionId(vm.getRegionId());// 设备所属区域 empParam.setStatus(DkdContants.EMP_STATUS_NORMAL);// 员工启用 empParam.setRoleCode(DkdContants.ROLE_CODE_BUSINESS);// 角色编码运营员 return success(empService.selectEmpList(empParam)); }根据售货机编号获取负责当前区域下的运维人员列表/** * 根据售货机获取运维人员列表 */ PreAuthorize(ss.hasPermi(manage:emp:list)) GetMapping(/operationList/{innerCode}) public AjaxResult getOperationList(PathVariable(innerCode) String innerCode) { // 1.查询售货机信息 VendingMachine vm vendingMachineService.selectVendingMachineByInnerCode(innerCode); if (vm null) { return error(售货机不存在); } // 2.根据区域id、角色编号、状态查询运维人员列表 Emp empParam new Emp(); empParam.setRegionId(vm.getRegionId());// 设备所属区域 empParam.setStatus(DkdContants.EMP_STATUS_NORMAL);// 员工启用 empParam.setRoleCode(DkdContants.ROLE_CODE_OPERATOR);// 角色编码维修员 return success(empService.selectEmpList(empParam)); }新增工单本系统中有两类工单需要创建分别是运维工单运维工单主要是对售货机的操作又可以细分为投放工单、撤机工单、维修工单运营工单运营工单主要是对货物的操作只有一种就是补货工单运维和运营新增工单共享一套后端接口思路新增工单时序图新增工单业务流程图1.查询售货机是否存在2.校验售货机状态与工单类型是否相符3.检查设备是否有未完成的同类型工单4.查询并校验员工是否存在5.校验员工区域是否匹配6.TaskDto-Task并补充属性保存工单7.判断是否为补货工单8.TaskDetailsDto-TaskDetails并补充属性批量保存实现步骤DTOpackage com.dkd.manage.domain.dto; import lombok.Data; Data public class TaskDetailsDto { private String channelCode;// 货道编号 private Long expectCapacity;// 期望补货数量 private Long skuId;// 商品Id private String skuName;// 商品名称 private String skuImage;// 商品图片 }package com.dkd.manage.domain.dto; import lombok.Data; import java.util.List; Data public class TaskDto { private Long createType;// 创建类型 private String innerCode;// 关联设备编号 private Long userId;// 任务执行人Id private Long assignorId;// 用户创建人id private Long productTypeId;// 工单类型 private String desc;// 描述信息 private ListTaskDetailsDto details;// 工单详情(只有补货工单才涉及) }TaskDetailsMapper/** * 批量新增工单详情 * param taskDetailsList * return 结果 */ int batchInsertTaskDetails(ListTaskDetails taskDetailsList);insert idbatchInsertTaskDetails INSERT INTO tb_task_details(task_id, channel_code, expect_capacity, sku_id, sku_name, sku_image) VALUES foreach itemitem collectionlist separator, (#{item.taskId}, #{item.channelCode}, #{item.expectCapacity}, #{item.skuId}, #{item.skuName}, #{item.skuImage}) /foreach /insertITaskDetailsService/** * 批量新增工单详情 * param taskDetailsList * return 结果 */ int batchInsertTaskDetails(ListTaskDetails taskDetailsList);/** * 批量新增工单详情 * param taskDetailsList * return 结果 */ Override public int batchInsertTaskDetails(ListTaskDetails taskDetailsList) { return taskDetailsMapper.batchInsertTaskDetails(taskDetailsList); }ITaskService/** * 新增运营、运维工单 * * param taskDto * return 结果 */ int insertTaskDto(TaskDto taskDto);Autowired private IVendingMachineService vendingMachineService; Autowired private IEmpService empService; Autowired private ITaskDetailsService taskDetailsService; Autowired private RedisTemplate redisTemplate; /** * 新增运营、运维工单 * * param taskDto * return 结果 */ Transactional Override public int insertTaskDto(TaskDto taskDto) { //1. 查询售货机是否存在 VendingMachine vm vendingMachineService.selectVendingMachineByInnerCode(taskDto.getInnerCode()); if (vm null) { throw new ServiceException(设备不存在); } //2. 校验售货机状态与工单类型是否相符 checkCreateTask(vm.getVmStatus(), taskDto.getProductTypeId()); //3. 检查设备是否有未完成的同类型工单 hasTask(taskDto.getInnerCode(), taskDto.getProductTypeId()); //4. 查询并校验员工是否存在 Emp emp empService.selectEmpById(taskDto.getUserId()); if (emp null) { throw new ServiceException(员工不存在); } //5. 校验员工区域是否匹配 if (!emp.getRegionId().equals(vm.getRegionId())) { throw new ServiceException(员工区域与设备区域不一致无法处理此工单); } //6. 将dto转为po并补充属性保存工单 Task task BeanUtil.copyProperties(taskDto, Task.class);// 属性复制 task.setTaskStatus(DkdContants.TASK_STATUS_CREATE);// 创建工单 task.setUserName(emp.getUserName());// 执行人名称 task.setRegionId(vm.getRegionId());// 所属区域id task.setAddr(vm.getAddr());// 地址 task.setCreateTime(DateUtils.getNowDate());// 创建时间 task.setTaskCode(generateTaskCode());// 工单编号 int taskResult taskMapper.insertTask(task); //7. 判断是否为补货工单 if (taskDto.getProductTypeId().equals(DkdContants.TASK_TYPE_SUPPLY)) { // 8.保存工单详情 ListTaskDetailsDto details taskDto.getDetails(); if (CollUtil.isEmpty(details)) { throw new ServiceException(补货工单详情不能为空); } // 将dto转为po补充属性 ListTaskDetails taskDetailsList details.stream().map(dto - { TaskDetails taskDetails BeanUtil.copyProperties(dto, TaskDetails.class); taskDetails.setTaskId(task.getTaskId()); return taskDetails; }).collect(Collectors.toList()); // 批量新增 taskDetailsService.batchInsertTaskDetails(taskDetailsList); } return taskResult; } /** * 生成并获取当天任务代码的唯一标识。 * 该方法首先尝试从Redis中获取当天的任务代码计数如果不存在则初始化为1并返回日期0001格式的字符串。 * 如果存在则对计数加1并返回更新后的任务代码。 * * return 返回当天任务代码的唯一标识格式为日期XXXX其中XXXX是四位数字的计数。 */ public String generateTaskCode() { // 获取当前日期并格式化为yyyyMMdd String dateStr DateUtils.getDate().replaceAll(-, ); // 根据日期生成redis的键 String key dkd.task.code. dateStr; // 判断key是否存在 if (!redisTemplate.hasKey(key)) { // 如果key不存在设置初始值为1并指定过期时间为1天 redisTemplate.opsForValue().set(key, 1, Duration.ofDays(1)); // 返回工单编号日期0001 return dateStr 0001; } // 如果key存在计数器10002确保字符串长度为4位 return dateStrStrUtil.padPre(redisTemplate.opsForValue().increment(key).toString(),4,0); } /** * 检查设备是否已有未完成的同类型工单。 * 本方法用于在创建新工单前验证指定设备是否已经有处于进行中的同类型工单。 * 如果存在未完成的同类型工单则抛出服务异常阻止新工单的创建。 * * param innerCode 设备的内部编码用于唯一标识设备。 * param productTypeId 任务的类型决定任务的性质投放、维修、补货、撤机。 */ private void hasTask(String innerCode, Long productTypeId) { // 创建Task对象并设置设备编号和工单类型ID以及任务状态为进行中 Task taskParam new Task(); taskParam.setInnerCode(innerCode); taskParam.setProductTypeId(productTypeId); taskParam.setTaskStatus(DkdContants.TASK_STATUS_PROGRESS); // 查询数据库中符合指定条件的工单列表 ListTask taskList taskMapper.selectTaskList(taskParam); // 如果存在未完成的同类型工单则抛出服务异常 if (CollUtil.isNotEmpty(taskList)) { throw new ServiceException(该设备有未完成的同类型工单不能重复创建); } } /** * 根据设备的状态和任务类型验证是否可以创建相应的任务。 * 如果条件不满足抛出服务异常。 * * param vmStatus 设备的状态表示设备是否在运行。 * param productTypeId 任务的类型决定任务的性质投放、维修、补货、撤机。 */ private void checkCreateTask(Long vmStatus, Long productTypeId) { // 如果是投放工单且设备状态为运行中则抛出异常因为设备已在运营中无法进行投放 if (productTypeId DkdContants.TASK_TYPE_DEPLOY vmStatus DkdContants.VM_STATUS_RUNNING) { throw new ServiceException(该设备状态为运行中无法进行投放); } // 如果是维修工单且设备状态不是运行中则抛出异常因为设备不在运营中无法进行维修 if (productTypeId DkdContants.TASK_TYPE_REPAIR vmStatus ! DkdContants.VM_STATUS_RUNNING) { throw new ServiceException(该设备状态不是运行中无法进行维修); } // 如果是补货工单且设备状态不是运行中则抛出异常因为设备不在运营状态无法进行补货 if (productTypeId DkdContants.TASK_TYPE_SUPPLY vmStatus ! DkdContants.VM_STATUS_RUNNING) { throw new ServiceException(该设备状态不是运行中无法进行补货); } // 如果是撤机工单且设备状态不是运行中则抛出异常因为设备不在运营状态无法进行撤机 if (productTypeId DkdContants.TASK_TYPE_REVOKE vmStatus ! DkdContants.VM_STATUS_RUNNING) { throw new ServiceException(该设备状态不是运行中无法进行撤机); } }方法说明redisTemplate.opsForValue().increment(key)原子性地将计数器1StrUtil.padPre(..., 4, 0)左补零至4位长度TaskController/** * 新增工单 */ PreAuthorize(ss.hasPermi(manage:task:add)) Log(title 工单, businessType BusinessType.INSERT) PostMapping public AjaxResult add(RequestBody TaskDto taskDto) { // 设置指派人登录用户id taskDto.setAssignorId(getUserId()); return toAjax(taskService.insertTaskDto(taskDto)); }取消工单对于未完成的工单管理员可以进行取消操作/** * 取消工单 */ PreAuthorize(ss.hasPermi(manage:task:edit)) Log(title 工单, businessType BusinessType.UPDATE) PutMapping(/cancel) public AjaxResult cancelTask(RequestBody Task task) { return toAjax(taskService.cancelTask(task)); }/** * 取消工单 * param task * return 结果 */ int cancelTask(Task task);/** * 取消工单 * param task * return 结果 */ Override public int cancelTask(Task task) { //1. 判断工单状态是否可以取消 // 先根据工单id查询数据库 Task taskDb taskMapper.selectTaskByTaskId(task.getTaskId()); // 判断工单状态是否为已取消如果是则抛出异常 if (taskDb.getTaskStatus().equals(DkdContants.TASK_STATUS_CANCEL)) { throw new ServiceException(该工单已取消了不能再次取消); } // 判断工单状态是否为已完成如果是则抛出异常 if (taskDb.getTaskStatus().equals(DkdContants.TASK_STATUS_FINISH)) { throw new ServiceException(该工单已完成了不能取消); } //2. 设置更新字段 task.setTaskStatus(DkdContants.TASK_STATUS_CANCEL);// 工单状态取消 task.setUpdateTime(DateUtils.getNowDate());// 更新时间 //3. 更新工单 return taskMapper.updateTask(task);// 注意别传错了这里是前端task参数 }查询补货详情运营工单页面可以查看补货详情/** * 查看工单补货详情 */ PreAuthorize(ss.hasPermi(manage:taskDetails:list)) GetMapping(value /byTaskId/{taskId}) public AjaxResult byTaskId(PathVariable(taskId) Long taskId) { TaskDetails taskDetailsParam new TaskDetails(); taskDetailsParam.setTaskId(taskId); return success(taskDetailsService.selectTaskDetailsList(taskDetailsParam)); }Knife4j如果不习惯使用swagger可以使用前端UI的增强解决方案knife4j对比swagger相比有以下优势友好界面离线文档接口排序安全控制在线调试文档清晰注解增强容易上手。1、ruoyi-common\pom.xml模块添加整合依赖!-- knife4j -- dependency groupIdcom.github.xiaoymin/groupId artifactIdknife4j-spring-boot-starter/artifactId version3.0.3/version /dependency2、views/tool/swagger/index.vue修改跳转访问地址已完成const url ref(import.meta.env.VITE_APP_BASE_API /doc.html)3、登录系统访问菜单系统工具/系统接口出现如下图表示成功。首次加载较慢TaskDetailsController添加swagger注解Api: 用于类级别描述API的标签和描述。ApiOperation: 用于方法级别描述一个HTTP操作。ApiParam: 用于参数级别描述请求参数。/** * 工单详情Controller * * author itheima * date 2024-07-22 */ RestController RequestMapping(/manage/taskDetails) Api(tags 工单详情) public class TaskDetailsController extends BaseController { /** * 查看工单补货详情 */ ApiOperation(根据工单id查看工单补货详情) PreAuthorize(ss.hasPermi(manage:taskDetails:query)) GetMapping(value /byTaskId/{taskId}) public RListTaskDetails byTaskId( ApiParam(value 工单ID, required true) PathVariable Long taskId) { TaskDetails taskDetailsParam new TaskDetails(); taskDetailsParam.setTaskId(taskId); return R.ok(taskDetailsService.selectTaskDetailsList(taskDetailsParam)); } }注意若依框架的AjaxResult由于继承自HashMap导致与Swagger和knife4j不兼容的问题选择替换返回值类型为R以解决Swagger解析问题减少整体改动量。5、TaskDetails实体类添加swagger注解ApiModelProperty注解来描述每个字段的意义/** * 工单详情对象 tb_task_details * * author itheima * date 2024-07-22 */ ApiModel(description 任务详情对象) public class TaskDetails extends BaseEntity { private static final long serialVersionUID 1L; /** $column.columnComment */ ApiModelProperty(value 任务详情ID, hidden true) private Long detailsId; /** 工单Id */ ApiModelProperty(value 工单Id) Excel(name 工单Id) private Long taskId; /** 货道编号 */ ApiModelProperty(value 货道编号) Excel(name 货道编号) private String channelCode; /** 补货期望容量 */ ApiModelProperty(value 补货期望容量) Excel(name 补货期望容量) private Long expectCapacity; /** 商品Id */ ApiModelProperty(value 商品Id) Excel(name 商品Id) private Long skuId; /** $column.columnComment */ ApiModelProperty(value 商品名称) Excel(name ${comment}, readConverterExp $column.readConverterExp()) private String skuName; /** $column.columnComment */ ApiModelProperty(value 商品图片) Excel(name ${comment}, readConverterExp $column.readConverterExp()) private String skuImage; }6、接口测试设置文档信息运营APPAndroid模拟器业务场景: 管理员在后台创建工单后工作人员可在运营管理App中查看并根据情况选择接受或取消分配给自己的任务本项目的App客户端部分已经由前端团队进行开发完成并且以apk的方式提供出来供我们测试使用如果要运行apk需要先安装安卓的模拟器。可以选择国内的安卓模拟器产品比如网易mumu、雷电、夜神等。课程中使用网易mumu模拟器官网地址http://mumu.172.com/。资料中提供了mumu安装包大家安装到非中文路径即可。资料中提供了运营管理App端安装包需要让模拟器中的App能够连接我们自己本地代码需要修改下URL地址:http://10.0.2.2是windows本机地址9007是后端服务端口Java后端本项目运营管理App的java后端已开发完成在资料中已提供源码导入idea中即可本项目连接的也是dkd数据库如果密码不是root可以进行修改功能测试投放工单帝可得管理端创建新设备帝可得管理端创建投放工单运营管理App端登录负责此工单员工即可查看待办工单可以选择拒绝、接受如果点击接受帝可得管理端工单状态改为进行在进行工单界面可以点击查看详情选择取消、完成如果点击完成工单帝可得管理端工单状态改为完成帝可得管理端设备状态改为运营表示设备投放成功补货工单帝可得管理端货道关联商品大家练习时可以全部关联帝可得管理端创建补货工单运营管理App端登录负责此工单员工即可查看待办工单可以选择拒绝、接受如果点击接受帝可得管理端工单状态改为进行在进行工单界面可以点击查看详情选择取消、完成如果点击完成工单帝可得管理端工单状态改为完成数据库货道表的库存已同步更新源码介绍运营管理App的java后端技术栈SpringBootMybatisPlus阿里云短信员工管理: 发送短信、App登录、查询员工信息工单管理: 查询工单、接受工单、拒绝/取消工单、完成工单工单详情: 根据工单id查询补货详情列表设备屏幕业务场景消费者可以在设备屏幕端查看商品列表-选择支付方式--显示支付二维码--用户扫码完成支付--商品出货设备屏幕本项目的设备屏幕客户端部分已经由前端团队进行开发完成在资料中已提供源码双击打开index.html即可Java后端本项目设备屏幕端的java后端已开发完成在资料中已提供源码导入idea中即可功能测试在设备屏幕端加上innerCode设备编号即可显示当前设备货道信息帝可得管理端设备策略分配设置折扣信息再次访问设备屏幕端价格就是折扣的了支付出货流程我们能够从屏幕上看到支付二维码其实是经历了“长途跋涉屏幕端实际上是一个H5页面向后端发起支付请求订单服务首先会创建订单然后调用第三方支付来获得用于生成支付二维码的链接。 然后订单微服务将二维码链接返回给屏幕端屏幕端生成二维码图片展示。用户看到二维码后拿出手机扫码支付此时第三方支付平台确认用户支付成功后会回调订单服务。订单服务收到回调信息后修改订单状态并通知设备发货。设备通过mqtt协议实现网络通信有兴趣可以看一下emqx框架优雅支付框架: https://gitee.com/myelegent/elegent-pay源码介绍设备屏幕端的java后端技术栈SpringBootMybatisPlus
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2410370.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!