# EngineeringRequestServiceImpl.java

    package cloud.wconnect.frontoffice.service.engineeringrequest.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.S3BucketFunctionCons;
    import cloud.wconnect.frontoffice.cons.engineeringrequest.EngineeringRequestCons;
    import cloud.wconnect.frontoffice.cons.engineeringrequest.EngineeringRequestStatusEnum;
    import cloud.wconnect.frontoffice.entity.engineeringrequest.*;
    import cloud.wconnect.frontoffice.mapper.engineeringrequest.EngineeringRequestMapper;
    import cloud.wconnect.frontoffice.pojo.dto.engineeringrequest.EngineeringRequestDTO;
    import cloud.wconnect.frontoffice.pojo.dto.engineeringrequest.UpdateEngineeringRequestDTO;
    import cloud.wconnect.frontoffice.pojo.dto.engineeringrequest.UpdateEngineeringRequestStatusDTO;
    import cloud.wconnect.frontoffice.pojo.query.engineeringrequest.EngineeringRequestQuery;
    import cloud.wconnect.frontoffice.service.engineeringrequest.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-05-10
     */
    @Service
    @DS("logbook")
    public class EngineeringRequestServiceImpl extends ServiceImpl<EngineeringRequestMapper, EngineeringRequest> implements IEngineeringRequestService {

        @Autowired
        private IEngineeringRequestLocationService engineeringRequestLocationService;
        @Autowired
        private IEngineeringRequestAreaService engineeringRequestAreaService;
        @Autowired
        private IEngineeringRequestItemService engineeringRequestItemService;
        @Autowired
        private IEngineeringRequestIssueService engineeringRequestIssueService;
        @Lazy
        @Autowired
        private IEngineeringRequestCommentService engineeringRequestCommentService;
        @Autowired
        private BeanMapper beanMapper;
        @Value("${aws-s3.region}")
        private String region;
        @Value("#{${aws-s3.function-bucket}}")
        private Map<String, Map<String, Object>> bucketMap;
        @Autowired
        private Executor executor;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 添加 Engineering Request
         *
         * @param propertyId                  资产 Id
         * @param updateEngineeringRequestDTO Engineering Request 信息
         * @param userId                      操作人
         * @return Engineering Request
         * @author yyt
         * @date 2023/3/29
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public EngineeringRequest save(int propertyId, UpdateEngineeringRequestDTO updateEngineeringRequestDTO, int userId) {
            EngineeringRequest engineeringRequest = beanMapper.map(updateEngineeringRequestDTO, EngineeringRequest.class);
            engineeringRequest.setPropertyId(propertyId);
            engineeringRequest.setUserId(userId);
            engineeringRequest.setTicketCreateTime(DateTimeUtils.nowTimestamp());
            save(engineeringRequest);

            engineeringRequestCommentService.saveEngineeringRequestAttachment(propertyId, engineeringRequest.getId(),
                    updateEngineeringRequestDTO.getAttachments(), userId);

            return engineeringRequest;
        }

        /**
         * 校验存在
         *
         * @param id engineering request id
         * @return Engineering Request
         * @author yyt
         * @date 2023/3/29
         */
        @Override
        public EngineeringRequest checkExist(int id) {
            EngineeringRequest engineeringRequest = getById(id);
            if (Objects.isNull(engineeringRequest)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the engineeringRequest is not found;id:" + id);
            }
            return engineeringRequest;
        }

        /**
         * 修改 Engineering Request
         *
         * @param updateBeforeEngineeringRequest 修改之前的 Engineering Request
         * @param updateEngineeringRequestDTO    Engineering Request 信息
         * @param userId                         操作人
         * @return Engineering Request
         * @author yyt
         * @date 2023/3/29
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public EngineeringRequest update(EngineeringRequest updateBeforeEngineeringRequest,
                                         UpdateEngineeringRequestDTO updateEngineeringRequestDTO,
                                         int userId) {
            update(new UpdateWrapper<EngineeringRequest>().lambda()
                    .eq(EngineeringRequest::getId, updateBeforeEngineeringRequest.getId())
                    .set(EngineeringRequest::getPriority, updateEngineeringRequestDTO.getPriority())
                    .set(EngineeringRequest::getCcemail, updateEngineeringRequestDTO.getCcemail())
                    .set(EngineeringRequest::getComments, updateEngineeringRequestDTO.getComments())
                    .set(EngineeringRequest::getLocationId, updateEngineeringRequestDTO.getLocationId())
                    .set(EngineeringRequest::getAreaId, updateEngineeringRequestDTO.getAreaId())
                    .set(EngineeringRequest::getItemId, updateEngineeringRequestDTO.getItemId())
                    .set(EngineeringRequest::getIssueId, updateEngineeringRequestDTO.getIssueId())
                    .set(EngineeringRequest::getContactInfo, updateEngineeringRequestDTO.getContactInfo())
            );

            //查询修改前的附件
            List<String> updateBeforeContentList = engineeringRequestCommentService.list(new QueryWrapper<EngineeringRequestComment>().lambda()
                            .eq(EngineeringRequestComment::getRequestId, updateBeforeEngineeringRequest.getId())
                            .eq(EngineeringRequestComment::getCommentType, EngineeringRequestComment.TYPE_REQUEST_ATTACHMENT))
                    .stream().map(EngineeringRequestComment::getContent).toList();

            engineeringRequestCommentService.clearEngineeringRequestAttachment(updateBeforeEngineeringRequest.getId());

            engineeringRequestCommentService.saveEngineeringRequestAttachment(updateBeforeEngineeringRequest.getPropertyId(),
                    updateBeforeEngineeringRequest.getId(), updateEngineeringRequestDTO.getAttachments(), userId);

            TransactionUtils.afterCommitAsyncExecute(executor,() -> {
                //修改前的s3Key集合
                List<String> updateBeforeS3Keys = AttachmentUtil.getKeysByAttachments(updateBeforeContentList, objectMapper);
                //修改后的s3Key集合
                List<String> updateAfterS3Keys = AttachmentUtil.getKeysByAttachments(updateEngineeringRequestDTO.getAttachments(), objectMapper);
                //删除的附件 = 修改前的附件集合 - 修改后的附件集合
                List<String> delS3Keys = Lists.newArrayList(updateBeforeS3Keys);
                delS3Keys.removeAll(updateAfterS3Keys);
                S3Utils.batchDeleteFile(region,bucketMap.get(S3BucketFunctionCons.ENGINEERING_REQUEST),delS3Keys);
            });

            EngineeringRequest updateContent = beanMapper.map(updateEngineeringRequestDTO, EngineeringRequest.class);
            updateContent.setId(updateBeforeEngineeringRequest.getId());
            return updateContent.setPropertyId(updateBeforeEngineeringRequest.getPropertyId())
                    .setUserId(updateBeforeEngineeringRequest.getUserId());
        }

        /**
         * 修改状态
         *
         * @param updateBeforeEngineeringRequest    修改之前的 Engineering Request
         * @param updateEngineeringRequestStatusDTO 修改状态信息
         * @param userId                            操作人
         * @return Engineering Request
         * @author yyt
         * @date 2023/3/29
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public EngineeringRequest updateStatus(EngineeringRequest updateBeforeEngineeringRequest,
                                               UpdateEngineeringRequestStatusDTO updateEngineeringRequestStatusDTO,
                                               int userId) {
            EngineeringRequest engineeringRequest = getOne(new QueryWrapper<EngineeringRequest>().lambda()
                    .eq(EngineeringRequest::getId, updateBeforeEngineeringRequest.getId())
                    .select(EngineeringRequest::getId, EngineeringRequest::getTicketStatus).last("for update"));
            Integer newStatus = updateEngineeringRequestStatusDTO.getNewStatus();
            switch (newStatus) {
                case EngineeringRequest.STATUS_RESOLVE:
                    if (engineeringRequest.getTicketStatus() != EngineeringRequest.STATUS_IN_PROGRESS) {
                        throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(EngineeringRequestStatusEnum.IN_PROGRESS.getI18nCode(), CommonFieldI18nConstant.RESOLVE),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "resolve can only be operated when it is in progress;id:" + updateBeforeEngineeringRequest.getId());
                    }
                    break;
                case EngineeringRequest.STATUS_IN_PROGRESS:
                    if (engineeringRequest.getTicketStatus() != EngineeringRequest.STATUS_OPEN) {
                        throw new BadRequestException(CommonErrorI18nConstant.SPECIFIC_STATE_CAN_OPERATE,
                                i18nMessageUtils.getErrorParamMapNeedTranslate(EngineeringRequestStatusEnum.OPEN.getI18nCode(), CommonFieldI18nConstant.ACCEPT),
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "accept can only be operated when it is open;id:" + updateBeforeEngineeringRequest.getId());
                    }
                default:
            }

            EngineeringRequest updateContent = new EngineeringRequest();
            updateContent.setTicketStatus(updateEngineeringRequestStatusDTO.getNewStatus());
            updateContent.setCompleteUser(userId);
            updateContent.setResolveTime(DateTimeUtils.nowTimestamp());
            updateContent.setId(updateBeforeEngineeringRequest.getId());
            updateById(updateContent);

            return updateBeforeEngineeringRequest.setTicketStatus(updateContent.getTicketStatus())
                    .setCompleteUser(updateContent.getCompleteUser()).setResolveTime(updateContent.getResolveTime());
        }

        /**
         * 分页查询数据
         *
         * @param propertyId 资产 id
         * @return Engineering Request
         * @author yyt
         * @date 2024/11/18
         **/
        @Override
        public Page<EngineeringRequest> listPage(Integer propertyId, EngineeringRequestQuery query) {
            return page(new Page<>(query.getPage(), query.getSize()), new QueryWrapper<EngineeringRequest>()
                    .lambda().eq(EngineeringRequest::getPropertyId, propertyId)
                    .eq(Objects.nonNull(query.getPriority()), EngineeringRequest::getPriority, query.getPriority())
                    .eq(Objects.nonNull(query.getStatus()), EngineeringRequest::getTicketStatus, query.getStatus())
                    .orderByDesc(EngineeringRequest::getTicketCreateTime));
        }

        /**
         * 获取 Engineering Request
         *
         * @param propertyId 资产 id
         * @param query      查询条件
         * @return Engineering Request
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public ListPagedDTO<EngineeringRequestDTO> listEngineeringRequestDTO(int propertyId, EngineeringRequestQuery query) {
            Page<EngineeringRequest> page = listPage(propertyId, query);
            List<EngineeringRequest> records = page.getRecords();
            if (CollectionUtils.isEmpty(records)) {
                return new ListPagedDTO<>();
            }

            int size = records.size();
            List<Integer> ids = Lists.newArrayListWithExpectedSize(size);
            Set<Integer> locationIds = Sets.newHashSetWithExpectedSize(size);
            Set<Integer> areIds = Sets.newHashSetWithExpectedSize(size);
            Set<Integer> itemIds = Sets.newHashSetWithExpectedSize(size);
            Set<Integer> issueIds = Sets.newHashSetWithExpectedSize(size);
            for (EngineeringRequest engineeringRequest : records) {
                ids.add(engineeringRequest.getId());
                locationIds.add(engineeringRequest.getLocationId());
                areIds.add(engineeringRequest.getAreaId());
                itemIds.add(engineeringRequest.getItemId());
                issueIds.add(engineeringRequest.getIssueId());
            }

            Map<Integer, String> locationNameById = Collections.emptyMap();
            if (CollectionUtils.isNotEmpty(locationIds)) {
                locationNameById = engineeringRequestLocationService.listByIds(locationIds)
                        .stream().collect(Collectors.toMap(EngineeringRequestLocation::getId, EngineeringRequestLocation::getName));
            }
            Map<Integer, String> areaNameById = Collections.emptyMap();
            if (CollectionUtils.isNotEmpty(areIds)) {
                areaNameById = engineeringRequestAreaService.listByIds(areIds)
                        .stream().collect(Collectors.toMap(EngineeringRequestArea::getId, EngineeringRequestArea::getName));
            }
            Map<Integer, String> itemNameById = Collections.emptyMap();
            if (CollectionUtils.isNotEmpty(itemIds)) {
                itemNameById = engineeringRequestItemService.listByIds(itemIds)
                        .stream().collect(Collectors.toMap(EngineeringRequestItem::getId, EngineeringRequestItem::getName));
            }
            Map<Integer, String> issueNameById = Collections.emptyMap();
            if (CollectionUtils.isNotEmpty(issueIds)) {
                issueNameById = engineeringRequestIssueService.listByIds(issueIds)
                        .stream().collect(Collectors.toMap(EngineeringRequestIssue::getId, EngineeringRequestIssue::getName));
            }

            Map<Integer, List<String>> attachmentsByRequestId = engineeringRequestCommentService.list(ids, EngineeringRequestComment.TYPE_REQUEST_ATTACHMENT)
                    .stream().collect(Collectors.groupingBy(EngineeringRequestComment::getRequestId,
                            Collectors.mapping(EngineeringRequestComment::getContent, Collectors.toList())));

            Map<Integer, String> finalLocationNameById = locationNameById;
            Map<Integer, String> finalAreaNameById = areaNameById;
            Map<Integer, String> finalItemNameById = itemNameById;
            Map<Integer, String> finalIssueNameById = issueNameById;
            return new ListPagedDTO<>(page.getTotal(), beanMapper.mapList(page.getRecords(), EngineeringRequestDTO.class, item -> {
                item.setLocationName(finalLocationNameById.get(item.getLocationId()));
                item.setAreaName(finalAreaNameById.get(item.getAreaId()));
                item.setItemName(finalItemNameById.get(item.getItemId()));
                item.setIssueName(finalIssueNameById.get(item.getIssueId()));
                item.setAttachments(attachmentsByRequestId.get(item.getId()));
            }));
        }

        /**
         * 获取 Engineering Request 详情
         *
         * @param engineeringRequest Engineering Request
         * @return Engineering Request
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public EngineeringRequestDTO getDetailDTO(EngineeringRequest engineeringRequest) {
            EngineeringRequestDTO engineeringRequestDTO = beanMapper.map(engineeringRequest, EngineeringRequestDTO.class);
            engineeringRequestDTO.setLocationName(engineeringRequestLocationService.getById(engineeringRequest.getLocationId()).getName());
            engineeringRequestDTO.setAreaName(engineeringRequestAreaService.getById(engineeringRequest.getAreaId()).getName());
            engineeringRequestDTO.setItemName(engineeringRequestItemService.getById(engineeringRequest.getItemId()).getName());
            engineeringRequestDTO.setIssueName(engineeringRequestIssueService.getById(engineeringRequest.getIssueId()).getName());

            List<EngineeringRequestComment> attachments = engineeringRequestCommentService.getEngineeringRequestAttachment(engineeringRequest.getId());
            engineeringRequestDTO.setAttachments(attachments.stream().map(EngineeringRequestComment::getContent).collect(Collectors.toList()));

            return engineeringRequestDTO;
        }

        /**
         * 校验 Location 是否被使用
         *
         * @param propertyId 资产 id
         * @param locationId Location Id
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public void checkLocationUsed(Integer propertyId, int locationId) {
            // 被占用时不能删除
            EngineeringRequest engineeringRequest = getOne(new QueryWrapper<EngineeringRequest>()
                    .lambda().eq(EngineeringRequest::getPropertyId, propertyId)
                    .eq(EngineeringRequest::getLocationId, locationId)
                    .last("limit 1"));
            if (Objects.nonNull(engineeringRequest)) {
                throw new BadRequestException(EngineeringRequestCons.ENGINEERING_REQUEST_LOCATION_USED_BY_ENGINEERING_REQUEST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Sorry, the location is used by engineering request;id:" + locationId);
            }
        }

        /**
         * 校验 Area 是否被使用
         *
         * @param propertyId 资产 id
         * @param areaId     Area Id
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public void checkAreaUsed(Integer propertyId, Integer areaId) {
            // 被占用时不能删除
            EngineeringRequest engineeringRequest = getOne(new QueryWrapper<EngineeringRequest>()
                    .lambda().eq(EngineeringRequest::getPropertyId, propertyId)
                    .eq(EngineeringRequest::getAreaId, areaId)
                    .last("limit 1"));
            if (Objects.nonNull(engineeringRequest)) {
                throw new BadRequestException(EngineeringRequestCons.ENGINEERING_REQUEST_AREA_USED_BY_ENGINEERING_REQUEST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Sorry, the area is used by engineering request;id:" + areaId);
            }
        }

        /**
         * 校验 Item 是否被使用
         *
         * @param propertyId 资产 id
         * @param itemId     Item Id
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public void checkItemUsed(Integer propertyId, int itemId) {
            // 被占用时不能删除
            EngineeringRequest engineeringRequest = getOne(new QueryWrapper<EngineeringRequest>()
                    .lambda().eq(EngineeringRequest::getPropertyId, propertyId)
                    .eq(EngineeringRequest::getItemId, itemId)
                    .last("limit 1"));
            if (Objects.nonNull(engineeringRequest)) {
                throw new BadRequestException(EngineeringRequestCons.ENGINEERING_REQUEST_ITEM_USED_BY_ENGINEERING_REQUEST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Sorry, the item is used by engineering request;id:" + itemId);
            }
        }

        /**
         * 校验 Issue 是否被使用
         *
         * @param propertyId 资产 id
         * @param issueId    Issue Id
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public void checkIssueUsed(int propertyId, int issueId) {
            // 被占用时不能删除
            EngineeringRequest engineeringRequest = getOne(new QueryWrapper<EngineeringRequest>()
                    .lambda().eq(EngineeringRequest::getPropertyId, propertyId)
                    .eq(EngineeringRequest::getIssueId, issueId)
                    .last("limit 1"));
            if (Objects.nonNull(engineeringRequest)) {
                throw new BadRequestException(EngineeringRequestCons.ENGINEERING_REQUEST_ISSUE_USED_BY_ENGINEERING_REQUEST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Sorry, the issue is used by engineering request;id:" + issueId);
            }
        }

    }