Skip to content

Commit

Permalink
Merge pull request #290 from ShallWeProject/develop
Browse files Browse the repository at this point in the history
feat: 알림톡 기능 적용
  • Loading branch information
sejineer authored Mar 5, 2024
2 parents c3b834e + 8afe9df commit 994ee0a
Show file tree
Hide file tree
Showing 24 changed files with 591 additions and 197 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ out/

/src/main/resources/application-dev.yml
/src/main/resources/application-local.yml
/src/main/resources/application-prod.yml
/src/main/resources/application-prod.yml
shallwe-master.pem
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.shallwe.domain.auth.dto.request;

import com.shallwe.domain.auth.dto.MessageMapping;
import com.shallwe.global.infrastructure.sms.dto.MessageMapping;
import lombok.Builder;
import lombok.Data;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ExperienceGiftServiceImpl implements ExperienceGiftService {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.shallwe.domain.common.Status;
import com.shallwe.domain.experiencegift.domain.ExperienceGift;
import com.shallwe.domain.shopowner.domain.ShopOwner;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

Expand All @@ -13,7 +14,12 @@
public interface ExperienceGiftRepository extends JpaRepository<ExperienceGift, Long>, ExperienceGiftQuerydslRepository{

List<ExperienceGift> findByTitleContainsAndStatus(String title,Status status);

List<ExperienceGift> findByShopOwnerIdAndStatus(Long id, Status status);

Optional<ExperienceGift> findByIdAndShopOwner(Long experienceGiftId, ShopOwner shopOwner);

@EntityGraph(attributePaths = {"shopOwner"})
Optional<ExperienceGift> findExperienceGiftById(Long id);

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.shallwe.domain.reservation.application;

import com.shallwe.domain.reservation.dto.response.DeleteReservationRes;
import com.shallwe.domain.reservation.dto.request.OwnerReservationCreate;
import com.shallwe.domain.reservation.dto.response.ReservationResponse;
import com.shallwe.domain.reservation.dto.request.UserReservationCreate;
Expand All @@ -12,8 +11,8 @@
public interface ReservationManipulationService {

List<ReservationResponse> addOwnerReservation(OwnerReservationCreate ownerReservationCreate, UserPrincipal userPrincipal);
ReservationResponse addUserReservation(UserReservationCreate reservationRequest, UserPrincipal userPrincipal);
ReservationResponse updateReservation(UpdateReservationReq updateReq, UserPrincipal userPrincipal);
DeleteReservationRes deleteReservation(Long id);
ReservationResponse addUserReservation(UserReservationCreate reservationRequest, UserPrincipal userPrincipal) throws Exception;
ReservationResponse updateReservation(UpdateReservationReq updateReq, UserPrincipal userPrincipal) throws Exception;
void cancelReservation(UserPrincipal userPrincipal, Long reservationId) throws Exception;

}
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
package com.shallwe.domain.reservation.application;

import static com.shallwe.domain.reservation.domain.ReservationStatus.*;

import static com.shallwe.domain.reservation.domain.ReservationStatus.WAITING;

import com.shallwe.domain.common.Status;
import com.shallwe.domain.experiencegift.domain.ExperienceGift;
import com.shallwe.domain.experiencegift.domain.repository.ExperienceGiftRepository;
import com.shallwe.domain.experiencegift.exception.ExperienceGiftNotFoundException;
import com.shallwe.domain.reservation.domain.Reservation;
import com.shallwe.domain.reservation.domain.ReservationStatus;
import com.shallwe.domain.reservation.domain.repository.ReservationRepository;
import com.shallwe.domain.reservation.dto.response.DeleteReservationRes;
import com.shallwe.domain.reservation.dto.request.OwnerReservationCreate;
import com.shallwe.domain.reservation.dto.response.ReservationResponse;
import com.shallwe.domain.reservation.dto.request.UserReservationCreate;
import com.shallwe.domain.reservation.dto.request.UpdateReservationReq;
import com.shallwe.domain.reservation.exception.InvalidReservationException;
import com.shallwe.domain.reservation.exception.InvalidReservationUpdateException;
import com.shallwe.domain.reservation.exception.InvalidSenderException;
import com.shallwe.domain.reservation.exception.InvalidReceiverException;
import com.shallwe.domain.shopowner.domain.ShopOwner;
import com.shallwe.domain.shopowner.domain.repository.ShopOwnerRepository;
import com.shallwe.domain.shopowner.exception.InvalidShopOwnerException;
import com.shallwe.domain.reservation.exception.*;

import com.shallwe.domain.user.domain.User;
import com.shallwe.domain.user.domain.repository.UserRepository;
import com.shallwe.domain.user.exception.InvalidUserException;
import com.shallwe.global.config.security.token.UserPrincipal;

import java.util.List;
import java.util.stream.Collectors;

import com.shallwe.global.infrastructure.sms.NaverSmsClient;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -38,67 +40,119 @@ public class ReservationManipulationServiceImpl implements ReservationManipulati

private final ReservationRepository reservationRepository;
private final ExperienceGiftRepository experienceGiftRepository;
private final ShopOwnerRepository shopOwnerRepository;
private final UserRepository userRepository;
private final NaverSmsClient naverSmsClient;

@Override
@Transactional
public List<ReservationResponse> addOwnerReservation(OwnerReservationCreate ownerReservationCreate, UserPrincipal userPrincipal) {
ExperienceGift experienceGift = experienceGiftRepository.findById(ownerReservationCreate.getExperienceGiftId())
public List<ReservationResponse> addOwnerReservation(
OwnerReservationCreate ownerReservationCreate, UserPrincipal userPrincipal) {
ExperienceGift experienceGift = experienceGiftRepository.findById(
ownerReservationCreate.getExperienceGiftId())
.orElseThrow(ExperienceGiftNotFoundException::new);

ShopOwner owner = shopOwnerRepository.findById(userPrincipal.getId())
.orElseThrow(InvalidShopOwnerException::new);

List<Reservation> reservations = OwnerReservationCreate.toEntityForOwner(ownerReservationCreate, experienceGift, owner);
List<Reservation> reservations = OwnerReservationCreate.toEntityForOwner(ownerReservationCreate,
experienceGift);
return reservations.stream()
.map(reservationRepository::save)
.map(ReservationResponse::toDtoOwner)
.collect(Collectors.toList());
}

@Override
@Transactional
public ReservationResponse addUserReservation(UserReservationCreate reservationRequest, UserPrincipal userPrincipal) {
public ReservationResponse addUserReservation(UserReservationCreate reservationRequest,
UserPrincipal userPrincipal) throws Exception {
User sender = userRepository.findById(userPrincipal.getId())
.orElseThrow(InvalidSenderException::new);

User receiver = userRepository.findByPhoneNumberAndStatus(reservationRequest.getPhoneNumber(), Status.ACTIVE)
User receiver = userRepository.findByPhoneNumberAndStatus(reservationRequest.getPhoneNumber(),
Status.ACTIVE)
.orElseThrow(InvalidReceiverException::new);

ExperienceGift experienceGift = experienceGiftRepository.findById(reservationRequest.getExperienceGiftId())
ExperienceGift experienceGift = experienceGiftRepository.findExperienceGiftById(
reservationRequest.getExperienceGiftId())
.orElseThrow(ExperienceGiftNotFoundException::new);

Reservation reservation = reservationRepository.findByDateAndTimeAndExperienceGift(
Reservation reservation = reservationRepository.findByDateAndTimeAndExperienceGiftWithPessimisticLock(
reservationRequest.getDate(), reservationRequest.getTime(), experienceGift)
.orElseThrow(InvalidReservationException::new);

if (reservation.getReservationStatus().equals(WAITING)) {
reservation.updateStatus(ReservationStatus.BOOKED);
reservation.updateStatus(BOOKED);
reservation.updateUserReservationRequest(reservationRequest, sender, receiver);
naverSmsClient.sendApply(receiver, experienceGift, reservation);
experienceGift.addReservationCount();
} else {
throw new InvalidReservationException();
}
return ReservationResponse.toDtoUser(reservation);
}

@Override
@Transactional
public ReservationResponse updateReservation(UpdateReservationReq updateReq, UserPrincipal userPrincipal) {
Reservation updateReservation = reservationRepository.findById(
updateReq.getReservationId()).map(
reservation -> {
reservation.updateReservation(updateReq);
return reservationRepository.save(reservation);
}
).orElseThrow(InvalidReservationException::new);
return ReservationResponse.toDtoUser(updateReservation);
public ReservationResponse updateReservation(UpdateReservationReq updateReq, UserPrincipal userPrincipal) throws Exception {
User user = userRepository.findById(userPrincipal.getId())
.orElseThrow(InvalidUserException::new);
//현재 예약 검색
Reservation currentReservation = reservationRepository.findReservationById(updateReq.getReservationId())
.orElseThrow(InvalidReservationException::new);

if(!currentReservation.getSender().getId().equals(user.getId()))
throw new UserReservationMismatchException();

//변경 가능한 예약 검색
Reservation newReservation = reservationRepository.findByDateAndTimeAndExperienceGift(
updateReq.getDate(), updateReq.getTime(),
currentReservation.getExperienceGift()
).orElseThrow(InvalidReservationUpdateException::new);

//DTO 생성
UserReservationCreate reservationCreateDto = UserReservationCreate.builder()
.persons(currentReservation.getPersons())
.experienceGiftId(currentReservation.getExperienceGift().getId())
.invitationComment(currentReservation.getInvitationComment())
.phoneNumber(currentReservation.getPhoneNumber())
.imageUrl(currentReservation.getInvitationImg())
.build();

//기존 예약 정보를 현재 예약 정보로 이동
newReservation.updateUserReservationRequest(reservationCreateDto,
currentReservation.getSender(), currentReservation.getReceiver());
newReservation.updateStatus(BOOKED);
Reservation updated = reservationRepository.save(newReservation);

//기존 예약 정보 상태 초기화
currentReservation.clearReservation();
currentReservation.updateStatus(WAITING);
reservationRepository.save(currentReservation);

naverSmsClient.sendChange(newReservation);
return ReservationResponse.toDtoUser(updated);
}

@Override
@Transactional
public DeleteReservationRes deleteReservation(Long id) {
Reservation reservation = reservationRepository.findById(id)
public void cancelReservation(final UserPrincipal userPrincipal, final Long reservationId) throws Exception {
Reservation reservation = reservationRepository.findReservationById(reservationId)
.orElseThrow(InvalidReservationException::new);
reservation.updateStatus(Status.DELETE);
return DeleteReservationRes.toDTO();

User user = userRepository.findById(userPrincipal.getId())
.orElseThrow(InvalidUserException::new);

if (!reservation.getSender().getId().equals(user.getId()))
throw new UserReservationMismatchException();

if (reservation.getReservationStatus().equals(BOOKED)) { // BOOKED 상태일 때 WAITING으로 변경
reservation.updateStatus(WAITING);
naverSmsClient.sendCancel(reservation);
reservation.clearReservation();
} else if (reservation.getReservationStatus().equals(CONFIRMED)) { // CONFIRMED 상태일 때 CANCELLED로 변경
reservation.updateStatus(CANCELLED);
naverSmsClient.sendCancel(reservation);
} else { // 예약 상태가 BOOKED나 CONFIRMED가 아닐 경우
throw new NotAvailableReservationStatusException();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ public class Reservation extends BaseEntity {
@Schema(description = "선물 ID")
private ExperienceGift experienceGift;

@ManyToOne
@JoinColumn(name = "owner_id", nullable = false)
@Schema(description = "사장ID")
private ShopOwner owner;


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sender_id")
@Schema(description = "보내는이 ID")
Expand Down Expand Up @@ -82,9 +76,8 @@ public class Reservation extends BaseEntity {
private List<MemoryPhoto> memoryPhotos = new ArrayList<>();

@Builder
public Reservation(ExperienceGift experienceGift, ShopOwner owner, LocalDate date, LocalTime time, ReservationStatus reservationStatus) {
public Reservation(ExperienceGift experienceGift, LocalDate date, LocalTime time, ReservationStatus reservationStatus) {
this.experienceGift = experienceGift;
this.owner = owner;
this.date = date;
this.time = time;
this.reservationStatus = reservationStatus;
Expand All @@ -107,4 +100,15 @@ public void updateUserReservationRequest(UserReservationCreate reservationReques
public void updateStatus(ReservationStatus status) {
this.reservationStatus = status;
}


public void clearReservation() {
this.sender = null;
this.receiver = null;
this.phoneNumber = null;
this.invitationComment = null;
this.persons = null;
this.invitationImg = null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import com.shallwe.domain.reservation.domain.ReservationStatus;
import com.shallwe.domain.shopowner.domain.ShopOwner;
import com.shallwe.domain.user.domain.User;
import jakarta.persistence.LockModeType;
import java.time.LocalDate;
import java.time.LocalTime;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
Expand All @@ -35,4 +37,13 @@ public interface ReservationRepository extends JpaRepository<Reservation, Long>,

Optional<List<Reservation>> findAllByExperienceGiftAndDate(ExperienceGift experienceGift, LocalDate date);


@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select r from Reservation r where r.date = :date and r.time = :time and r.experienceGift=:experienceGift")
Optional<Reservation> findByDateAndTimeAndExperienceGiftWithPessimisticLock(LocalDate date, LocalTime time, ExperienceGift experienceGift);

@EntityGraph(attributePaths = {"experienceGift", "experienceGift.shopOwner", "sender", "receiver"})
Optional<Reservation> findReservationById(Long reservationId);


}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class OwnerReservationCreate {
private Map<LocalDate, List<LocalTime>> dateTimeMap;

public static List<Reservation> toEntityForOwner(OwnerReservationCreate ownerReservationCreate,
ExperienceGift experienceGift, ShopOwner owner) {
ExperienceGift experienceGift) {
List<Reservation> reservations = new ArrayList<>();

for (Map.Entry<LocalDate, List<LocalTime>> entry : ownerReservationCreate.getDateTimeMap()
Expand All @@ -42,7 +42,6 @@ public static List<Reservation> toEntityForOwner(OwnerReservationCreate ownerRes
.experienceGift(experienceGift)
.date(date)
.time(time)
.owner(owner)
.reservationStatus(WAITING)
.build();
reservations.add(toEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@Data
public class UpdateReservationReq {

@Schema(type = "long", example = "2", description = "예약 날짜")
@Schema(type = "long", example = "2", description = "바꾸기 전 예약 ID")
private Long reservationId;

@Schema(type = "string", example = "2023-08-25", description = "예약 날짜")
Expand Down
Loading

0 comments on commit 994ee0a

Please sign in to comment.