CoffeechatCommandService.java

package com.newbit.coffeechat.command.application.service;

import com.newbit.coffeechat.command.application.dto.request.CoffeechatCancelRequest;
import com.newbit.coffeechat.command.domain.aggregate.RequestTime;
import com.newbit.coffeechat.command.domain.repository.CoffeechatRepository;
import com.newbit.coffeechat.command.application.dto.request.CoffeechatCreateRequest;
import com.newbit.coffeechat.command.domain.aggregate.Coffeechat;
import com.newbit.coffeechat.command.domain.repository.RequestTimeRepository;
import com.newbit.coffeechat.query.dto.request.CoffeechatSearchServiceRequest;
import com.newbit.coffeechat.query.dto.response.CoffeechatListResponse;
import com.newbit.coffeechat.query.service.CoffeechatQueryService;
import com.newbit.coffeechat.query.dto.response.ProgressStatus;
import com.newbit.coffeeletter.domain.chat.CoffeeLetterRoom;
import com.newbit.coffeeletter.dto.CoffeeLetterRoomDTO;
import com.newbit.coffeeletter.service.RoomService;
import com.newbit.common.exception.BusinessException;
import com.newbit.common.exception.ErrorCode;
import com.newbit.notification.command.application.dto.request.NotificationSendRequest;
import com.newbit.notification.command.application.service.NotificationCommandService;
import com.newbit.purchase.command.application.service.DiamondCoffeechatTransactionCommandService;
import com.newbit.user.dto.response.MentorDTO;
import com.newbit.user.dto.response.UserDTO;
import com.newbit.user.service.MentorService;
import com.newbit.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
public class CoffeechatCommandService {

    private final CoffeechatRepository coffeechatRepository;
    private final CoffeechatQueryService coffeechatQueryService;
    private final RequestTimeRepository requestTimeRepository;
    private final MentorService mentorService;
    private final DiamondCoffeechatTransactionCommandService transactionCommandService;
    private final NotificationCommandService notificationCommandService;
    private final RoomService roomService;
    private final UserService userService;

    /**
     * 한두 번만 사용하는 간단한 조회여서 과도한 추상화를 피하기 위해
     * requestTimeService 로직 대신 repository 직접 호출해서 사용
     */

    /* 커피챗 등록 */
    @Transactional
    public Long createCoffeechat(Long userId, CoffeechatCreateRequest request) {
        // 1. 진행중인 커피챗이 존재
        CoffeechatSearchServiceRequest coffeechatSearchServiceRequest = new CoffeechatSearchServiceRequest();
        coffeechatSearchServiceRequest.setMenteeId(userId);
        coffeechatSearchServiceRequest.setMentorId(request.getMentorId());
        coffeechatSearchServiceRequest.setIsProgressing(true);
        CoffeechatListResponse coffeechatDtos = coffeechatQueryService.getCoffeechats(coffeechatSearchServiceRequest);
        if (!coffeechatDtos.getCoffeechats().isEmpty()) throw new BusinessException(ErrorCode.COFFEECHAT_ALREADY_EXIST);

        // 2. 커피챗 등록
        Coffeechat newCoffeechat = Coffeechat.of(userId, request.getMentorId(), request.getRequestMessage(), request.getPurchaseQuantity());

        Coffeechat coffeechat = coffeechatRepository.save(newCoffeechat);

        // 3. 커피챗 요청 등록(서비스 함수 생성)
        createRequestTime(coffeechat.getCoffeechatId(), request.getRequestTimes(), request.getPurchaseQuantity());

        // 4. 커피챗 요청 등록시 멘토에게 실시간 알림 발송
        notificationCommandService.sendNotification(
               new NotificationSendRequest(
                       mentorService.getUserIdByMentorId(request.getMentorId())
                       , 3L
                       , coffeechat.getCoffeechatId()
                       , "새로운 커피챗 신청이 도착했습니다."
               )
        );

        return coffeechat.getCoffeechatId();
    }

