feat(aftersales): 售后新增回访、问题归档与问题库
- 工单完结后进入待回访,登记「满意且未复发」回访后方可归档;不满意/复发自动回退处理中
- closeTicket 语义改为归档,归档时自动沉淀一条问题库草稿(幂等)
- 新增 after_sale_visit / after_sale_problem 表;after_sale_ticket 加 visit_status/problem_id 等 6 列
- 新增回访与问题库的 Controller/Service/DAL/VO/枚举;问题库支持发布/停用/参考检索
- StorageRecordTypeEnum 登记回访/问题库附件类型;SaveReqVO 兼容 handlerUserId
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.problem; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemRespVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemSaveReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.problem.AfterSaleProblemDO; |
| | | import cn.iocoder.yudao.module.aftersales.service.problem.AfterSaleProblemService; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管çåå° - å®åé®é¢åº") |
| | | @RestController |
| | | @RequestMapping("/aftersales/problem") |
| | | @Validated |
| | | public class AfterSaleProblemController { |
| | | |
| | | @Resource |
| | | private AfterSaleProblemService problemService; |
| | | |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建å®åé®é¢") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:create')") |
| | | public CommonResult<Long> createProblem(@Valid @RequestBody AfterSaleProblemSaveReqVO createReqVO) { |
| | | return success(problemService.createProblem(createReqVO, getLoginUserId())); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°å®åé®é¢") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:update')") |
| | | public CommonResult<Boolean> updateProblem(@Valid @RequestBody AfterSaleProblemSaveReqVO updateReqVO) { |
| | | problemService.updateProblem(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤å®åé®é¢") |
| | | @Parameter(name = "id", description = "é®é¢ç¼å·", required = true, example = "1") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:delete')") |
| | | public CommonResult<Boolean> deleteProblem(@RequestParam("id") Long id) { |
| | | problemService.deleteProblem(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾å®åé®é¢") |
| | | @Parameter(name = "id", description = "é®é¢ç¼å·", required = true, example = "1") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:query')") |
| | | public CommonResult<AfterSaleProblemRespVO> getProblem(@RequestParam("id") Long id) { |
| | | AfterSaleProblemDO problemDO = problemService.getProblem(id); |
| | | AfterSaleProblemRespVO respVO = buildProblemDetailList(List.of(problemDO)).get(0); |
| | | respVO.setAttachmentList(storageAttachmentApi.listAttachments("aftersales_after_sale_problem", id)); |
| | | return success(respVO); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾å®åé®é¢å页") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:query')") |
| | | public CommonResult<PageResult<AfterSaleProblemRespVO>> getProblemPage(@Valid AfterSaleProblemPageReqVO pageReqVO) { |
| | | PageResult<AfterSaleProblemDO> pageResult = problemService.getProblemPage(pageReqVO); |
| | | return success(new PageResult<>(buildProblemDetailList(pageResult.getList()), pageResult.getTotal())); |
| | | } |
| | | |
| | | @PutMapping("/publish") |
| | | @Operation(summary = "åå¸å®åé®é¢") |
| | | @Parameter(name = "id", description = "é®é¢ç¼å·", required = true, example = "1") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:publish')") |
| | | public CommonResult<Boolean> publishProblem(@RequestParam("id") Long id) { |
| | | problemService.publishProblem(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/disable") |
| | | @Operation(summary = "åç¨å®åé®é¢") |
| | | @Parameter(name = "id", description = "é®é¢ç¼å·", required = true, example = "1") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:disable')") |
| | | public CommonResult<Boolean> disableProblem(@RequestParam("id") Long id) { |
| | | problemService.disableProblem(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/reference") |
| | | @Operation(summary = "æ°å·¥ååçåèæ£ç´¢(å·²åå¸é®é¢)") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:problem:query')") |
| | | public CommonResult<List<AfterSaleProblemRespVO>> getReferenceList(@Valid AfterSaleProblemPageReqVO pageReqVO) { |
| | | return success(buildProblemDetailList(problemService.getReferenceList(pageReqVO))); |
| | | } |
| | | |
| | | // ========== å
鍿¹æ³ï¼æ¹éæ¼è£
å
³èåç§° ========== |
| | | |
| | | private List<AfterSaleProblemRespVO> buildProblemDetailList(List<AfterSaleProblemDO> problemList) { |
| | | if (CollUtil.isEmpty(problemList)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 1. æ¹éå 载客æ·åç§° |
| | | Set<Long> customerIds = convertSet(problemList, AfterSaleProblemDO::getCustomerId); |
| | | Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(customerIds); |
| | | // 2. æ¹éå è½½æ²æ·äººåç§° |
| | | Set<Long> userIds = convertSet(problemList, AfterSaleProblemDO::getOwnerUserId); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| | | // 3. æ¼è£
åç§°åæ®µ |
| | | return BeanUtils.toBean(problemList, AfterSaleProblemRespVO.class, vo -> { |
| | | CrmCustomerRespDTO customer = customerMap.get(vo.getCustomerId()); |
| | | if (customer != null) { |
| | | vo.setCustomerName(customer.getName()); |
| | | } |
| | | AdminUserRespDTO ownerUser = userMap.get(vo.getOwnerUserId()); |
| | | if (ownerUser != null) { |
| | | vo.setOwnerUserName(ownerUser.getNickname()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | |
| | | /** |
| | | * å®åé®é¢åº - å页æ¥è¯¢ Request VO |
| | | */ |
| | | @Schema(description = "å®åé®é¢åº - å页æ¥è¯¢ Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class AfterSaleProblemPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "é®é¢åå·", example = "AP20260813000001") |
| | | private String no; |
| | | |
| | | @Schema(description = "å
³é®å(åå·/æ é¢/æè¿°/è§£å³æ¹æ¡)", example = "ä¼ æå¨") |
| | | private String keyword; |
| | | |
| | | @Schema(description = "æ¥æºå·¥åç¼å·", example = "1") |
| | | private Long sourceTicketId; |
| | | |
| | | @Schema(description = "ç©æç¼å·", example = "1") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "ç©æç¼ç (模ç³)", example = "MAT-001") |
| | | private String itemCode; |
| | | |
| | | @Schema(description = "é®é¢åç±»", example = "åè½æ
é") |
| | | private String issueCategory; |
| | | |
| | | @Schema(description = "é®é¢ç±»å", example = "1") |
| | | private Integer issueType; |
| | | |
| | | @Schema(description = "æ ¹å åç±»", example = "3") |
| | | private Integer rootCauseCategory; |
| | | |
| | | @Schema(description = "ç¶æ: 0-è稿 1-å·²åå¸ 2-å·²åç¨", example = "1") |
| | | private Integer status; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo; |
| | | |
| | | import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®åé®é¢åº - ååº VO |
| | | */ |
| | | @Schema(description = "å®åé®é¢åº - ååº VO") |
| | | @Data |
| | | public class AfterSaleProblemRespVO { |
| | | |
| | | @Schema(description = "é®é¢ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "é®é¢åå·", example = "AP20260813000001") |
| | | private String no; |
| | | |
| | | @Schema(description = "é®é¢æ é¢", example = "æåæºä¼ æå¨æ è¾åº") |
| | | private String title; |
| | | |
| | | @Schema(description = "æ¥æºå·¥åç¼å·", example = "1") |
| | | private Long sourceTicketId; |
| | | |
| | | @Schema(description = "æ¥æºå·¥ååå·(åä½)", example = "AS20260813000001") |
| | | private String sourceTicketNo; |
| | | |
| | | @Schema(description = "客æ·ç¼å·", example = "1") |
| | | private Long customerId; |
| | | |
| | | @Schema(description = "客æ·åç§°") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "ç©æç¼å·", example = "1") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "ç©æç¼ç ", example = "MAT-001") |
| | | private String itemCode; |
| | | |
| | | @Schema(description = "ç©æåç§°", example = "çµåå
å¨ä»¶") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "çäº§æ¹æ¬¡ç¼å·", example = "1") |
| | | private Long batchId; |
| | | |
| | | @Schema(description = "çäº§æ¹æ¬¡å·", example = "BATCH-2024-001") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "é®é¢ç±»å", example = "1") |
| | | private Integer issueType; |
| | | |
| | | @Schema(description = "é®é¢åç±»", example = "åè½æ
é") |
| | | private String issueCategory; |
| | | |
| | | @Schema(description = "严éç¨åº¦", example = "2") |
| | | private Integer severityLevel; |
| | | |
| | | @Schema(description = "é®é¢æè¿°") |
| | | private String description; |
| | | |
| | | @Schema(description = "æ ¹å åç±»", example = "3") |
| | | private Integer rootCauseCategory; |
| | | |
| | | @Schema(description = "æ ¹å åæ") |
| | | private String rootCause; |
| | | |
| | | @Schema(description = "è§£å³æ¹æ¡") |
| | | private String solution; |
| | | |
| | | @Schema(description = "ç¶æ: 0-è稿 1-å·²åå¸ 2-å·²åç¨", example = "1") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æ²æ·äººç¨æ·ç¼å·", example = "1") |
| | | private Long ownerUserId; |
| | | |
| | | @Schema(description = "æ²æ·äººåç§°") |
| | | private String ownerUserName; |
| | | |
| | | @Schema(description = "å叿¶é´") |
| | | private LocalDateTime publishTime; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建è
") |
| | | private String creator; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "æ´æ°è
") |
| | | private String updater; |
| | | |
| | | @Schema(description = "æ´æ°æ¶é´") |
| | | private LocalDateTime updateTime; |
| | | |
| | | @Schema(description = "éä»¶å表") |
| | | private List<StorageBlobRespDTO> attachmentList; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®åé®é¢åº - ä¿å Request VO |
| | | */ |
| | | @Schema(description = "å®åé®é¢åº - ä¿å Request VO") |
| | | @Data |
| | | public class AfterSaleProblemSaveReqVO { |
| | | |
| | | @Schema(description = "é®é¢ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "é®é¢æ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "æåæºä¼ æå¨æ è¾åº") |
| | | @NotEmpty(message = "é®é¢æ é¢ä¸è½ä¸ºç©º") |
| | | private String title; |
| | | |
| | | @Schema(description = "æ¥æºå·¥åç¼å·(å¯éï¼ä¾¿äºè¿½æº¯)", example = "1") |
| | | private Long sourceTicketId; |
| | | |
| | | @Schema(description = "客æ·ç¼å·", example = "1") |
| | | private Long customerId; |
| | | |
| | | @Schema(description = "ç©æç¼å·", example = "1") |
| | | private Long itemId; |
| | | |
| | | @Schema(description = "ç©æç¼ç ", example = "MAT-001") |
| | | private String itemCode; |
| | | |
| | | @Schema(description = "ç©æåç§°", example = "çµåå
å¨ä»¶") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "çäº§æ¹æ¬¡ç¼å·", example = "1") |
| | | private Long batchId; |
| | | |
| | | @Schema(description = "çäº§æ¹æ¬¡å·", example = "BATCH-2024-001") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "é®é¢ç±»å: 0-æªå¤å® 1-ä¸è¬é®é¢ 2-ç»´ä¿®é®é¢ 3-éè´§é®é¢", example = "1") |
| | | private Integer issueType; |
| | | |
| | | @Schema(description = "é®é¢åç±»", example = "åè½æ
é") |
| | | private String issueCategory; |
| | | |
| | | @Schema(description = "严éç¨åº¦: 0-轻微 1-ä¸è¬ 2-严é 3-è´å½", example = "2") |
| | | private Integer severityLevel; |
| | | |
| | | @Schema(description = "é®é¢æè¿°") |
| | | private String description; |
| | | |
| | | @Schema(description = "æ ¹å åç±»: 1-设计 2-ç©æ 3-å·¥èº 4-è£
é
5-è¿è¾ 6-å
è£
7-ä½¿ç¨ 8-软件 9-å
¶ä»", example = "3") |
| | | private Integer rootCauseCategory; |
| | | |
| | | @Schema(description = "æ ¹å åæ") |
| | | private String rootCause; |
| | | |
| | | @Schema(description = "è§£å³æ¹æ¡") |
| | | private String solution; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "éä»¶ blobId å表") |
| | | private List<Long> blobIds; |
| | | |
| | | } |
| | |
| | | @Schema(description = "é®é¢ç±»å", example = "1") |
| | | private Integer issueType; |
| | | |
| | | @Schema(description = "åè®¿ç¶æ: 0-å¾
å访 1-å·²å访éè¿", example = "0") |
| | | private Integer visitStatus; |
| | | |
| | | @Schema(description = "åºæ¯ç±»å", example = "1") |
| | | private Integer sceneType; |
| | | |
| | |
| | | @Schema(description = "å
³éæ¶é´") |
| | | private LocalDateTime closedTime; |
| | | |
| | | @Schema(description = "åè®¿ç¶æ: 0-å¾
å访 1-å·²å访éè¿", example = "0") |
| | | private Integer visitStatus; |
| | | |
| | | @Schema(description = "æè¿ä¸æ¬¡å访æ¶é´") |
| | | private LocalDateTime lastVisitTime; |
| | | |
| | | @Schema(description = "æè¿ä¸æ¬¡å访æ¯å¦æ»¡æ", example = "true") |
| | | private Boolean lastVisitSatisfied; |
| | | |
| | | @Schema(description = "æè¿ä¸æ¬¡å访é®é¢æ¯å¦å¤å", example = "false") |
| | | private Boolean lastVisitRecurred; |
| | | |
| | | @Schema(description = "æ²æ·é®é¢ç¼å·", example = "1") |
| | | private Long problemId; |
| | | |
| | | @Schema(description = "æ²æ·é®é¢åå·", example = "AP20260813000001") |
| | | private String problemNo; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | |
| | | @Schema(description = "ååç¼å·", example = "1") |
| | | private Long contractId; |
| | | |
| | | @Schema(description = "å¤çäººç¨æ·ç¼å·(å¯éï¼ä»
èç¨¿æææï¼å¤ç人åé
建议走 assign æ¥å£)", example = "2") |
| | | private Long handlerUserId; |
| | | |
| | | @Schema(description = "é®é¢ç±»å: 1-ä¸è¬é®é¢ 2-ç»´ä¿®é®é¢ 3-éè´§é®é¢", example = "1") |
| | | private Integer issueType; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.visit; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitCreateReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitRespVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.visit.AfterSaleVisitDO; |
| | | import cn.iocoder.yudao.module.aftersales.service.visit.AfterSaleVisitService; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管çåå° - å®åå访记å½") |
| | | @RestController |
| | | @RequestMapping("/aftersales/visit") |
| | | @Validated |
| | | public class AfterSaleVisitController { |
| | | |
| | | @Resource |
| | | private AfterSaleVisitService visitService; |
| | | |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "ç»è®°å®åå访") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:visit:create')") |
| | | public CommonResult<Long> createVisit(@Valid @RequestBody AfterSaleVisitCreateReqVO createReqVO) { |
| | | return success(visitService.createVisit(createReqVO, getLoginUserId())); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾å®åå访记å½") |
| | | @Parameter(name = "id", description = "å访记å½ç¼å·", required = true, example = "1") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:visit:query')") |
| | | public CommonResult<AfterSaleVisitRespVO> getVisit(@RequestParam("id") Long id) { |
| | | AfterSaleVisitDO visitDO = visitService.getVisit(id); |
| | | AfterSaleVisitRespVO respVO = buildVisitDetailList(List.of(visitDO)).get(0); |
| | | respVO.setAttachmentList(storageAttachmentApi.listAttachments("aftersales_after_sale_visit", id)); |
| | | return success(respVO); |
| | | } |
| | | |
| | | @GetMapping("/list") |
| | | @Operation(summary = "è·å¾æå·¥åçå访记å½å表") |
| | | @Parameter(name = "ticketId", description = "å·¥åç¼å·", required = true, example = "1") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:visit:query')") |
| | | public CommonResult<List<AfterSaleVisitRespVO>> getVisitListByTicketId(@RequestParam("ticketId") Long ticketId) { |
| | | List<AfterSaleVisitDO> visitList = visitService.getVisitListByTicketId(ticketId); |
| | | return success(buildVisitDetailList(visitList)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾å®åå访记å½å页") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:visit:query')") |
| | | public CommonResult<PageResult<AfterSaleVisitRespVO>> getVisitPage(@Valid AfterSaleVisitPageReqVO pageReqVO) { |
| | | PageResult<AfterSaleVisitDO> pageResult = visitService.getVisitPage(pageReqVO); |
| | | return success(new PageResult<>(buildVisitDetailList(pageResult.getList()), pageResult.getTotal())); |
| | | } |
| | | |
| | | // ========== å
鍿¹æ³ï¼æ¹éæ¼è£
å
³èåç§° ========== |
| | | |
| | | private List<AfterSaleVisitRespVO> buildVisitDetailList(List<AfterSaleVisitDO> visitList) { |
| | | if (CollUtil.isEmpty(visitList)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // æ¹éå è½½å访人åç§° |
| | | Set<Long> userIds = convertSet(visitList, AfterSaleVisitDO::getVisitorUserId); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| | | return BeanUtils.toBean(visitList, AfterSaleVisitRespVO.class, vo -> { |
| | | AdminUserRespDTO visitorUser = userMap.get(vo.getVisitorUserId()); |
| | | if (visitorUser != null) { |
| | | vo.setVisitorUserName(visitorUser.getNickname()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ - ç»è®° Request VO |
| | | */ |
| | | @Schema(description = "å®ååè®¿è®°å½ - ç»è®° Request VO") |
| | | @Data |
| | | public class AfterSaleVisitCreateReqVO { |
| | | |
| | | @Schema(description = "å·¥åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å·¥åä¸è½ä¸ºç©º") |
| | | private Long ticketId; |
| | | |
| | | @Schema(description = "å访æ¹å¼: 0-çµè¯ 1-ä¸é¨ 2-è¿ç¨ 3-å
¶ä»", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | @NotNull(message = "å访æ¹å¼ä¸è½ä¸ºç©º") |
| | | private Integer visitType; |
| | | |
| | | @Schema(description = "å访å
容", example = "客æ·åé¦å·²æ£å¸¸ä½¿ç¨") |
| | | private String content; |
| | | |
| | | @Schema(description = "æ¯å¦æ»¡æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| | | @NotNull(message = "æ¯å¦æ»¡æä¸è½ä¸ºç©º") |
| | | private Boolean satisfied; |
| | | |
| | | @Schema(description = "é®é¢æ¯å¦å¤å", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") |
| | | @NotNull(message = "é®é¢æ¯å¦å¤åä¸è½ä¸ºç©º") |
| | | private Boolean recurred; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "éä»¶ blobId å表") |
| | | private List<Long> blobIds; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.ToString; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ - å页æ¥è¯¢ Request VO |
| | | */ |
| | | @Schema(description = "å®ååè®¿è®°å½ - å页æ¥è¯¢ Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | public class AfterSaleVisitPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "å·¥åç¼å·", example = "1") |
| | | private Long ticketId; |
| | | |
| | | @Schema(description = "å访æ¹å¼", example = "0") |
| | | private Integer visitType; |
| | | |
| | | @Schema(description = "æ¯å¦æ»¡æ", example = "true") |
| | | private Boolean satisfied; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo; |
| | | |
| | | import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ - ååº VO |
| | | */ |
| | | @Schema(description = "å®ååè®¿è®°å½ - ååº VO") |
| | | @Data |
| | | public class AfterSaleVisitRespVO { |
| | | |
| | | @Schema(description = "å访记å½ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å·¥åç¼å·", example = "1") |
| | | private Long ticketId; |
| | | |
| | | @Schema(description = "å访æ¹å¼: 0-çµè¯ 1-ä¸é¨ 2-è¿ç¨ 3-å
¶ä»", example = "0") |
| | | private Integer visitType; |
| | | |
| | | @Schema(description = "åè®¿äººç¨æ·ç¼å·", example = "1") |
| | | private Long visitorUserId; |
| | | |
| | | @Schema(description = "å访人åç§°") |
| | | private String visitorUserName; |
| | | |
| | | @Schema(description = "å访å
容") |
| | | private String content; |
| | | |
| | | @Schema(description = "æ¯å¦æ»¡æ", example = "true") |
| | | private Boolean satisfied; |
| | | |
| | | @Schema(description = "é®é¢æ¯å¦å¤å", example = "false") |
| | | private Boolean recurred; |
| | | |
| | | @Schema(description = "å访æ¶é´") |
| | | private LocalDateTime visitTime; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建è
") |
| | | private String creator; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "éä»¶å表") |
| | | private List<StorageBlobRespDTO> attachmentList; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.dal.dataobject.problem; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleProblemStatusEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * å®åé®é¢åº DO |
| | | * <p> |
| | | * ç¬ç«çç¥è¯æ²æ·å®ä½ï¼å·¥å彿¡£æ¶æå·¥åå¿«ç
§èªå¨çæè稿ï¼äººå·¥è¡¥å
æ ¹å /æ¹æ¡ååå¸ï¼ |
| | | * æ°å·¥ååçæ¶å¯æç©æ/åç±»/å
³é®åå½ä¸å·²åå¸é®é¢ä½åèã |
| | | */ |
| | | @TableName("after_sale_problem") |
| | | @KeySequence("after_sale_problem_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class AfterSaleProblemDO extends BaseDO { |
| | | |
| | | /** |
| | | * é®é¢ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * é®é¢åå·ï¼æ ¼å¼ AP+yyyyMMdd+6ä½ |
| | | */ |
| | | private String no; |
| | | /** |
| | | * é®é¢æ é¢ |
| | | */ |
| | | private String title; |
| | | /** |
| | | * æ¥æºå·¥åç¼å·ï¼å
³è after_sale_ticket.id |
| | | */ |
| | | private Long sourceTicketId; |
| | | /** |
| | | * æ¥æºå·¥ååå·(åä½) |
| | | */ |
| | | private String sourceTicketNo; |
| | | /** |
| | | * 客æ·ç¼å·(æ¥æºå·¥å带å
¥) |
| | | */ |
| | | private Long customerId; |
| | | /** |
| | | * ç©æç¼å·(é»è®¤åæ¥æºå·¥å馿¡æç») |
| | | */ |
| | | private Long itemId; |
| | | /** |
| | | * ç©æç¼ç (åä½) |
| | | */ |
| | | private String itemCode; |
| | | /** |
| | | * ç©æåç§°(åä½) |
| | | */ |
| | | private String itemName; |
| | | /** |
| | | * çäº§æ¹æ¬¡ç¼å·(å¯é) |
| | | */ |
| | | private Long batchId; |
| | | /** |
| | | * çäº§æ¹æ¬¡å·(å¯é) |
| | | */ |
| | | private String batchCode; |
| | | /** |
| | | * é®é¢ç±»åï¼æä¸¾ {@link cn.iocoder.yudao.module.aftersales.enums.AfterSaleIssueTypeEnum} |
| | | */ |
| | | private Integer issueType; |
| | | /** |
| | | * é®é¢åç±» |
| | | */ |
| | | private String issueCategory; |
| | | /** |
| | | * 严éç¨åº¦: 0-轻微 1-ä¸è¬ 2-严é 3-è´å½ |
| | | */ |
| | | private Integer severityLevel; |
| | | /** |
| | | * é®é¢æè¿° |
| | | */ |
| | | private String description; |
| | | /** |
| | | * æ ¹å åç±»ï¼æä¸¾ {@link cn.iocoder.yudao.module.aftersales.enums.AfterSaleRootCauseCategoryEnum} |
| | | */ |
| | | private Integer rootCauseCategory; |
| | | /** |
| | | * æ ¹å åæ |
| | | */ |
| | | private String rootCause; |
| | | /** |
| | | * è§£å³æ¹æ¡ |
| | | */ |
| | | private String solution; |
| | | /** |
| | | * ç¶æï¼æä¸¾ {@link AfterSaleProblemStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | /** |
| | | * æ²æ·äººç¨æ·ç¼å· |
| | | */ |
| | | private Long ownerUserId; |
| | | /** |
| | | * å叿¶é´ |
| | | */ |
| | | private LocalDateTime publishTime; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| | |
| | | */ |
| | | private LocalDateTime closedTime; |
| | | /** |
| | | * åè®¿ç¶æ: 0-å¾
å访 1-å·²å访éè¿ |
| | | * |
| | | * å®ç»(COMPLETED)åè¿å
¥å¾
å访ï¼ç»è®°ã满æä¸æªå¤åãçå访å置为已å访éè¿ï¼æ¹å¯å½æ¡£ |
| | | */ |
| | | private Integer visitStatus; |
| | | /** |
| | | * æè¿ä¸æ¬¡å访æ¶é´ |
| | | */ |
| | | private LocalDateTime lastVisitTime; |
| | | /** |
| | | * æè¿ä¸æ¬¡å访æ¯å¦æ»¡æ |
| | | */ |
| | | private Boolean lastVisitSatisfied; |
| | | /** |
| | | * æè¿ä¸æ¬¡å访é®é¢æ¯å¦å¤å |
| | | */ |
| | | private Boolean lastVisitRecurred; |
| | | /** |
| | | * æ²æ·é®é¢ç¼å·ï¼å
³è after_sale_problem.id |
| | | */ |
| | | private Long problemId; |
| | | /** |
| | | * æ²æ·é®é¢åå·(åä½) |
| | | */ |
| | | private String problemNo; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.dal.dataobject.visit; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ DO |
| | | * <p> |
| | | * æå¨å®åå·¥åä¸çå访åè®°å½ï¼å¯å¤æ¡ï¼ç»è®°å访æ¶åæ¥æ¨è¿å·¥ååè®¿ç¶æã |
| | | */ |
| | | @TableName("after_sale_visit") |
| | | @KeySequence("after_sale_visit_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class AfterSaleVisitDO extends BaseDO { |
| | | |
| | | /** |
| | | * å访记å½ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * å·¥åç¼å·ï¼å
³è after_sale_ticket.id |
| | | */ |
| | | private Long ticketId; |
| | | /** |
| | | * å访æ¹å¼: 0-çµè¯ 1-ä¸é¨ 2-è¿ç¨ 3-å
¶ä» |
| | | */ |
| | | private Integer visitType; |
| | | /** |
| | | * åè®¿äººç¨æ·ç¼å· |
| | | */ |
| | | private Long visitorUserId; |
| | | /** |
| | | * å访å
容 |
| | | */ |
| | | private String content; |
| | | /** |
| | | * æ¯å¦æ»¡æ: 0-å¦ 1-æ¯ |
| | | */ |
| | | @TableField("is_satisfied") |
| | | private Boolean satisfied; |
| | | /** |
| | | * é®é¢æ¯å¦å¤å: 0-å¦ 1-æ¯ |
| | | */ |
| | | @TableField("is_recurred") |
| | | private Boolean recurred; |
| | | /** |
| | | * å访æ¶é´(ç»è®°å³å½æ¶) |
| | | */ |
| | | private LocalDateTime visitTime; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.dal.mysql.problem; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.problem.AfterSaleProblemDO; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleProblemStatusEnum; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®åé®é¢åº Mapper |
| | | */ |
| | | @Mapper |
| | | public interface AfterSaleProblemMapper extends BaseMapperX<AfterSaleProblemDO> { |
| | | |
| | | default AfterSaleProblemDO selectByNo(String no) { |
| | | return selectOne(AfterSaleProblemDO::getNo, no); |
| | | } |
| | | |
| | | default AfterSaleProblemDO selectBySourceTicketId(Long sourceTicketId) { |
| | | return selectOne(AfterSaleProblemDO::getSourceTicketId, sourceTicketId); |
| | | } |
| | | |
| | | default PageResult<AfterSaleProblemDO> selectPage(PageParam pageParam, String no, String keyword, |
| | | Long sourceTicketId, Long itemId, String itemCode, |
| | | String issueCategory, |
| | | Integer issueType, Integer rootCauseCategory, Integer status) { |
| | | LambdaQueryWrapperX<AfterSaleProblemDO> query = new LambdaQueryWrapperX<AfterSaleProblemDO>() |
| | | .eqIfPresent(AfterSaleProblemDO::getNo, no) |
| | | .eqIfPresent(AfterSaleProblemDO::getSourceTicketId, sourceTicketId) |
| | | .eqIfPresent(AfterSaleProblemDO::getItemId, itemId) |
| | | .likeIfPresent(AfterSaleProblemDO::getItemCode, itemCode) |
| | | .eqIfPresent(AfterSaleProblemDO::getIssueCategory, issueCategory) |
| | | .eqIfPresent(AfterSaleProblemDO::getIssueType, issueType) |
| | | .eqIfPresent(AfterSaleProblemDO::getRootCauseCategory, rootCauseCategory) |
| | | .eqIfPresent(AfterSaleProblemDO::getStatus, status); |
| | | // å
³é®åæ£ç´¢ï¼åå·/æ é¢/æè¿°/è§£å³æ¹æ¡ |
| | | if (StrUtil.isNotBlank(keyword)) { |
| | | query.and(w -> w.like(AfterSaleProblemDO::getNo, keyword) |
| | | .or().like(AfterSaleProblemDO::getTitle, keyword) |
| | | .or().like(AfterSaleProblemDO::getDescription, keyword) |
| | | .or().like(AfterSaleProblemDO::getSolution, keyword)); |
| | | } |
| | | query.orderByDesc(AfterSaleProblemDO::getUpdateTime); |
| | | return selectPage(pageParam, query); |
| | | } |
| | | |
| | | /** |
| | | * æ°å·¥ååçåèæ£ç´¢ï¼åªè¿åå·²åå¸ï¼æç©æ/åç±»/å
³é®åå½ä¸ï¼åæè¿æ´æ° 10 æ¡ |
| | | */ |
| | | default List<AfterSaleProblemDO> selectReferenceList(Long itemId, String issueCategory, Integer issueType, |
| | | Integer rootCauseCategory, String keyword) { |
| | | LambdaQueryWrapperX<AfterSaleProblemDO> query = new LambdaQueryWrapperX<AfterSaleProblemDO>() |
| | | .eq(AfterSaleProblemDO::getStatus, AfterSaleProblemStatusEnum.PUBLISHED.getStatus()); |
| | | query.and(w -> { |
| | | boolean hasCond = false; |
| | | if (itemId != null) { |
| | | w.eq(AfterSaleProblemDO::getItemId, itemId); |
| | | hasCond = true; |
| | | } |
| | | if (StrUtil.isNotBlank(issueCategory)) { |
| | | if (hasCond) { |
| | | w.or(); |
| | | } |
| | | w.eq(AfterSaleProblemDO::getIssueCategory, issueCategory); |
| | | hasCond = true; |
| | | } |
| | | if (issueType != null) { |
| | | if (hasCond) { |
| | | w.or(); |
| | | } |
| | | w.eq(AfterSaleProblemDO::getIssueType, issueType); |
| | | hasCond = true; |
| | | } |
| | | if (rootCauseCategory != null) { |
| | | if (hasCond) { |
| | | w.or(); |
| | | } |
| | | w.eq(AfterSaleProblemDO::getRootCauseCategory, rootCauseCategory); |
| | | hasCond = true; |
| | | } |
| | | if (StrUtil.isNotBlank(keyword)) { |
| | | if (hasCond) { |
| | | w.or(); |
| | | } |
| | | w.like(AfterSaleProblemDO::getTitle, keyword); |
| | | hasCond = true; |
| | | } |
| | | if (!hasCond) { |
| | | w.eq(AfterSaleProblemDO::getId, -1L); // æ æ¡ä»¶æ¶è¿å空ï¼é¿å
å
¨è¡¨æå |
| | | } |
| | | }); |
| | | query.orderByDesc(AfterSaleProblemDO::getUpdateTime).last("LIMIT 10"); |
| | | return selectList(query); |
| | | } |
| | | |
| | | } |
| | |
| | | default PageResult<AfterSaleTicketDO> selectPage(PageParam pageParam, Long userId, Integer sceneType, |
| | | String no, String name, |
| | | Long customerId, Long saleOrderId, |
| | | Integer status, Integer issueType) { |
| | | Integer status, Integer issueType, Integer visitStatus) { |
| | | MPJLambdaWrapperX<AfterSaleTicketDO> query = new MPJLambdaWrapperX<>(); |
| | | // æ°æ®æé |
| | | AfterSalePermissionUtils.appendPermissionCondition(query, AfterSaleBizTypeEnum.AFTER_SALE_TICKET.getType(), |
| | |
| | | .eqIfPresent(AfterSaleTicketDO::getSaleOrderId, saleOrderId) |
| | | .eqIfPresent(AfterSaleTicketDO::getStatus, status) |
| | | .eqIfPresent(AfterSaleTicketDO::getIssueType, issueType) |
| | | .eqIfPresent(AfterSaleTicketDO::getVisitStatus, visitStatus) |
| | | .orderByDesc(AfterSaleTicketDO::getId); |
| | | return selectJoinPage(pageParam, AfterSaleTicketDO.class, query); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.dal.mysql.visit; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.visit.AfterSaleVisitDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ Mapper |
| | | */ |
| | | @Mapper |
| | | public interface AfterSaleVisitMapper extends BaseMapperX<AfterSaleVisitDO> { |
| | | |
| | | default List<AfterSaleVisitDO> selectListByTicketId(Long ticketId) { |
| | | return selectList(new LambdaQueryWrapperX<AfterSaleVisitDO>() |
| | | .eq(AfterSaleVisitDO::getTicketId, ticketId) |
| | | .orderByDesc(AfterSaleVisitDO::getVisitTime)); |
| | | } |
| | | |
| | | default PageResult<AfterSaleVisitDO> selectPage(PageParam pageParam, Long ticketId, Integer visitType, |
| | | Boolean satisfied) { |
| | | return selectPage(pageParam, new LambdaQueryWrapperX<AfterSaleVisitDO>() |
| | | .eqIfPresent(AfterSaleVisitDO::getTicketId, ticketId) |
| | | .eqIfPresent(AfterSaleVisitDO::getVisitType, visitType) |
| | | .eqIfPresent(AfterSaleVisitDO::getSatisfied, satisfied) |
| | | .orderByDesc(AfterSaleVisitDO::getVisitTime)); |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | public static final String RETURN_NO_PREFIX = "RT"; |
| | | |
| | | /** |
| | | * å®åé®é¢åº {@link cn.iocoder.yudao.module.aftersales.dal.dataobject.problem.AfterSaleProblemDO} |
| | | */ |
| | | public static final String PROBLEM_NO_PREFIX = "AP"; |
| | | |
| | | @Resource |
| | | private StringRedisTemplate stringRedisTemplate; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.enums; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * å®åé®é¢åºç¶ææä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum AfterSaleProblemStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | DRAFT(0, "è稿"), |
| | | PUBLISHED(1, "å·²åå¸"), |
| | | DISABLED(2, "å·²åç¨"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleProblemStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * é®é¢ç¶æ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * é®é¢ç¶æåç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static String getNameByStatus(Integer status) { |
| | | return Arrays.stream(values()) |
| | | .filter(item -> item.getStatus().equals(status)) |
| | | .map(AfterSaleProblemStatusEnum::getName) |
| | | .findFirst().orElse(null); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.enums; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * å®åé®é¢æ ¹å åç±»æä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum AfterSaleRootCauseCategoryEnum implements ArrayValuable<Integer> { |
| | | |
| | | DESIGN(1, "设计缺é·"), |
| | | MATERIAL(2, "ç©æç¼ºé·"), |
| | | PROCESS(3, "å·¥èºç¼ºé·"), |
| | | ASSEMBLY(4, "è£
é
缺é·"), |
| | | TRANSPORT(5, "è¿è¾æå"), |
| | | PACKAGING(6, "å
è£
缺é·"), |
| | | USAGE(7, "使ç¨ä¸å½"), |
| | | SOFTWARE(8, "软件缺é·"), |
| | | OTHER(9, "å
¶ä»"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleRootCauseCategoryEnum::getType).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * æ ¹å åç±» |
| | | */ |
| | | private final Integer type; |
| | | /** |
| | | * æ ¹å åç±»åç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static String getNameByType(Integer type) { |
| | | return Arrays.stream(values()) |
| | | .filter(item -> item.getType().equals(type)) |
| | | .map(AfterSaleRootCauseCategoryEnum::getName) |
| | | .findFirst().orElse(null); |
| | | } |
| | | |
| | | } |
| | |
| | | PROCESSING(10, "å¤çä¸"), |
| | | WAITING_RETURN(30, "å¾
éè´§"), |
| | | WAITING_REFUND(40, "å¾
鿬¾/æ¢è´§"), |
| | | // 50 å·²å®ç»ï¼è¯ä¹=å¾
å访ï¼ç»è®°ã满æä¸æªå¤åãçå访éè¿åæ¹å¯å½æ¡£ |
| | | COMPLETED(50, "å·²å®ç»"), |
| | | // 60 å·²å
³éï¼è§ä¸ºå½æ¡£ç»æ(åªè¯»)ï¼å½æ¡£æ¶èªå¨æ²æ·ä¸æ¡é®é¢åºè稿 |
| | | CLOSED(60, "å·²å
³é"), |
| | | CANCELED(99, "已忶"); |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.enums; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * å®åå·¥ååè®¿ç¶ææä¸¾ |
| | | * <p> |
| | | * å·¥åå®ç»(COMPLETED)åè¿å
¥ãå¾
å访ãï¼ç»è®°ã满æä¸æªå¤åãçå访å置为ãå·²å访éè¿ãï¼æ¹å¯å½æ¡£ã |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum AfterSaleVisitStatusEnum implements ArrayValuable<Integer> { |
| | | |
| | | WAITING(0, "å¾
å访"), |
| | | PASSED(1, "å·²å访éè¿"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleVisitStatusEnum::getStatus).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * åè®¿ç¶æ |
| | | */ |
| | | private final Integer status; |
| | | /** |
| | | * åè®¿ç¶æåç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static String getNameByStatus(Integer status) { |
| | | return Arrays.stream(values()) |
| | | .filter(item -> item.getStatus().equals(status)) |
| | | .map(AfterSaleVisitStatusEnum::getName) |
| | | .findFirst().orElse(null); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.enums; |
| | | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * å®åå访æ¹å¼æä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum AfterSaleVisitTypeEnum implements ArrayValuable<Integer> { |
| | | |
| | | PHONE(0, "çµè¯å访"), |
| | | ON_SITE(1, "ä¸é¨å访"), |
| | | REMOTE(2, "è¿ç¨å访"), |
| | | OTHER(3, "å
¶ä»"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleVisitTypeEnum::getType).toArray(Integer[]::new); |
| | | |
| | | /** |
| | | * å访æ¹å¼ |
| | | */ |
| | | private final Integer type; |
| | | /** |
| | | * å访æ¹å¼åç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | |
| | | public static String getNameByType(Integer type) { |
| | | return Arrays.stream(values()) |
| | | .filter(item -> item.getType().equals(type)) |
| | | .map(AfterSaleVisitTypeEnum::getName) |
| | | .findFirst().orElse(null); |
| | | } |
| | | |
| | | } |
| | |
| | | ErrorCode TICKET_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_022_000_006, "æäº¤å·¥å审æ¹å¤±è´¥ï¼åå ï¼å·¥å䏿¯èç¨¿ç¶æ"); |
| | | ErrorCode TICKET_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_022_000_007, "å®åå·¥åå®¡æ¹æµç¨ä¸åå¨ææªæ¿æ´»ï¼è¯·æ£æ¥ BPM é
ç½®"); |
| | | ErrorCode TICKET_STATUS_ERROR = new ErrorCode(1_022_000_008, "å·¥åå½åç¶æä¸å
è®¸æ¤æä½"); |
| | | ErrorCode TICKET_ARCHIVE_FAIL_NOT_VISITED = new ErrorCode(1_022_000_009, "彿¡£å¤±è´¥ï¼åå ï¼å·¥åå°æªå访éè¿ï¼è¯·å
ç»è®°æ»¡æçå访记å½"); |
| | | |
| | | // ========== å®åå·¥åæç»è¡ 1-022-001-000 ========== |
| | | ErrorCode TICKET_ITEM_NOT_EXISTS = new ErrorCode(1_022_001_000, "å·¥åæç»è¡ä¸åå¨"); |
| | |
| | | ErrorCode REPAIR_NOT_EXISTS = new ErrorCode(1_022_003_000, "维修记å½ä¸åå¨"); |
| | | ErrorCode REPAIR_UPDATE_FAIL_STATUS = new ErrorCode(1_022_003_001, "æ´æ°ç»´ä¿®ç¶æå¤±è´¥ï¼åå ï¼å·²ç»æ¯è¯¥ç¶æ"); |
| | | |
| | | // ========== å®åå访 1-022-004-000 ========== |
| | | ErrorCode VISIT_NOT_EXISTS = new ErrorCode(1_022_004_000, "å访记å½ä¸åå¨"); |
| | | ErrorCode VISIT_CREATE_FAIL_STATUS = new ErrorCode(1_022_004_001, "ç»è®°å访失败ï¼åå ï¼å·¥å䏿¯å·²å®ç»(å¾
å访)ç¶æ"); |
| | | |
| | | // ========== å®åé®é¢åº 1-022-005-000 ========== |
| | | ErrorCode PROBLEM_NOT_EXISTS = new ErrorCode(1_022_005_000, "é®é¢è®°å½ä¸åå¨"); |
| | | ErrorCode PROBLEM_NO_EXISTS = new ErrorCode(1_022_005_001, "é®é¢åå·éå¤ï¼è¯·éè¯"); |
| | | ErrorCode PROBLEM_UPDATE_FAIL_STATUS = new ErrorCode(1_022_005_002, "é®é¢ä¿®æ¹å¤±è´¥ï¼åå ï¼ä»
èç¨¿ç¶æå¯ç¼è¾"); |
| | | ErrorCode PROBLEM_DELETE_FAIL_STATUS = new ErrorCode(1_022_005_003, "é®é¢å é¤å¤±è´¥ï¼åå ï¼ä»
èç¨¿ç¶æå¯å é¤"); |
| | | ErrorCode PROBLEM_PUBLISH_FAIL_STATUS = new ErrorCode(1_022_005_004, "é®é¢åå¸å¤±è´¥ï¼åå ï¼å½åç¶æä¸å¯åå¸"); |
| | | ErrorCode PROBLEM_DISABLE_FAIL_STATUS = new ErrorCode(1_022_005_005, "é®é¢åç¨å¤±è´¥ï¼åå ï¼å½åç¶æä¸å¯åç¨"); |
| | | |
| | | } |
| | |
| | | String AFTER_SALE_REPAIR_COMPLETE_SUB_TYPE = "å®æç»´ä¿®"; |
| | | String AFTER_SALE_REPAIR_COMPLETE_SUCCESS = "宿äºç»´ä¿®è®°å½ã{{#repairId}}ãï¼ç»´ä¿®ç»æï¼ã{{#repairResult}}ã"; |
| | | |
| | | // ======================= å®åå访 ======================= |
| | | |
| | | String AFTER_SALE_VISIT_TYPE = "å®åå访"; |
| | | String AFTER_SALE_VISIT_CREATE_SUB_TYPE = "ç»è®°å访"; |
| | | String AFTER_SALE_VISIT_CREATE_SUCCESS = "ç»è®°äºå®åå·¥åã{{#ticketNo}}ãçåè®¿ï¼æ»¡æã{{#satisfied}}ã/å¤åã{{#recurred}}ã"; |
| | | |
| | | // ======================= å®åé®é¢åº ======================= |
| | | |
| | | String AFTER_SALE_PROBLEM_TYPE = "å®åé®é¢åº"; |
| | | String AFTER_SALE_PROBLEM_CREATE_SUB_TYPE = "å建é®é¢"; |
| | | String AFTER_SALE_PROBLEM_CREATE_SUCCESS = "å建äºå®åé®é¢ã{{#problem.title}}ã"; |
| | | String AFTER_SALE_PROBLEM_UPDATE_SUB_TYPE = "æ´æ°é®é¢"; |
| | | String AFTER_SALE_PROBLEM_UPDATE_SUCCESS = "æ´æ°äºå®åé®é¢ã{{#problemTitle}}ã: {_DIFF{#updateReqVO}}"; |
| | | String AFTER_SALE_PROBLEM_DELETE_SUB_TYPE = "å é¤é®é¢"; |
| | | String AFTER_SALE_PROBLEM_DELETE_SUCCESS = "å é¤äºå®åé®é¢ã{{#problemTitle}}ã"; |
| | | String AFTER_SALE_PROBLEM_PUBLISH_SUB_TYPE = "åå¸é®é¢"; |
| | | String AFTER_SALE_PROBLEM_PUBLISH_SUCCESS = "åå¸äºå®åé®é¢ã{{#problemNo}}ã"; |
| | | String AFTER_SALE_PROBLEM_DISABLE_SUB_TYPE = "åç¨é®é¢"; |
| | | String AFTER_SALE_PROBLEM_DISABLE_SUCCESS = "åç¨äºå®åé®é¢ã{{#problemNo}}ã"; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.service.problem; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemSaveReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.problem.AfterSaleProblemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®åé®é¢åº Service æ¥å£ |
| | | */ |
| | | public interface AfterSaleProblemService { |
| | | |
| | | /** |
| | | * å建é®é¢(人工建档)ï¼åå§ç¶æè稿 |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @param userId å½åç¨æ·ç¼å·(æ²æ·äºº) |
| | | * @return é®é¢ç¼å· |
| | | */ |
| | | Long createProblem(@Valid AfterSaleProblemSaveReqVO createReqVO, Long userId); |
| | | |
| | | /** |
| | | * æ´æ°é®é¢ |
| | | * |
| | | * @param updateReqVO æ´æ°ä¿¡æ¯ |
| | | */ |
| | | void updateProblem(@Valid AfterSaleProblemSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å é¤é®é¢(ä»
è稿) |
| | | * |
| | | * @param id é®é¢ç¼å· |
| | | */ |
| | | void deleteProblem(Long id); |
| | | |
| | | /** |
| | | * è·å¾é®é¢ |
| | | * |
| | | * @param id é®é¢ç¼å· |
| | | * @return é®é¢ |
| | | */ |
| | | AfterSaleProblemDO getProblem(Long id); |
| | | |
| | | /** |
| | | * è·å¾é®é¢å表 |
| | | * |
| | | * @param ids ç¼å·å表 |
| | | * @return é®é¢å表 |
| | | */ |
| | | List<AfterSaleProblemDO> getProblemList(Collection<Long> ids); |
| | | |
| | | /** |
| | | * è·å¾é®é¢å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return é®é¢å页 |
| | | */ |
| | | PageResult<AfterSaleProblemDO> getProblemPage(AfterSaleProblemPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * åå¸é®é¢ |
| | | * |
| | | * @param id é®é¢ç¼å· |
| | | */ |
| | | void publishProblem(Long id); |
| | | |
| | | /** |
| | | * åç¨é®é¢ |
| | | * |
| | | * @param id é®é¢ç¼å· |
| | | */ |
| | | void disableProblem(Long id); |
| | | |
| | | /** |
| | | * æ°å·¥ååçåèæ£ç´¢ï¼å½ä¸å·²åå¸åå²é®é¢ |
| | | * |
| | | * @param pageReqVO åèæ£ç´¢æ¡ä»¶(å itemId/issueCategory/issueType/rootCauseCategory/keyword) |
| | | * @return å·²åå¸é®é¢å表(è³å¤10æ¡) |
| | | */ |
| | | List<AfterSaleProblemDO> getReferenceList(AfterSaleProblemPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * å·¥å彿¡£æ¶èªå¨æ²æ·é®é¢(è稿æ)ï¼åä¸å·¥åä»
æ²æ·ä¸æ¬¡(å¹ç) |
| | | * |
| | | * @param ticket å®åå·¥å |
| | | * @return æ²æ·åºçé®é¢ |
| | | */ |
| | | AfterSaleProblemDO createProblemFromTicket(AfterSaleTicketDO ticket); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.service.problem; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.problem.vo.AfterSaleProblemSaveReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.problem.AfterSaleProblemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.problem.AfterSaleProblemMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketItemMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleProblemStatusEnum; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * å®åé®é¢åº Service å®ç°ç±» |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class AfterSaleProblemServiceImpl implements AfterSaleProblemService { |
| | | |
| | | @Resource |
| | | private AfterSaleProblemMapper problemMapper; |
| | | @Resource |
| | | private AfterSaleTicketMapper ticketMapper; |
| | | @Resource |
| | | private AfterSaleTicketItemMapper ticketItemMapper; |
| | | @Resource |
| | | private AfterSaleNoRedisDAO noRedisDAO; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createProblem(AfterSaleProblemSaveReqVO createReqVO, Long userId) { |
| | | AfterSaleProblemDO problem = BeanUtils.toBean(createReqVO, AfterSaleProblemDO.class); |
| | | // æ¥æºå·¥åå卿¶è¡¥å
¨åä½åå·ä¸é»è®¤å®¢æ· |
| | | if (createReqVO.getSourceTicketId() != null) { |
| | | AfterSaleTicketDO ticket = ticketMapper.selectById(createReqVO.getSourceTicketId()); |
| | | if (ticket == null) { |
| | | throw exception(TICKET_NOT_EXISTS); |
| | | } |
| | | problem.setSourceTicketNo(ticket.getNo()); |
| | | if (problem.getCustomerId() == null) { |
| | | problem.setCustomerId(ticket.getCustomerId()); |
| | | } |
| | | } |
| | | problem.setNo(generateNo()) |
| | | .setStatus(AfterSaleProblemStatusEnum.DRAFT.getStatus()) |
| | | .setOwnerUserId(userId); |
| | | problemMapper.insert(problem); |
| | | // ç»å®éä»¶ |
| | | if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) { |
| | | storageAttachmentApi.bindAttachments("file", "aftersales_after_sale_problem", |
| | | problem.getId(), createReqVO.getBlobIds()); |
| | | } |
| | | return problem.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateProblem(AfterSaleProblemSaveReqVO updateReqVO) { |
| | | AfterSaleProblemDO problem = validateProblemExists(updateReqVO.getId()); |
| | | if (!AfterSaleProblemStatusEnum.DRAFT.getStatus().equals(problem.getStatus())) { |
| | | throw exception(PROBLEM_UPDATE_FAIL_STATUS); |
| | | } |
| | | AfterSaleProblemDO update = BeanUtils.toBean(updateReqVO, AfterSaleProblemDO.class); |
| | | problemMapper.updateById(update); |
| | | if (CollUtil.isNotEmpty(updateReqVO.getBlobIds())) { |
| | | storageAttachmentApi.updateAttachments("file", "aftersales_after_sale_problem", |
| | | updateReqVO.getId(), updateReqVO.getBlobIds()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteProblem(Long id) { |
| | | AfterSaleProblemDO problem = validateProblemExists(id); |
| | | if (!AfterSaleProblemStatusEnum.DRAFT.getStatus().equals(problem.getStatus())) { |
| | | throw exception(PROBLEM_DELETE_FAIL_STATUS); |
| | | } |
| | | problemMapper.deleteById(id); |
| | | storageAttachmentApi.deleteAttachmentsByRecord("aftersales_after_sale_problem", id); |
| | | // è§£é¤å·¥å对é®é¢çå
³è |
| | | ticketMapper.update(null, new LambdaUpdateWrapper<AfterSaleTicketDO>() |
| | | .eq(AfterSaleTicketDO::getProblemId, id) |
| | | .set(AfterSaleTicketDO::getProblemId, null) |
| | | .set(AfterSaleTicketDO::getProblemNo, null)); |
| | | } |
| | | |
| | | @Override |
| | | public AfterSaleProblemDO getProblem(Long id) { |
| | | return validateProblemExists(id); |
| | | } |
| | | |
| | | @Override |
| | | public List<AfterSaleProblemDO> getProblemList(Collection<Long> ids) { |
| | | if (CollUtil.isEmpty(ids)) { |
| | | return List.of(); |
| | | } |
| | | return problemMapper.selectBatchIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<AfterSaleProblemDO> getProblemPage(AfterSaleProblemPageReqVO pageReqVO) { |
| | | return problemMapper.selectPage(pageReqVO, pageReqVO.getNo(), pageReqVO.getKeyword(), |
| | | pageReqVO.getSourceTicketId(), pageReqVO.getItemId(), pageReqVO.getItemCode(), |
| | | pageReqVO.getIssueCategory(), |
| | | pageReqVO.getIssueType(), pageReqVO.getRootCauseCategory(), pageReqVO.getStatus()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void publishProblem(Long id) { |
| | | AfterSaleProblemDO problem = validateProblemExists(id); |
| | | if (!(AfterSaleProblemStatusEnum.DRAFT.getStatus().equals(problem.getStatus()) |
| | | || AfterSaleProblemStatusEnum.DISABLED.getStatus().equals(problem.getStatus()))) { |
| | | throw exception(PROBLEM_PUBLISH_FAIL_STATUS); |
| | | } |
| | | problemMapper.updateById(new AfterSaleProblemDO().setId(id) |
| | | .setStatus(AfterSaleProblemStatusEnum.PUBLISHED.getStatus()) |
| | | .setPublishTime(LocalDateTime.now())); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void disableProblem(Long id) { |
| | | AfterSaleProblemDO problem = validateProblemExists(id); |
| | | if (!AfterSaleProblemStatusEnum.PUBLISHED.getStatus().equals(problem.getStatus())) { |
| | | throw exception(PROBLEM_DISABLE_FAIL_STATUS); |
| | | } |
| | | problemMapper.updateById(new AfterSaleProblemDO().setId(id) |
| | | .setStatus(AfterSaleProblemStatusEnum.DISABLED.getStatus())); |
| | | } |
| | | |
| | | @Override |
| | | public List<AfterSaleProblemDO> getReferenceList(AfterSaleProblemPageReqVO pageReqVO) { |
| | | return problemMapper.selectReferenceList(pageReqVO.getItemId(), pageReqVO.getIssueCategory(), |
| | | pageReqVO.getIssueType(), pageReqVO.getRootCauseCategory(), pageReqVO.getKeyword()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public AfterSaleProblemDO createProblemFromTicket(AfterSaleTicketDO ticket) { |
| | | // å¹çï¼åä¸å·¥åå·²æ²æ·è¿åç´æ¥å¤ç¨ |
| | | AfterSaleProblemDO exist = problemMapper.selectBySourceTicketId(ticket.getId()); |
| | | if (exist != null) { |
| | | return exist; |
| | | } |
| | | AfterSaleProblemDO problem = new AfterSaleProblemDO() |
| | | .setNo(generateNo()) |
| | | .setTitle(ticket.getName()) |
| | | .setSourceTicketId(ticket.getId()) |
| | | .setSourceTicketNo(ticket.getNo()) |
| | | .setCustomerId(ticket.getCustomerId()) |
| | | .setIssueType(ticket.getIssueType()) |
| | | .setIssueCategory(ticket.getIssueCategory()) |
| | | .setSeverityLevel(ticket.getSeverityLevel()) |
| | | .setDescription(ticket.getIssueDescription()) |
| | | .setStatus(AfterSaleProblemStatusEnum.DRAFT.getStatus()) |
| | | .setOwnerUserId(getLoginUserId()); |
| | | // åå·¥å馿¡æç»ä½ä¸ºç©æå¿«ç
§(å
è®¸æ æç»è¡) |
| | | AfterSaleTicketItemDO firstItem = CollUtil.getFirst(ticketItemMapper.selectListByTicketId(ticket.getId())); |
| | | if (firstItem != null) { |
| | | problem.setItemId(firstItem.getItemId()) |
| | | .setItemCode(firstItem.getItemCode()) |
| | | .setItemName(firstItem.getItemName()) |
| | | .setBatchId(firstItem.getBatchId()) |
| | | .setBatchCode(firstItem.getBatchCode()); |
| | | } |
| | | problemMapper.insert(problem); |
| | | return problem; |
| | | } |
| | | |
| | | // ========== å
鍿¹æ³ ========== |
| | | |
| | | private AfterSaleProblemDO validateProblemExists(Long id) { |
| | | AfterSaleProblemDO problem = problemMapper.selectById(id); |
| | | if (problem == null) { |
| | | throw exception(PROBLEM_NOT_EXISTS); |
| | | } |
| | | return problem; |
| | | } |
| | | |
| | | private String generateNo() { |
| | | String no = noRedisDAO.generate(AfterSaleNoRedisDAO.PROBLEM_NO_PREFIX); |
| | | if (problemMapper.selectByNo(no) != null) { |
| | | throw exception(PROBLEM_NO_EXISTS); |
| | | } |
| | | return no; |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.repair.AfterSaleRepairMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleRepairStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleVisitStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | repairMapper.updateById(new AfterSaleRepairDO().setId(id) |
| | | .setStatus(AfterSaleRepairStatusEnum.COMPLETED.getStatus()) |
| | | .setFinishTime(LocalDateTime.now())); |
| | | // æ´æ°å·¥åç¶æ |
| | | // æ´æ°å·¥åç¶æï¼å®ç»åè¿å
¥å¾
å访 |
| | | AfterSaleTicketDO ticket = ticketMapper.selectById(repair.getTicketId()); |
| | | if (ticket != null) { |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId()) |
| | | .setStatus(cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum.COMPLETED.getStatus()) |
| | | .setResolvedTime(LocalDateTime.now())); |
| | | .setStatus(AfterSaleTicketStatusEnum.COMPLETED.getStatus()) |
| | | .setResolvedTime(LocalDateTime.now()) |
| | | .setVisitStatus(AfterSaleVisitStatusEnum.WAITING.getStatus())); |
| | | } |
| | | } |
| | | |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketSaveReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.problem.AfterSaleProblemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketItemMapper; |
| | |
| | | import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleVisitStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum; |
| | | import cn.iocoder.yudao.module.aftersales.service.permission.AfterSalePermissionService; |
| | | import cn.iocoder.yudao.module.aftersales.service.permission.bo.AfterSalePermissionCreateReqBO; |
| | | import cn.iocoder.yudao.module.aftersales.service.problem.AfterSaleProblemService; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | |
| | | @Resource |
| | | private AfterSalePermissionService permissionService; |
| | | |
| | | @Resource |
| | | private AfterSaleProblemService problemService; |
| | | |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | |
| | | return ticketMapper.selectPage(pageReqVO, userId, pageReqVO.getSceneType(), |
| | | pageReqVO.getNo(), pageReqVO.getName(), |
| | | pageReqVO.getCustomerId(), pageReqVO.getSaleOrderId(), |
| | | pageReqVO.getStatus(), pageReqVO.getIssueType()); |
| | | pageReqVO.getStatus(), pageReqVO.getIssueType(), pageReqVO.getVisitStatus()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | AfterSaleTicketDO ticket = validateTicketExists(id); |
| | | validateTicketStatus(ticket, AfterSaleTicketStatusEnum.PROCESSING, |
| | | AfterSaleTicketStatusEnum.WAITING_RETURN, AfterSaleTicketStatusEnum.WAITING_REFUND); |
| | | // å®ç»åè¿å
¥å¾
å访ï¼éç»è®°æ»¡æçåè®¿åæ¹å¯å½æ¡£ |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(id) |
| | | .setStatus(AfterSaleTicketStatusEnum.COMPLETED.getStatus()) |
| | | .setResolvedTime(LocalDateTime.now())); |
| | | .setResolvedTime(LocalDateTime.now()) |
| | | .setVisitStatus(AfterSaleVisitStatusEnum.WAITING.getStatus())); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void closeTicket(Long id) { |
| | | // 1. æ ¡éªå·¥ååå¨ä¸å¤äºå·²å®ç»ç¶æ |
| | | AfterSaleTicketDO ticket = validateTicketExists(id); |
| | | validateTicketStatus(ticket, AfterSaleTicketStatusEnum.COMPLETED); |
| | | // 2. æ ¡éªå·²å访éè¿ï¼æªå访éè¿ä¸å
è®¸å½æ¡£ |
| | | if (!AfterSaleVisitStatusEnum.PASSED.getStatus().equals(ticket.getVisitStatus())) { |
| | | throw exception(TICKET_ARCHIVE_FAIL_NOT_VISITED); |
| | | } |
| | | // 3. èªå¨æ²æ·é®é¢(å¹çï¼åä¸å·¥åä»
æ²æ·ä¸æ¡)ï¼è¿åè稿æé®é¢ |
| | | AfterSaleProblemDO problem = problemService.createProblemFromTicket(ticket); |
| | | // 4. ç½®å½æ¡£ç»æ(åªè¯») |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(id) |
| | | .setStatus(AfterSaleTicketStatusEnum.CLOSED.getStatus()) |
| | | .setClosedTime(LocalDateTime.now())); |
| | | .setClosedTime(LocalDateTime.now()) |
| | | .setProblemId(problem.getId()) |
| | | .setProblemNo(problem.getNo())); |
| | | } |
| | | |
| | | @Override |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.service.visit; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitCreateReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.visit.AfterSaleVisitDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ Service æ¥å£ |
| | | */ |
| | | public interface AfterSaleVisitService { |
| | | |
| | | /** |
| | | * ç»è®°å访 |
| | | * <p> |
| | | * å·¥åé¡»å¤äºå·²å®ç»(å¾
å访)ç¶æãç»è®°æ¶åæ¥æ¨è¿å·¥åï¼ |
| | | * 满æä¸æªå¤å â å·¥åå访éè¿(å¯å½æ¡£)ï¼ä¸æ»¡ææå¤å â å·¥ååéå¤çä¸éæ°å¤çã |
| | | * |
| | | * @param createReqVO ç»è®°ä¿¡æ¯ |
| | | * @param userId å½åç¨æ·ç¼å·(å访人) |
| | | * @return å访记å½ç¼å· |
| | | */ |
| | | Long createVisit(@Valid AfterSaleVisitCreateReqVO createReqVO, Long userId); |
| | | |
| | | /** |
| | | * è·å¾åè®¿è®°å½ |
| | | * |
| | | * @param id ç¼å· |
| | | * @return åè®¿è®°å½ |
| | | */ |
| | | AfterSaleVisitDO getVisit(Long id); |
| | | |
| | | /** |
| | | * è·å¾æå·¥åçå访记å½å表 |
| | | * |
| | | * @param ticketId å·¥åç¼å· |
| | | * @return å访记å½å表 |
| | | */ |
| | | List<AfterSaleVisitDO> getVisitListByTicketId(Long ticketId); |
| | | |
| | | /** |
| | | * è·å¾å访记å½å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return å访记å½å页 |
| | | */ |
| | | PageResult<AfterSaleVisitDO> getVisitPage(AfterSaleVisitPageReqVO pageReqVO); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.service.visit; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitCreateReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.visit.vo.AfterSaleVisitPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.visit.AfterSaleVisitDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.visit.AfterSaleVisitMapper; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleVisitStatusEnum; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.TICKET_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.VISIT_CREATE_FAIL_STATUS; |
| | | import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.VISIT_NOT_EXISTS; |
| | | |
| | | /** |
| | | * å®ååè®¿è®°å½ Service å®ç°ç±» |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class AfterSaleVisitServiceImpl implements AfterSaleVisitService { |
| | | |
| | | @Resource |
| | | private AfterSaleVisitMapper visitMapper; |
| | | @Resource |
| | | private AfterSaleTicketMapper ticketMapper; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createVisit(AfterSaleVisitCreateReqVO createReqVO, Long userId) { |
| | | // 1. æ ¡éªå·¥åï¼å¿
é¡»åå¨ä¸å¤äºå·²å®ç»(å¾
å访)ç¶æ |
| | | AfterSaleTicketDO ticket = ticketMapper.selectById(createReqVO.getTicketId()); |
| | | if (ticket == null) { |
| | | throw exception(TICKET_NOT_EXISTS); |
| | | } |
| | | if (!AfterSaleTicketStatusEnum.COMPLETED.getStatus().equals(ticket.getStatus())) { |
| | | throw exception(VISIT_CREATE_FAIL_STATUS); |
| | | } |
| | | |
| | | // 2. æå
¥åè®¿è®°å½ |
| | | AfterSaleVisitDO visitDO = BeanUtils.toBean(createReqVO, AfterSaleVisitDO.class) |
| | | .setVisitorUserId(userId) |
| | | .setVisitTime(LocalDateTime.now()); |
| | | visitMapper.insert(visitDO); |
| | | |
| | | // 3. ç»å®éä»¶ |
| | | if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) { |
| | | storageAttachmentApi.bindAttachments("file", "aftersales_after_sale_visit", |
| | | visitDO.getId(), createReqVO.getBlobIds()); |
| | | } |
| | | |
| | | // 4. æ¨è¿å·¥ååè®¿ç¶æï¼æ»¡æä¸æªå¤å â éè¿ï¼ä¸æ»¡ææå¤å â åéå¤çä¸éæ°å¤ç |
| | | boolean satisfied = Boolean.TRUE.equals(createReqVO.getSatisfied()); |
| | | boolean recurred = Boolean.TRUE.equals(createReqVO.getRecurred()); |
| | | AfterSaleTicketDO update = new AfterSaleTicketDO().setId(ticket.getId()) |
| | | .setLastVisitTime(visitDO.getVisitTime()) |
| | | .setLastVisitSatisfied(satisfied) |
| | | .setLastVisitRecurred(recurred); |
| | | if (satisfied && !recurred) { |
| | | update.setVisitStatus(AfterSaleVisitStatusEnum.PASSED.getStatus()); |
| | | } else { |
| | | update.setStatus(AfterSaleTicketStatusEnum.PROCESSING.getStatus()) |
| | | .setResolvedTime(null) |
| | | .setVisitStatus(AfterSaleVisitStatusEnum.WAITING.getStatus()); |
| | | } |
| | | ticketMapper.updateById(update); |
| | | return visitDO.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public AfterSaleVisitDO getVisit(Long id) { |
| | | AfterSaleVisitDO visitDO = visitMapper.selectById(id); |
| | | if (visitDO == null) { |
| | | throw exception(VISIT_NOT_EXISTS); |
| | | } |
| | | return visitDO; |
| | | } |
| | | |
| | | @Override |
| | | public List<AfterSaleVisitDO> getVisitListByTicketId(Long ticketId) { |
| | | return visitMapper.selectListByTicketId(ticketId); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<AfterSaleVisitDO> getVisitPage(AfterSaleVisitPageReqVO pageReqVO) { |
| | | return visitMapper.selectPage(pageReqVO, pageReqVO.getTicketId(), |
| | | pageReqVO.getVisitType(), pageReqVO.getSatisfied()); |
| | | } |
| | | |
| | | } |
| | |
| | | // ===== å®å ===== |
| | | AFTER_SALE_TICKET("aftersales_after_sale_ticket", "å®åå·¥å"), |
| | | AFTER_SALE_RETURN("aftersales_after_sale_return", "å®åéè´§ç³è¯·"), |
| | | AFTER_SALE_VISIT("aftersales_after_sale_visit", "å®åå访"), |
| | | AFTER_SALE_PROBLEM("aftersales_after_sale_problem", "å®åé®é¢åº"), |
| | | |
| | | // ===== MES ===== |
| | | MES_PD_DOCUMENT("mes_pd_document", "MES è®¾è®¡èµæ"), |