Creation of appointment implemented

This commit is contained in:
Matthias Engelien
2024-09-15 12:22:31 +02:00
parent a2164a0eb3
commit c311564ecc
7 changed files with 157 additions and 18 deletions

View File

@@ -11,6 +11,7 @@ import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
@@ -56,8 +57,8 @@ public class GarageApiController implements WerkstattApi {
long garageId = Long.parseLong(werkstattId);
Optional<Long> serviceId = NumberUtils.isParsable(leistungsId) ? Optional.of(Long.parseLong(leistungsId))
: Optional.empty();
Optional<Date> appointmentsFrom = Optional.ofNullable(parseLocalDateTime(von));
Optional<Date> appointmentsTill = Optional.ofNullable(parseLocalDateTime(bis));
Optional<Date> appointmentsFrom = Optional.ofNullable(parseDate(von));
Optional<Date> appointmentsTill = Optional.ofNullable(parseDate(bis));
log.info("Filter appointments by garage {}, serviceId {}, from {}, till {}", garageId, serviceId,
appointmentsFrom, appointmentsTill);
@@ -76,15 +77,31 @@ public class GarageApiController implements WerkstattApi {
@Override
public ResponseEntity<Termin> postTermin(String werkstattId, @Valid TerminRequest termin) {
// TODO Auto-generated method stub
return WerkstattApi.super.postTermin(werkstattId, termin);
// input validation
Assert.isTrue(NumberUtils.isParsable(werkstattId), "werkstattId ungültig");
Assert.isTrue(NumberUtils.isParsable(termin.getLeistungsId()), "leistungsId ungültig");
Assert.notNull(parseDate(termin.getVon()), "von ungültig");
Assert.notNull(parseDate(termin.getBis()), "bis ungültig");
long garageId = Long.parseLong(werkstattId);
long serviceId = Long.parseLong(termin.getLeistungsId());
Date appointmentFrom = parseDate(termin.getVon());
Date appointmentTill = parseDate(termin.getBis());
// create appointment if possible
Optional<Appointment> appointment = appointmentService.createAppointment(garageId, serviceId,
appointmentFrom, appointmentTill);
return appointment.map(a -> ResponseEntity.ok(AppointmentTerminMapper.toTermin(a)))
.orElse(ResponseEntity.status(HttpStatus.CONFLICT).build());
}
private Date parseLocalDateTime(String dateTimeString) {
private Date parseDate(String dateTimeString) {
if (StringUtils.isNotBlank(dateTimeString)) {
try {
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy hh:mm");
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm");
return format.parse(dateTimeString);
} catch (ParseException e) {

View File

@@ -1,8 +1,5 @@
package de.etecture.ga.dto.mapper;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import de.etecture.ga.dto.Termin;
@@ -16,17 +13,13 @@ public class AppointmentTerminMapper {
log.info("Mapping Object {}", appointment);
LocalDateTime appointmentStart = Instant.ofEpochMilli(appointment.appointmentTime().getTime())
.atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime appointmentEnd = appointmentStart.plus(appointment.duration());
return new Termin()
.id(Long.toString(appointment.id()))
.leistungsId(Long.toString(appointment.serviceId().getId()))
.leistung(appointment.serviceName())
.werkstattName("to_be_set")
.von(appointmentStart.format(DateTimeFormatter.ofPattern("dd.MM.yyyy hh:mm")))
.bis(appointmentEnd.format(DateTimeFormatter.ofPattern("dd.MM.yyyy hh:mm")));
.von(appointment.appointmentStart().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")))
.bis(appointment.appointmentEnd().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")));
}
}

View File

@@ -1,6 +1,9 @@
package de.etecture.ga.model;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import org.springframework.data.annotation.Id;
@@ -32,4 +35,14 @@ public class Appointment {
private Integer slot = 1;
private Duration duration = Duration.ZERO;
public LocalDateTime appointmentStart() {
return Instant.ofEpochMilli(this.appointmentTime().getTime())
.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public LocalDateTime appointmentEnd() {
return this.appointmentStart().plus(this.duration());
}
}

View File

@@ -1,13 +1,21 @@
package de.etecture.ga.service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.stereotype.Service;
import de.etecture.ga.model.Appointment;
import de.etecture.ga.model.Garage;
import de.etecture.ga.model.GarageServices;
import de.etecture.ga.model.MDService;
import de.etecture.ga.repository.AppointmentRepository;
import lombok.AllArgsConstructor;
@@ -17,6 +25,10 @@ public class AppointmentService {
private final AppointmentRepository repository;
private final GarageService garageService;
private final MDServiceService serviceService;
public Optional<Appointment> getAppointment(long appointmentId) {
return repository.findById(appointmentId);
@@ -47,4 +59,63 @@ public class AppointmentService {
return appointments.toList();
}
public Optional<Appointment> createAppointment(long garageId, long serviceId, Date from, Date till) {
Optional<Garage> garage = garageService.getGarage(garageId);
if (garage.isEmpty())
throw new IllegalArgumentException("GarageId not valid");
Optional<MDService> service = serviceService.getMDService(serviceId)
.filter(s -> garage.get().garageServices().stream().anyMatch(gs -> s.id() == gs.serviceId().getId()));
Optional<GarageServices> garageService = garage.get().garageServices().stream()
.filter(gs -> serviceId == gs.serviceId().getId()).findFirst();
if (service.isEmpty() || garageService.isEmpty())
throw new IllegalArgumentException("serviceId not valid");
LocalDateTime fromDateTime = Instant.ofEpochMilli(from.getTime()).atZone(ZoneId.systemDefault())
.toLocalDateTime();
LocalDateTime tillDateTime = Instant.ofEpochMilli(till.getTime()).atZone(ZoneId.systemDefault())
.toLocalDateTime();
LocalDateTime validAppointmentTime = null;
while (!fromDateTime.isAfter(tillDateTime)) {
if (isSlotAvailable(garage.get(), fromDateTime)) {
validAppointmentTime = fromDateTime;
break;
}
fromDateTime = fromDateTime.plusMinutes(15);
}
if (validAppointmentTime != null) {
Appointment appointment = new Appointment().garageId(AggregateReference.to(garage.get().id()))
.appointmentTime(Date.from(validAppointmentTime.atZone(ZoneId.systemDefault()).toInstant()))
.serviceId(AggregateReference.to(service.get().id())).serviceCode(service.get().code())
.serviceName(service.get().name()).duration(garageService.get().duration());
return Optional.of(repository.save(appointment));
} else {
return Optional.empty();
}
}
private boolean isSlotAvailable(Garage garage, LocalDateTime time) {
long appointments = garage.appointments().stream()
.filter(a -> (a.appointmentStart().isBefore(time) || a.appointmentStart().isEqual(time))
&& a.appointmentEnd().isAfter(time))
.count();
return appointments < garage.maxAppointments();
}
private LocalDateTime roundUpToQuarter(LocalDateTime datetime) {
if (datetime.getMinute() % 15 == 0)
return datetime;
int minutesToAdd = 15 - (datetime.getMinute() % 15);
return datetime.plusMinutes(minutesToAdd).truncatedTo(ChronoUnit.MINUTES);
}
}

View File

@@ -0,0 +1,21 @@
package de.etecture.ga.service;
import java.util.Optional;
import org.springframework.stereotype.Service;
import de.etecture.ga.model.Garage;
import de.etecture.ga.repository.GarageRepository;
import lombok.AllArgsConstructor;
@Service
@AllArgsConstructor
public class GarageService {
private final GarageRepository repository;
public Optional<Garage> getGarage(long id) {
return repository.findById(id);
}
}

View File

@@ -1,6 +1,7 @@
package de.etecture.ga.service;
import java.security.InvalidParameterException;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@@ -16,6 +17,11 @@ public class MDServiceService {
private final MDServiceRepository serviceRepository;
public Optional<MDService> getMDService(long id) {
return serviceRepository.findById(id);
}
public MDService storeMDService(String serviceCode) {
if (StringUtils.isBlank(serviceCode))

View File

@@ -1,18 +1,22 @@
package de.etecture.ga.api;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.fail;
import static org.hamcrest.Matchers.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.etecture.ga.dto.TerminRequest;
import de.etecture.ga.model.Appointment;
import de.etecture.ga.model.Garage;
import de.etecture.ga.repository.AppointmentRepository;
@@ -114,7 +118,21 @@ class GarageApiControllerTest {
}
@Test
void testPostTermin() {
fail("Not yet implemented");
void testPostTermin() throws Exception {
TerminRequest terminReq = new TerminRequest().leistungsId("3").von("02.01.2019 12:00").bis("02.01.2019 13:00");
ObjectMapper objectMapper = new ObjectMapper();
String terminJson = objectMapper.writeValueAsString(terminReq);
this.mockMvc
.perform(MockMvcRequestBuilders.post("/werkstatt/{werkstattId}/termin", testGarage.id())
.contentType(MediaType.APPLICATION_JSON).content(terminJson))
.andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").exists())
.andExpect(MockMvcResultMatchers.jsonPath("$.leistung").value("Radwechsel"))
.andExpect(MockMvcResultMatchers.jsonPath("$.leistungsId").value("3"))
.andExpect(MockMvcResultMatchers.jsonPath("$.von").value("02.01.2019 12:00"))
.andExpect(MockMvcResultMatchers.jsonPath("$.bis").value("02.01.2019 12:30"));
}
}