feat(ticket): 添加工单报表导出功能并优化工单处理逻辑
- 新增 AdminTicketReportVO 类用于工单报表导出- 添加 TicketCloseEvent 事件处理工单关闭逻辑 - 更新 TicketController,增加工单报表导出功能 - 修改 TicketEventListener 和 TicketEventPublisher,支持工单关闭事件 - 更新 TicketInfoVO,增加用户是否为处理人和 CQM 的字段 - 优化 TicketSolution 和 TicketSolutionAudit 的 equals 和 hashCode 方法 - 改进 TicketSolutionServiceImpl 中的措施删除和更新逻辑 - 在 TiketController 中添加工单关闭后的消息推送和事件发布
This commit is contained in:
parent
c7de80b9cf
commit
1c042b9f0a
|
|
@ -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<Integer> handleIds= Arrays.stream(handle.split(",")).map(Integer::parseInt).collect(Collectors.toList());
|
||||
if (StrUtil.isNotBlank(handle)) {
|
||||
List<AdminUser> adminUsers = adminUserService.listByIds(Arrays.stream(handle.split(",")).map(Integer::parseInt).collect(Collectors.toList()));
|
||||
List<AdminUser> adminUsers = adminUserService.listByIds(handleIds);
|
||||
handle = adminUsers.stream().map(AdminUser::getUserName).collect(Collectors.joining(","));
|
||||
}
|
||||
List<Integer> 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<Void> 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<AdminTicketVO> datas = ticketService.exportSearch(request);
|
||||
EecExcelUtil.export("工单报表", "sheet1", Convert.toList(AdminTicketReportVO.class, datas), response);
|
||||
}
|
||||
}
|
||||
|
|
@ -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("<b>工单已关闭</b><br/>感谢你的使用,如有问题,请重新提交新的工单。")
|
||||
.setCreateTime(Instant.now());
|
||||
ticketChatService.addMessage(request.getTicketId(), message);
|
||||
//推送消息
|
||||
ssePushService.sendTicketMessageToAdmin(ticket.getId(),message);
|
||||
ssePushService.sendTicketMessageToApp(ticket.getId(),message);
|
||||
}
|
||||
return ApiResult.success();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Integer> userIds= Arrays.stream(ticket.getHandle().split(",")).filter(StrUtil::isNotBlank).map(Integer::parseInt).collect(Collectors.toList());
|
||||
List<AdminUser> 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()))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -112,4 +112,14 @@ public class TicketInfoVO {
|
|||
* 事故等级,0:一般;1:较严重;2:严重
|
||||
*/
|
||||
private Byte accidentLevel;
|
||||
|
||||
/**
|
||||
* 当前用户是否处理人
|
||||
*/
|
||||
private Boolean userIsHandle;
|
||||
|
||||
/**
|
||||
* 当前用户是否为CQM
|
||||
*/
|
||||
private Boolean userIsCQM;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import lombok.experimental.Accessors;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -84,6 +84,11 @@ public class TicketSolutionAuditServiceImpl extends ServiceImpl<TicketSolutionAu
|
|||
.setCreateTime(LocalDateTime.now());
|
||||
if (Objects.isNull(detp.getId())){
|
||||
forAdd.add(audit);
|
||||
VUtils.trueThrowBusinessError(lambdaQuery()
|
||||
.eq(TicketSolutionAudit::getTicketId,request.getTicketId())
|
||||
.eq(TicketSolutionAudit::getDeptName,detp.getDeptName())
|
||||
.exists())
|
||||
.throwMessage(detp.getDeptName()+"部门已设置了审核人员");
|
||||
}else {
|
||||
audit.setId(detp.getId());
|
||||
TicketSolutionAudit entity=getById(detp.getId());
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ public class TicketSolutionServiceImpl extends ServiceImpl<TicketSolutionMapper,
|
|||
VUtils.trueThrowBusinessError(solutions.stream().anyMatch(s->!Objects.equals(s.getCreateUserId(), userId)))
|
||||
.throwMessage("不能删除他人创建的措施");
|
||||
solutions1.removeIf(s->Objects.equals(s.getCreateUserId(), userId));
|
||||
List<TicketSolution> ts=forUpdate.stream().filter(s->!Objects.equals(s.getCreateUserId(), userId)).collect(Collectors.toList());
|
||||
List<TicketSolution> 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<TicketSolutionMapper,
|
|||
.set(Ticket::getAccidentLevel, request.getAccidentLevel())
|
||||
.eq(Ticket::getId, request.getTicketId())
|
||||
.update();
|
||||
baseMapper.delete(new LambdaQueryWrapper<TicketSolution>()
|
||||
.eq(TicketSolution::getTicketId,request.getTicketId())
|
||||
.notIn(TicketSolution::getId, idForReserve)
|
||||
);
|
||||
if(CollectionUtil.isNotEmpty(idForReserve)) {
|
||||
baseMapper.delete(new LambdaQueryWrapper<TicketSolution>()
|
||||
.eq(TicketSolution::getTicketId, request.getTicketId())
|
||||
.notIn(TicketSolution::getId, idForReserve)
|
||||
);
|
||||
}
|
||||
if (CollectionUtil.isNotEmpty(forAdd)){
|
||||
saveBatch(forAdd);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue