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() .eq(AfterSaleTicketDO::getProblemId, id) .set(AfterSaleTicketDO::getProblemId, null) .set(AfterSaleTicketDO::getProblemNo, null)); } @Override public AfterSaleProblemDO getProblem(Long id) { return validateProblemExists(id); } @Override public List getProblemList(Collection ids) { if (CollUtil.isEmpty(ids)) { return List.of(); } return problemMapper.selectBatchIds(ids); } @Override public PageResult 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 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; } }