From 1c042b9f0a79f1b17945940409b5a50c5318b96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E9=B9=8F=E9=A3=9E?= Date: Sat, 29 Mar 2025 10:55:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(ticket):=20=E6=B7=BB=E5=8A=A0=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E6=8A=A5=E8=A1=A8=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E5=B7=A5=E5=8D=95=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AdminTicketReportVO 类用于工单报表导出- 添加 TicketCloseEvent 事件处理工单关闭逻辑 - 更新 TicketController,增加工单报表导出功能 - 修改 TicketEventListener 和 TicketEventPublisher,支持工单关闭事件 - 更新 TicketInfoVO,增加用户是否为处理人和 CQM 的字段 - 优化 TicketSolution 和 TicketSolutionAudit 的 equals 和 hashCode 方法 - 改进 TicketSolutionServiceImpl 中的措施删除和更新逻辑 - 在 TiketController 中添加工单关闭后的消息推送和事件发布 --- .../admin/controller/TicketController.java | 21 ++++- .../cfs/controller/TiketController.java | 15 +++ .../cfs/event/TicketCloseEvent.java | 72 ++++++++++++++ .../cfs/listener/TicketEventListener.java | 11 ++- .../cfs/publisher/TicketEventPublisher.java | 6 ++ .../common/pojo/vo/AdminTicketReportVO.java | 94 +++++++++++++++++++ .../common/pojo/vo/TicketInfoVO.java | 10 ++ .../repository/entity/TicketSolution.java | 20 ++++ .../entity/TicketSolutionAudit.java | 3 + .../impl/TicketSolutionAuditServiceImpl.java | 5 + .../impl/TicketSolutionServiceImpl.java | 12 ++- 11 files changed, 258 insertions(+), 11 deletions(-) create mode 100644 nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/event/TicketCloseEvent.java create mode 100644 nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/AdminTicketReportVO.java diff --git a/nflg-mobilebroken-admin/src/main/java/com/nflg/mobilebroken/admin/controller/TicketController.java b/nflg-mobilebroken-admin/src/main/java/com/nflg/mobilebroken/admin/controller/TicketController.java index 0689597a..867d0c40 100644 --- a/nflg-mobilebroken-admin/src/main/java/com/nflg/mobilebroken/admin/controller/TicketController.java +++ b/nflg-mobilebroken-admin/src/main/java/com/nflg/mobilebroken/admin/controller/TicketController.java @@ -1,6 +1,7 @@ package com.nflg.mobilebroken.admin.controller; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import com.itextpdf.text.pdf.BaseFont; import com.nflg.mobilebroken.admin.annotation.ApiMark; @@ -566,10 +567,12 @@ public class TicketController extends ControllerBase { warrantyStateDesc = warrantyState.getName(); } String handle = ticket.getHandle(); + List handleIds= Arrays.stream(handle.split(",")).map(Integer::parseInt).collect(Collectors.toList()); if (StrUtil.isNotBlank(handle)) { - List adminUsers = adminUserService.listByIds(Arrays.stream(handle.split(",")).map(Integer::parseInt).collect(Collectors.toList())); + List adminUsers = adminUserService.listByIds(handleIds); handle = adminUsers.stream().map(AdminUser::getUserName).collect(Collectors.joining(",")); } + List cqms=adminUserService.getCQMIds(); TicketInfoVO vo = new TicketInfoVO() .setId(ticket.getId()) .setNo(ticket.getNo()) @@ -602,6 +605,8 @@ public class TicketController extends ControllerBase { .setHandle(handle) .setSolution(ticket.getReason()) .setAccidentLevel(ticket.getAccidentLevel()) + .setUserIsHandle(handleIds.contains(AdminUserUtil.getUserId())) + .setUserIsCQM(cqms.contains(AdminUserUtil.getUserId())) .setEvaluate(getTicketEvaluate(ticket.getId())); return ApiResult.success(vo); } @@ -773,7 +778,7 @@ public class TicketController extends ControllerBase { * 驳回工单解决方案 * @param request 请求信息 **/ - @GetMapping("rejectSolution") + @PostMapping("rejectSolution") @ApiMark(moduleName = "工单管理", apiName = "驳回工单解决方案") public ApiResult rejectSolution(@Valid @RequestBody SolutionRejectRequest request) { Ticket ticket = ticketSolutionAuditService.reject(request); @@ -825,4 +830,16 @@ public class TicketController extends ControllerBase { } return ApiResult.success(); } + + /** + * 导出工单报表 + * @param request 请求参数 + */ + @PostMapping("exportTicketReport") + @ApiMark(moduleName = "工单管理", apiName = "导出工单报表") + public void exportTicketReport(HttpServletResponse response,@Valid @RequestBody AdminTicketSearchRequest request) throws IOException { + request.setPageSize(Integer.MAX_VALUE); + List datas = ticketService.exportSearch(request); + EecExcelUtil.export("工单报表", "sheet1", Convert.toList(AdminTicketReportVO.class, datas), response); + } } \ No newline at end of file diff --git a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/controller/TiketController.java b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/controller/TiketController.java index ccedfa84..6e4d4290 100644 --- a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/controller/TiketController.java +++ b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/controller/TiketController.java @@ -400,6 +400,21 @@ public class TiketController extends ControllerBase { //推送消息 ssePushService.sendTicketMessageToAdmin(ticket.getId(),message); ssePushService.sendTicketMessageToApp(ticket.getId(),message); + if (ticketService.close(ticket)){ + ticketEventPublisher.publishTicketCloseEvent(ticket); + message = new ChatMessageDTO() + .setId(cn.hutool.core.util.IdUtil.getSnowflakeNextIdStr()) + .setFrom("system") + .setTicketState(ticket.getState()) + .setSenderId(0) + .setSenderName("服务助手") + .setContent("工单已关闭
感谢你的使用,如有问题,请重新提交新的工单。") + .setCreateTime(Instant.now()); + ticketChatService.addMessage(request.getTicketId(), message); + //推送消息 + ssePushService.sendTicketMessageToAdmin(ticket.getId(),message); + ssePushService.sendTicketMessageToApp(ticket.getId(),message); + } return ApiResult.success(); } } diff --git a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/event/TicketCloseEvent.java b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/event/TicketCloseEvent.java new file mode 100644 index 00000000..1fb02377 --- /dev/null +++ b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/event/TicketCloseEvent.java @@ -0,0 +1,72 @@ +package com.nflg.mobilebroken.cfs.event; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.util.StrUtil; +import com.nflg.mobilebroken.common.constant.MessageSubType; +import com.nflg.mobilebroken.common.constant.MessageType; +import com.nflg.mobilebroken.repository.entity.AdminMessage; +import com.nflg.mobilebroken.repository.entity.AdminUser; +import com.nflg.mobilebroken.repository.entity.Ticket; +import com.nflg.mobilebroken.repository.service.*; +import com.nflg.mobilebroken.starter.service.EmailService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class TicketCloseEvent extends ApplicationEvent implements ApplicationContextAware { + + private static final DateTimeFormatter FORMATTER= DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN); + private final Ticket ticket; + private IDictionaryItemTranslateService dictionaryItemTranslateService; + private IAppUserService appUserService; + private EmailService emailService; + private ITBaseDeviceTypeService deviceTypeService; + private IAdminUserService adminUserService; + private IAdminMessageService adminMessageService; + + public TicketCloseEvent(Object source,Ticket ticket) { + super(source); + this.ticket = ticket; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.dictionaryItemTranslateService = applicationContext.getBean(IDictionaryItemTranslateService.class); + this.appUserService = applicationContext.getBean(IAppUserService.class); + this.emailService = applicationContext.getBean(EmailService.class); + this.adminUserService = applicationContext.getBean(IAdminUserService.class); + this.adminMessageService = applicationContext.getBean(IAdminMessageService.class); + this.deviceTypeService = applicationContext.getBean(ITBaseDeviceTypeService.class); + } + + public void send(){ + sendUserMessage(); + } + + private void sendUserMessage(){ + //我的待办 + List userIds= Arrays.stream(ticket.getHandle().split(",")).filter(StrUtil::isNotBlank).map(Integer::parseInt).collect(Collectors.toList()); + List adminUsers=adminUserService.listByIds(userIds); + if (CollectionUtil.isNotEmpty(adminUsers)){ + adminUsers.forEach(c -> adminMessageService.add( + new AdminMessage() + .setNo(ticket.getNo()) + .setTitle(ticket.getTitle()) + .setUserId(c.getId()) + .setSourceId(ticket.getId()) + .setSource(0) + .setType(MessageType.WorkOrderAssignment.getState()) + .setSubType(MessageSubType.TicketClosed.getState()) + .setIsRead(false) + .setCreateTime(LocalDateTime.now())) + ); + } + } +} diff --git a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/listener/TicketEventListener.java b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/listener/TicketEventListener.java index 98946eeb..38526f61 100644 --- a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/listener/TicketEventListener.java +++ b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/listener/TicketEventListener.java @@ -1,9 +1,6 @@ package com.nflg.mobilebroken.cfs.listener; -import com.nflg.mobilebroken.cfs.event.TicketCreateEvent; -import com.nflg.mobilebroken.cfs.event.TicketEvaluateEvent; -import com.nflg.mobilebroken.cfs.event.TicketReplyEvent; -import com.nflg.mobilebroken.cfs.event.TicketRevokeEvent; +import com.nflg.mobilebroken.cfs.event.*; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -34,4 +31,10 @@ public class TicketEventListener { public void handleTicketEvaluateEvent(TicketEvaluateEvent event) { event.send(); } + + @Async + @EventListener + public void handleTicketCloseEvent(TicketCloseEvent event) { + event.send(); + } } diff --git a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/publisher/TicketEventPublisher.java b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/publisher/TicketEventPublisher.java index b941a1de..8beedc7a 100644 --- a/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/publisher/TicketEventPublisher.java +++ b/nflg-mobilebroken-cfs-app/src/main/java/com/nflg/mobilebroken/cfs/publisher/TicketEventPublisher.java @@ -46,4 +46,10 @@ public class TicketEventPublisher { event.setApplicationContext(applicationContext); eventPublisher.publishEvent(event); } + + public void publishTicketCloseEvent(Ticket ticket) { + TicketCloseEvent event = new TicketCloseEvent(this, ticket); + event.setApplicationContext(applicationContext); + eventPublisher.publishEvent(event); + } } diff --git a/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/AdminTicketReportVO.java b/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/AdminTicketReportVO.java new file mode 100644 index 00000000..cac38f4b --- /dev/null +++ b/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/AdminTicketReportVO.java @@ -0,0 +1,94 @@ +package com.nflg.mobilebroken.common.pojo.vo; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.nflg.mobilebroken.common.constant.TicketState; +import com.nflg.mobilebroken.common.constant.TicketUrgency; +import lombok.Data; +import org.ttzero.excel.annotation.ExcelColumn; +import org.ttzero.excel.annotation.IgnoreExport; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +@Data +public class AdminTicketReportVO { + + //工单编号 + @ExcelColumn("工单编号") + private String no; + + //解决状态 + @ExcelColumn("解决状态") + private String stateDesc; + + //紧急程度 + @IgnoreExport + @JsonIgnore + private Byte urgency; + + //紧急程度 + @ExcelColumn("紧急程度") + private String urgencyDesc; + //设备编号 + @ExcelColumn("设备编号") + private String deviceNo; + //设备类型 + @ExcelColumn("设备类型") + private String deviceType; + //使用时长 + @ExcelColumn("使用时长(小时)") + private Integer useTime; + //代理商 + @ExcelColumn("代理商") + private String agentName; + //客户 + @ExcelColumn("客户") + private String customerName; + //提交时间 + @ExcelColumn("提交时间") + private LocalDateTime createTime; + //根本原因分析 + @IgnoreExport + private String reason; + //根本原因分析 + @ExcelColumn("根本原因分析") + private String reason1; + //工单状态 + @JsonIgnore + @IgnoreExport + private Byte state; + //处理完成时间 + @JsonIgnore + @IgnoreExport + private LocalDateTime completeTime; + //处理时长 + @ExcelColumn("处理时长") + private Long processingTime; + + public String getUrgencyDesc() { + if (Objects.isNull(urgency)) { + return ""; + } + return TicketUrgency.findByValue(urgency).getDescription(); + } + + public String getReason() { + return StrUtil.subWithLength(reason, 0, 10); + } + + public String getReason1() { + return reason; + } + + public Long getProcessingTime() { + if (TicketState.Processing.getState().compareTo(state)>=0){ + return ChronoUnit.DAYS.between(createTime.toLocalDate(),LocalDateTime.now().toLocalDate())+1; + } + if (TicketState.Closed.getState().compareTo(state)>=0) { + return ChronoUnit.DAYS.between(completeTime, LocalDateTime.now()) + 1; + } + return null; + } +} diff --git a/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/TicketInfoVO.java b/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/TicketInfoVO.java index 6f27c106..f8418d1c 100644 --- a/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/TicketInfoVO.java +++ b/nflg-mobilebroken-common/src/main/java/com/nflg/mobilebroken/common/pojo/vo/TicketInfoVO.java @@ -112,4 +112,14 @@ public class TicketInfoVO { * 事故等级,0:一般;1:较严重;2:严重 */ private Byte accidentLevel; + + /** + * 当前用户是否处理人 + */ + private Boolean userIsHandle; + + /** + * 当前用户是否为CQM + */ + private Boolean userIsCQM; } diff --git a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolution.java b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolution.java index 833539d9..cd07cd16 100644 --- a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolution.java +++ b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolution.java @@ -9,6 +9,7 @@ import lombok.experimental.Accessors; import java.io.Serializable; import java.time.LocalDateTime; +import java.util.Objects; /** *

@@ -78,4 +79,23 @@ public class TicketSolution implements Serializable { * 创建时间 */ private LocalDateTime createTime; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TicketSolution that = (TicketSolution) o; + return Objects.equals(id, that.id) && + Objects.equals(ticketId, that.ticketId) && + Objects.equals(dictionaryItemId, that.dictionaryItemId) && + Objects.equals(description, that.description) && + Objects.equals(superintendent, that.superintendent) && + Objects.equals(scheduleDate, that.scheduleDate) && + Objects.equals(confirmedDate, that.confirmedDate); + } + + @Override + public int hashCode() { + return Objects.hash(id, ticketId, dictionaryItemId, description, superintendent, scheduleDate, confirmedDate); + } } diff --git a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolutionAudit.java b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolutionAudit.java index edbbe284..fa18379f 100644 --- a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolutionAudit.java +++ b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/entity/TicketSolutionAudit.java @@ -1,5 +1,7 @@ package com.nflg.mobilebroken.repository.entity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Getter; import lombok.Setter; @@ -24,6 +26,7 @@ public class TicketSolutionAudit implements Serializable { private static final long serialVersionUID = 1L; + @TableId(value = "id", type = IdType.AUTO) private Integer id; /** diff --git a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/service/impl/TicketSolutionAuditServiceImpl.java b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/service/impl/TicketSolutionAuditServiceImpl.java index f3cc0d7b..78fc3478 100644 --- a/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/service/impl/TicketSolutionAuditServiceImpl.java +++ b/nflg-mobilebroken-repository/src/main/java/com/nflg/mobilebroken/repository/service/impl/TicketSolutionAuditServiceImpl.java @@ -84,6 +84,11 @@ public class TicketSolutionAuditServiceImpl extends ServiceImpl!Objects.equals(s.getCreateUserId(), userId))) .throwMessage("不能删除他人创建的措施"); solutions1.removeIf(s->Objects.equals(s.getCreateUserId(), userId)); - List ts=forUpdate.stream().filter(s->!Objects.equals(s.getCreateUserId(), userId)).collect(Collectors.toList()); + List ts=forUpdate.stream().filter(s->!Objects.equals(s.getCreateUserId(), userId)).sorted(Comparator.comparing(TicketSolution::getId)).collect(Collectors.toList()); VUtils.trueThrowBusinessError(!ts.equals(solutions1)).throwMessage("不能修改他人创建的措施"); } ticketService.lambdaUpdate() @@ -161,10 +161,12 @@ public class TicketSolutionServiceImpl extends ServiceImpl() - .eq(TicketSolution::getTicketId,request.getTicketId()) - .notIn(TicketSolution::getId, idForReserve) - ); + if(CollectionUtil.isNotEmpty(idForReserve)) { + baseMapper.delete(new LambdaQueryWrapper() + .eq(TicketSolution::getTicketId, request.getTicketId()) + .notIn(TicketSolution::getId, idForReserve) + ); + } if (CollectionUtil.isNotEmpty(forAdd)){ saveBatch(forAdd); }