diff --git a/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ApplicationException.java b/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ApplicationException.java new file mode 100644 index 0000000..08179cf --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ApplicationException.java @@ -0,0 +1,26 @@ +package com.capgemini.training.appointmentbooking.common.exception; + +import lombok.Getter; + +import java.util.UUID; + +@Getter +public abstract class ApplicationException extends RuntimeException { + + private final String uniqueId; + + protected ApplicationException(String message) { + super(message); + this.uniqueId = UUID.randomUUID().toString(); + } + + protected ApplicationException(Throwable cause) { + super(cause); + this.uniqueId = UUID.randomUUID().toString(); + } + + protected ApplicationException(Throwable cause, String message) { + super(message, cause); + this.uniqueId = UUID.randomUUID().toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ConflictedAppointmentException.java b/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ConflictedAppointmentException.java index d0c2c82..5d8a056 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ConflictedAppointmentException.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/common/exception/ConflictedAppointmentException.java @@ -1,6 +1,6 @@ package com.capgemini.training.appointmentbooking.common.exception; -public class ConflictedAppointmentException extends RuntimeException { +public class ConflictedAppointmentException extends ApplicationException { public ConflictedAppointmentException() { super("The appointment conflicts with another scheduled appointment."); diff --git a/src/main/java/com/capgemini/training/appointmentbooking/common/exception/EntityNotFoundException.java b/src/main/java/com/capgemini/training/appointmentbooking/common/exception/EntityNotFoundException.java new file mode 100644 index 0000000..8be568a --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/common/exception/EntityNotFoundException.java @@ -0,0 +1,7 @@ +package com.capgemini.training.appointmentbooking.common.exception; + +public class EntityNotFoundException extends ApplicationException { + public EntityNotFoundException(String entityName, Long id) { + super(entityName + " not found for ID=" + id); + } +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/common/to/AppointmentEto.java b/src/main/java/com/capgemini/training/appointmentbooking/common/to/AppointmentEto.java index db9fa0a..58949bc 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/common/to/AppointmentEto.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/common/to/AppointmentEto.java @@ -7,5 +7,6 @@ import java.time.Instant; @Builder -public record AppointmentEto(@NotNull Long id, @NotNull Instant dateTime, @NotNull AppointmentStatus status) { +public record AppointmentEto(@NotNull Long id, @NotNull Instant dateTime, @NotNull Instant endsAt, + @NotNull AppointmentStatus status) { } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUc.java b/src/main/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUc.java index 86e2c0b..66d2f46 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUc.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUc.java @@ -16,6 +16,7 @@ public interface FindAppointmentUc { List findAll(); - boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime); + boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime, + @NotNull Instant endDateTime); } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImpl.java index ccfea0b..79185ba 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImpl.java @@ -47,8 +47,10 @@ public List findByCriteria(AppointmentCriteria criteria) { } @Override - public boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime) { - return appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(specialistId, dateTime, null); + public boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime, + @NotNull Instant endDateTime) { + return appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(specialistId, dateTime, + endDateTime); } } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImpl.java index b12dcb0..788f282 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImpl.java @@ -2,6 +2,7 @@ import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; import com.capgemini.training.appointmentbooking.common.exception.ConflictedAppointmentException; +import com.capgemini.training.appointmentbooking.common.exception.EntityNotFoundException; import com.capgemini.training.appointmentbooking.common.to.AppointmentBookingEto; import com.capgemini.training.appointmentbooking.common.to.AppointmentCto; import com.capgemini.training.appointmentbooking.common.to.AppointmentEto; @@ -15,13 +16,15 @@ import com.capgemini.training.appointmentbooking.logic.ManageAppointmentUc; import com.capgemini.training.appointmentbooking.logic.mapper.AppointmentCtoMapper; import com.capgemini.training.appointmentbooking.logic.mapper.AppointmentMapper; -import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + @Service @Transactional @Validated @@ -52,44 +55,40 @@ public ManageAppointmentUcImpl(FindAppointmentUc findAppointmentUc, AppointmentR @Override public AppointmentCto bookAppointment(@Valid AppointmentBookingEto appointmentBookingEto) { + ClientEntity clientEntity = clientRepository.findById(appointmentBookingEto.clientId()) + .orElseThrow(() -> new EntityNotFoundException("Client", appointmentBookingEto.clientId())); + TreatmentEntity treatmentEntity = treatmentRepository.findById(appointmentBookingEto.treatmentId()) + .orElseThrow(() -> new EntityNotFoundException("Treatment", appointmentBookingEto.clientId())); + + Instant endDate = appointmentBookingEto.dateTime().plus(treatmentEntity.getDurationMinutes(), + ChronoUnit.MINUTES); boolean hasConflict = findAppointmentUc.hasConflictingAppointment(appointmentBookingEto.specialistId(), - appointmentBookingEto.dateTime()); + appointmentBookingEto.dateTime(), endDate); if (hasConflict) { throw new ConflictedAppointmentException(); } - AppointmentEntity appointmentEntity = buildAppointmentEntity(appointmentBookingEto); - appointmentEntity = appointmentRepository.saveAndFlush(appointmentEntity); + AppointmentEntity entity = new AppointmentEntity(); + entity.setClient(clientEntity); + entity.setTreatment(treatmentEntity); + entity.setDateTime(appointmentBookingEto.dateTime()); + entity.setEndsAt(endDate); + entity.setStatus(AppointmentStatus.SCHEDULED); - return appointmentCtoMapper.toCto(appointmentEntity); + entity = appointmentRepository.save(entity); + return appointmentCtoMapper.toCto(entity); } @Override public AppointmentEto updateAppointmentStatus(@NotNull Long appointmentId, @NotNull AppointmentStatus appointmentStatus) { - AppointmentEntity appointmentEntity = findAppointmentUc.findById(appointmentId) - .map(appointmentCtoMapper::toEntity) - .orElseThrow(() -> new EntityNotFoundException("Appointment not found for ID: " + appointmentId)); + AppointmentEntity appointmentEntity = appointmentRepository.findById(appointmentId) + .orElseThrow(() -> new EntityNotFoundException("Appointment", appointmentId)); appointmentEntity.setStatus(appointmentStatus); - appointmentEntity = appointmentRepository.saveAndFlush(appointmentEntity); return appointmentMapper.toEto(appointmentEntity); } - private AppointmentEntity buildAppointmentEntity(@Valid AppointmentBookingEto appointmentBookingEto) { - AppointmentEntity entity = new AppointmentEntity(); - - ClientEntity clientEntity = clientRepository.getReferenceById(appointmentBookingEto.clientId()); - TreatmentEntity treatmentEntity = treatmentRepository.getReferenceById(appointmentBookingEto.treatmentId()); - - entity.setClient(clientEntity); - entity.setTreatment(treatmentEntity); - entity.setDateTime(appointmentBookingEto.dateTime()); - entity.setStatus(AppointmentStatus.SCHEDULED); - - return entity; - } - } \ No newline at end of file diff --git a/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageTreatmentUcImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageTreatmentUcImpl.java index efb2174..3387f6d 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageTreatmentUcImpl.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/logic/impl/ManageTreatmentUcImpl.java @@ -1,5 +1,6 @@ package com.capgemini.training.appointmentbooking.logic.impl; +import com.capgemini.training.appointmentbooking.common.exception.EntityNotFoundException; import com.capgemini.training.appointmentbooking.common.to.TreatmentCreationTo; import com.capgemini.training.appointmentbooking.common.to.TreatmentCto; import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity; @@ -34,14 +35,15 @@ public ManageTreatmentUcImpl(TreatmentRepository treatmentRepository, Specialist @Override public TreatmentCto createTreatment(@Valid TreatmentCreationTo treatmentCreationTo) { TreatmentEntity treatmentEntity = buildTreatmentEntity(treatmentCreationTo); - treatmentEntity = treatmentRepository.saveAndFlush(treatmentEntity); + treatmentEntity = treatmentRepository.save(treatmentEntity); return treatmentCtoMapper.toCto(treatmentEntity); } private TreatmentEntity buildTreatmentEntity(@Valid TreatmentCreationTo treatmentCreationTo) { TreatmentEntity entity = new TreatmentEntity(); - SpecialistEntity specialistEntity = specialistRepository.getReferenceById(treatmentCreationTo.specialistId()); + SpecialistEntity specialistEntity = specialistRepository.findById(treatmentCreationTo.specialistId()) + .orElseThrow(() -> new EntityNotFoundException("Specialist", treatmentCreationTo.specialistId())); entity.setName(treatmentCreationTo.name()); entity.setDescription(treatmentCreationTo.description()); diff --git a/src/main/java/com/capgemini/training/appointmentbooking/logic/mapper/AppointmentCtoMapper.java b/src/main/java/com/capgemini/training/appointmentbooking/logic/mapper/AppointmentCtoMapper.java index 8eba177..88d84c7 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/logic/mapper/AppointmentCtoMapper.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/logic/mapper/AppointmentCtoMapper.java @@ -11,6 +11,7 @@ public interface AppointmentCtoMapper { @Mapping(source = "appointmentEto.id", target = "id") @Mapping(source = "appointmentEto.dateTime", target = "dateTime") @Mapping(source = "appointmentEto.status", target = "status") + @Mapping(source = "appointmentEto.endsAt", target = "endsAt") @Mapping(source = "clientEto", target = "client") @Mapping(source = "treatmentCto", target = "treatment") @Mapping(target = "version", ignore = true) diff --git a/src/test/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUcTestIT.java b/src/test/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUcTestIT.java index d32a163..f35ba76 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUcTestIT.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/logic/FindAppointmentUcTestIT.java @@ -10,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; @@ -64,9 +65,11 @@ void shouldFindConflictedAppointment() { // given Long specialistId = -1L; Instant dateTime = toInstant("2024-03-01 09:00:00"); + Instant endDateTime = dateTime.plus(15, ChronoUnit.MINUTES); // when - boolean hasConflictingAppointment = findAppointmentUc.hasConflictingAppointment(specialistId, dateTime); + boolean hasConflictingAppointment = findAppointmentUc.hasConflictingAppointment(specialistId, dateTime, + endDateTime); // then assertThat(hasConflictingAppointment).isTrue(); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImplTest.java b/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImplTest.java index 21d415b..8f8c0a7 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImplTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/FindAppointmentUcImplTest.java @@ -17,6 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; @@ -102,10 +103,13 @@ void shouldFindConflictedAppointment() { // given Long specialistId = -1L; Instant dateTime = toInstant("2024-03-01 09:00:00"); - when(appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(any(), any(), any())).thenReturn(true); + Instant endDateTime = dateTime.plus(15, ChronoUnit.MINUTES); + when(appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(any(), any(), any())) + .thenReturn(true); // when - boolean hasConflictingAppointment = findAppointmentUc.hasConflictingAppointment(specialistId, dateTime); + boolean hasConflictingAppointment = findAppointmentUc.hasConflictingAppointment(specialistId, dateTime, + endDateTime); // then assertThat(hasConflictingAppointment).isTrue(); diff --git a/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImplTest.java b/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImplTest.java index 27ced9b..b22e284 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImplTest.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/logic/impl/ManageAppointmentUcImplTest.java @@ -23,6 +23,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Optional; import static org.mockito.ArgumentMatchers.any; @@ -71,7 +72,11 @@ void shouldBookAppointment() { Long specialistId = -2L; Long treatmentId = -3L; Instant dateTime = toInstant("2024-03-03 14:00:00"); + int treatmentDuration = 15; + Instant endDateTime = dateTime.plus(treatmentDuration, ChronoUnit.MINUTES); AppointmentStatus status = AppointmentStatus.SCHEDULED; + TreatmentEntity treatment = new TreatmentEntity(); + treatment.setDurationMinutes(treatmentDuration); AppointmentBookingEto appointmentBookingEto = AppointmentBookingEto.builder().clientId(clientId) .specialistId(specialistId).treatmentId(treatmentId).dateTime(dateTime).build(); @@ -79,11 +84,10 @@ void shouldBookAppointment() { AppointmentEntity appointmentEntity = buildAppointmentEntity(clientId, specialistId, treatmentId, dateTime, status); - when(findAppointmentUc.hasConflictingAppointment(specialistId, dateTime)).thenReturn(false); - when(clientRepository.getReferenceById(appointmentBookingEto.clientId())).thenReturn(new ClientEntity()); - when(treatmentRepository.getReferenceById(appointmentBookingEto.treatmentId())) - .thenReturn((new TreatmentEntity())); - when(appointmentRepository.saveAndFlush(any())).thenReturn(appointmentEntity); + when(findAppointmentUc.hasConflictingAppointment(specialistId, dateTime, endDateTime)).thenReturn(false); + when(clientRepository.findById(appointmentBookingEto.clientId())).thenReturn(Optional.of(new ClientEntity())); + when(treatmentRepository.findById(appointmentBookingEto.treatmentId())).thenReturn(Optional.of(treatment)); + when(appointmentRepository.save(any())).thenReturn(appointmentEntity); // when AppointmentCto result = manageAppointmentUc.bookAppointment(appointmentBookingEto); @@ -101,11 +105,17 @@ void shouldThrowAnExceptionDuringAppointmentBooking() { // given Long specialistId = -3L; Instant dateTime = toInstant("2024-03-03 14:00:00"); + int treatmentDuration = 15; + Instant endDateTime = dateTime.plus(treatmentDuration, ChronoUnit.MINUTES); + TreatmentEntity treatment = new TreatmentEntity(); + treatment.setDurationMinutes(treatmentDuration); AppointmentBookingEto appointmentBookingEto = AppointmentBookingEto.builder().specialistId(specialistId) .dateTime(dateTime).build(); - when(findAppointmentUc.hasConflictingAppointment(specialistId, dateTime)).thenReturn(true); + when(findAppointmentUc.hasConflictingAppointment(specialistId, dateTime, endDateTime)).thenReturn(true); + when(clientRepository.findById(appointmentBookingEto.clientId())).thenReturn(Optional.of(new ClientEntity())); + when(treatmentRepository.findById(appointmentBookingEto.treatmentId())).thenReturn(Optional.of(treatment)); // when & then assertThatThrownBy(() -> manageAppointmentUc.bookAppointment(appointmentBookingEto)) @@ -134,11 +144,10 @@ private static AppointmentEntity buildAppointmentEntity(Long clientId, Long spec void shouldUpdateAppointmentStatus() { // given Long appointmentId = -1L; - AppointmentStatus status = AppointmentStatus.COMPLETED; - when(findAppointmentUc.findById(appointmentId)).thenReturn(Optional.of(AppointmentCto.builder().build())); AppointmentEntity appointmentEntity = new AppointmentEntity(); + AppointmentStatus status = AppointmentStatus.COMPLETED; appointmentEntity.setStatus(status); - when(appointmentRepository.saveAndFlush(any())).thenReturn(appointmentEntity); + when(appointmentRepository.findById(appointmentId)).thenReturn(Optional.of(appointmentEntity)); // when AppointmentEto result = manageAppointmentUc.updateAppointmentStatus(appointmentId, status);