    private void createRequestTime(Long coffeechatId, List<LocalDateTime> requestTimes, int purchaseQuantity) {
        requestTimes.forEach(time -> {
            if (time.isBefore(LocalDateTime.now())) {
                throw new BusinessException(ErrorCode.REQUEST_DATE_IN_PAST); // 시작 날짜가 오늘보다 이전입니다.
            }

            LocalDateTime endTime = time.plusMinutes(30L * purchaseQuantity);
            requestTimeRepository.save(RequestTime.of(
                    time.toLocalDate(),
                    time,
                    endTime,
                    coffeechatId
            ));
        });
    }


    @Transactional
    public void markAsPurchased(Long coffeechatId) {
        Coffeechat coffeechat = coffeechatRepository.findById(coffeechatId)
                .orElseThrow(() -> new BusinessException(ErrorCode.COFFEECHAT_NOT_FOUND));

        if (coffeechat.getProgressStatus() != ProgressStatus.PAYMENT_WAITING) {
            throw new BusinessException(ErrorCode.COFFEECHAT_NOT_PURCHASABLE);
        }

        coffeechat.markAsPurchased();
    }

    @Transactional
    public void acceptCoffeechatTime(Long requestTimeId) {
        // 1. requestTime 객체 찾기
        RequestTime requestTime = requestTimeRepository.findById(requestTimeId)
                .orElseThrow(() -> new BusinessException(ErrorCode.REQUEST_TIME_NOT_FOUND));

        // 2. 커피챗 ID로 커피챗 객체 찾기
        Coffeechat coffeechat = coffeechatRepository.findById(requestTime.getCoffeechatId())
                .orElseThrow(() -> new BusinessException(ErrorCode.COFFEECHAT_NOT_FOUND));

        // 3. 커피챗 객체 update하기
        coffeechat.confirmSchedule(requestTime.getStartTime());

        // 4. 채팅방 열기
        Long mentorUserId = mentorService.getUserIdByMentorId(coffeechat.getMentorId());
        UserDTO mentor = userService.getUserByUserId(mentorUserId);
        UserDTO mentee = userService.getUserByUserId(coffeechat.getMenteeId());
        CoffeeLetterRoomDTO roomDto = CoffeeLetterRoomDTO.builder()
                .coffeeChatId(coffeechat.getCoffeechatId())
                .mentorId(coffeechat.getMentorId())
                .mentorName(mentor.getNickname())
                .menteeId(coffeechat.getMenteeId())
                .menteeName(mentee.getNickname())
                .createdAt(requestTime.getStartTime())
                .endTime(requestTime.getEndTime())
                .status(CoffeeLetterRoom.RoomStatus.ACTIVE)
                .build();
        roomService.createRoom(roomDto);

        // 5. 해당 coffeechatId에 대한 requestTime 객체 리스트 찾기
        List<RequestTime> requests = requestTimeRepository.findAllByCoffeechatId(coffeechat.getCoffeechatId());

        // 6. 해당 객체들 삭제
        requests.forEach(req -> requestTimeRepository.deleteById(req.getRequestTimeId()));

        // 7. 멘티에게 승인 알림 보내주기
        notificationCommandService.sendNotification(
                new NotificationSendRequest(
                        coffeechat.getMenteeId()
                        , 4L
                        , coffeechat.getCoffeechatId()
                        , "커피챗 요청이 승인되었습니다."
                )
        );
    }

    @Transactional
    public void rejectCoffeechatTime(Long coffeechatId) {
        // 1. 커피챗 ID로 커피챗 객체 찾기
        Coffeechat coffeechat = coffeechatRepository.findById(coffeechatId)
                .orElseThrow(() -> new BusinessException(ErrorCode.COFFEECHAT_NOT_FOUND));

        // 2. 커피챗 객체 update하기
        coffeechat.rejectSchedule();

        // 3. 해당 coffeechatId에 대한 requestTime 객체 리스트 찾기
        List<RequestTime> requests = requestTimeRepository.findAllByCoffeechatId(coffeechatId);

        // 4. 해당 객체들 삭제
        requests.forEach(req -> requestTimeRepository.deleteById(req.getRequestTimeId()));

        // 5. 멘토에게 거절 알림 보내주기
        notificationCommandService.sendNotification(
                new NotificationSendRequest(
                        coffeechat.getMenteeId()
                        , 5L
                        , coffeechat.getCoffeechatId()
                        , "커피챗 요청이 거절되었습니다."
                )
        );
    }

