liu
2026-09-14 8f058b8be8a2fea5b93044ac84278177787e45af
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java
@@ -5,6 +5,7 @@
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;
@@ -12,9 +13,11 @@
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;
@@ -49,6 +52,9 @@
    private AfterSalePermissionService permissionService;
    @Resource
    private AfterSaleProblemService problemService;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Override
@@ -67,13 +73,14 @@
                .setOwnerUserId(userId);
        ticketMapper.insert(ticket);
        // 3. 插入明细行
        // 3. 插入明细行(每条生成独立售后单号:工单号-序号)
        if (CollUtil.isNotEmpty(createReqVO.getItems())) {
            createReqVO.getItems().forEach(item -> {
                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class);
            for (int i = 0; i < createReqVO.getItems().size(); i++) {
                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(createReqVO.getItems().get(i), AfterSaleTicketItemDO.class);
                itemDO.setTicketId(ticket.getId());
                itemDO.setItemNo(buildItemNo(no, i + 1));
                ticketItemMapper.insert(itemDO);
            });
            }
        }
        // 4. 绑定附件
@@ -102,14 +109,15 @@
        AfterSaleTicketDO updateObj = BeanUtils.toBean(updateReqVO, AfterSaleTicketDO.class);
        ticketMapper.updateById(updateObj);
        // 3. 更新明细行(先删后增)
        // 3. 更新明细行(先删后增,每条生成独立售后单号:工单号-序号)
        ticketItemMapper.deleteByTicketId(ticket.getId());
        if (CollUtil.isNotEmpty(updateReqVO.getItems())) {
            updateReqVO.getItems().forEach(item -> {
                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class);
            for (int i = 0; i < updateReqVO.getItems().size(); i++) {
                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(updateReqVO.getItems().get(i), AfterSaleTicketItemDO.class);
                itemDO.setTicketId(ticket.getId());
                itemDO.setItemNo(buildItemNo(ticket.getNo(), i + 1));
                ticketItemMapper.insert(itemDO);
            });
            }
        }
        // 4. 更新附件
@@ -146,7 +154,7 @@
        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
@@ -178,19 +186,31 @@
        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
@@ -204,6 +224,11 @@
    // ========== 内部方法 ==========
    /** 生成明细售后单号:工单号-两位序号 */
    private String buildItemNo(String ticketNo, int index) {
        return ticketNo + "-" + String.format("%02d", index);
    }
    private AfterSaleTicketDO validateTicketExists(Long id) {
        AfterSaleTicketDO ticket = ticketMapper.selectById(id);
        if (ticket == null) {