Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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.");
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface FindAppointmentUc {

List<AppointmentCto> findAll();

boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime);
boolean hasConflictingAppointment(@NotNull Long specialistId, @NotNull Instant dateTime,
@NotNull Instant endDateTime);

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ public List<AppointmentCto> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,19 +72,22 @@ 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();

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);
Expand All @@ -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))
Expand Down Expand Up @@ -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);
Expand Down
Loading