    @Transactional
    public void closeCoffeechat(Long coffeechatId) {
        // 1. 커피챗 ID로 커피챗 객체 찾기
        Coffeechat coffeechat = coffeechatRepository.findById(coffeechatId)
                .orElseThrow(() -> new BusinessException(ErrorCode.COFFEECHAT_NOT_FOUND));

        // 2. 커피챗 객체 update하기
        coffeechat.closeSchedule();
    }

    @Transactional
    public void confirmPurchaseCoffeechat(Long coffeechatId) {
        // 1. 커피챗 ID로 커피챗 객체 찾기
        Coffeechat coffeechat = coffeechatRepository.findById(coffeechatId)
                .orElseThrow(() -> new BusinessException(ErrorCode.COFFEECHAT_NOT_FOUND));

        // 2. 커피챗 객체 update하기
        coffeechat.confirmPurchaseSchedule();

        // 3. 멘토ID로 멘토 객체 가져오기
        MentorDTO mentorDTO = mentorService.getMentorInfo(coffeechat.getMentorId());

        // 4. 정산내역에 추가하기
        int totalQuantity = mentorDTO.getPrice() * coffeechat.getPurchaseQuantity();
        transactionCommandService.addSaleHistory(coffeechat.getMentorId(), totalQuantity, coffeechatId);
    }

    @Transactional
    public void cancelCoffeechat(Long userId, CoffeechatCancelRequest coffeechatCancelRequest) {
        // 1. 커피챗 ID로 커피챗 객체 찾기
        Coffeechat coffeechat = coffeechatRepository.findById(coffeechatCancelRequest.getCoffeechatId())
                .orElseThrow(() -> new BusinessException(ErrorCode.COFFEECHAT_NOT_FOUND));

        // 2. userId가 멘티Id인지 확인하기
        if(!coffeechat.getMenteeId().equals(userId)) {
            throw new BusinessException(ErrorCode.COFFEECHAT_CANCEL_NOT_ALLOWED);
        }

        // 3. 커피챗이 CANCEL 상태이거나, COMPLETE 상태이면 에러
        switch (coffeechat.getProgressStatus()){
            case CANCEL, COMPLETE -> throw new BusinessException(ErrorCode.INVALID_COFFEECHAT_STATUS_CANCEL);
        }

        // 4. 커피챗이 coffeechat_waiting 상태이면 환불 진행하기
        if(coffeechat.getProgressStatus().equals(ProgressStatus.COFFEECHAT_WAITING)) {
            MentorDTO mentorDTO = mentorService.getMentorInfo(coffeechat.getMentorId());
            int totalQuantity = mentorDTO.getPrice() * coffeechat.getPurchaseQuantity();
            transactionCommandService.refundCoffeeChat(coffeechatCancelRequest.getCoffeechatId(), userId, totalQuantity);
        }

        // 5. 커피챗 객체 업데이트하기
        coffeechat.cancelCoffeechat(coffeechatCancelRequest.getCancelReasonId());

        // 6. 채팅방 취소
        String roomId = roomService.findRoomIdByCoffeeChatId(coffeechat.getCoffeechatId());
        roomService.cancelRoom(roomId);

        // 7. 멘토에게 커피챗 취소 알림
        notificationCommandService.sendNotification(
                new NotificationSendRequest(
                        mentorService.getUserIdByMentorId(coffeechat.getMentorId())
                        , 6L
                        , coffeechat.getCoffeechatId()
                        , "진행 예정인 커피챗이 취소되었습니다."
                )
        );
    }
}