diff --git a/pom.xml b/pom.xml
index 21a3a2d..19720d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,10 @@
com.fasterxml.jackson.dataformat
jackson-dataformat-csv
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
org.springdoc
diff --git a/src/main/java/de/etecture/ga/api/GarageApiController.java b/src/main/java/de/etecture/ga/api/GarageApiController.java
index 6110bdc..42cbbea 100644
--- a/src/main/java/de/etecture/ga/api/GarageApiController.java
+++ b/src/main/java/de/etecture/ga/api/GarageApiController.java
@@ -3,31 +3,68 @@
*/
package de.etecture.ga.api;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.Assert;
import de.etecture.ga.dto.Termin;
import de.etecture.ga.dto.TerminRequest;
+import de.etecture.ga.dto.mapper.AppointmentTerminMapper;
+import de.etecture.ga.model.Appointment;
+import de.etecture.ga.service.AppointmentService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
/**
*
*/
+@AllArgsConstructor
+@Controller
+@Slf4j
public class GarageApiController implements WerkstattApi {
+ private final AppointmentService appointmentService;
+
@Override
- public ResponseEntity getTermin(String werkstattId, String terminId) {
- // TODO Auto-generated method stub
- return WerkstattApi.super.getTermin(werkstattId, terminId);
+ public ResponseEntity getTermin(@NotNull String werkstattId, @NotNull String terminId) {
+
+ Assert.isTrue(NumberUtils.isParsable(werkstattId), "werkstattId ungültig");
+ Assert.isTrue(NumberUtils.isParsable(terminId), "terminId ungültig");
+
+ Optional appointment = appointmentService.getAppointment(Long.parseLong(terminId),
+ Long.parseLong(werkstattId));
+
+ return ResponseEntity.of(appointment.map(AppointmentTerminMapper::toTermin));
}
@Override
- public ResponseEntity> getTermine(String werkstattId, @Valid String von, @Valid String bis,
+ public ResponseEntity> getTermine(@NotNull String werkstattId, @Valid String von, @Valid String bis,
@Valid String leistungsId) {
- // TODO Auto-generated method stub
- return WerkstattApi.super.getTermine(werkstattId, von, bis, leistungsId);
+
+ Assert.isTrue(NumberUtils.isParsable(werkstattId), "werkstattId ungültig");
+
+ long garageId = Long.parseLong(werkstattId);
+ Optional serviceId = NumberUtils.isParsable(leistungsId) ? Optional.of(Long.parseLong(leistungsId))
+ : Optional.empty();
+ Optional appointmentsFrom = Optional.ofNullable(parseLocalDateTime(von));
+ Optional appointmentsTill = Optional.ofNullable(parseLocalDateTime(bis));
+
+ log.info("Filter appointments by garage {}, serviceId {}, from {}, till {}", garageId, serviceId,
+ appointmentsFrom, appointmentsTill);
+
+ return ResponseEntity
+ .ok(appointmentService.getAppointments(garageId, serviceId, appointmentsFrom, appointmentsTill).stream()
+ .map(AppointmentTerminMapper::toTermin).toList());
}
@Override
@@ -43,4 +80,18 @@ public class GarageApiController implements WerkstattApi {
return WerkstattApi.super.postTermin(werkstattId, termin);
}
+ private Date parseLocalDateTime(String dateTimeString) {
+
+ if (StringUtils.isNotBlank(dateTimeString)) {
+ try {
+ SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy hh:mm");
+
+ return format.parse(dateTimeString);
+ } catch (ParseException e) {
+ log.error("Invalid data to parse", e);
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/de/etecture/ga/dto/mapper/AppointmentTerminMapper.java b/src/main/java/de/etecture/ga/dto/mapper/AppointmentTerminMapper.java
new file mode 100644
index 0000000..96e4f9c
--- /dev/null
+++ b/src/main/java/de/etecture/ga/dto/mapper/AppointmentTerminMapper.java
@@ -0,0 +1,32 @@
+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;
+import de.etecture.ga.model.Appointment;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class AppointmentTerminMapper {
+
+ public static Termin toTermin(Appointment appointment) {
+
+ 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")));
+
+ }
+}
diff --git a/src/main/java/de/etecture/ga/model/Appointment.java b/src/main/java/de/etecture/ga/model/Appointment.java
index 092a8f8..acece4b 100644
--- a/src/main/java/de/etecture/ga/model/Appointment.java
+++ b/src/main/java/de/etecture/ga/model/Appointment.java
@@ -20,6 +20,9 @@ public class Appointment {
@Column("GARAGE_ID")
private AggregateReference garageId;
+ @Column("SERVICE_ID")
+ private AggregateReference serviceId;
+
private String serviceCode;
private String serviceName;
diff --git a/src/main/java/de/etecture/ga/repository/AppointmentRepository.java b/src/main/java/de/etecture/ga/repository/AppointmentRepository.java
index cc4716e..84f6b58 100644
--- a/src/main/java/de/etecture/ga/repository/AppointmentRepository.java
+++ b/src/main/java/de/etecture/ga/repository/AppointmentRepository.java
@@ -1,9 +1,22 @@
package de.etecture.ga.repository;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import de.etecture.ga.model.Appointment;
public interface AppointmentRepository extends CrudRepository {
+ public Optional findByIdAndGarageId(long id, long garageId);
+
+ public List findByGarageId(long garageId);
+
+ @Query("select a.* from APPOINTMENT a "
+ + "join GARAGE g on g.ID = a.GARAGE_ID "
+ + "join MD_SERVICE s on s.ID = a.SERVICE_ID "
+ + "where s.CODE = :serviceCode and g.ID = :garageId ")
+ public List findByServiceCodeAndGarageId(String serviceCode, long garageId);
}
diff --git a/src/main/java/de/etecture/ga/service/AppointmentService.java b/src/main/java/de/etecture/ga/service/AppointmentService.java
new file mode 100644
index 0000000..8e52d04
--- /dev/null
+++ b/src/main/java/de/etecture/ga/service/AppointmentService.java
@@ -0,0 +1,50 @@
+package de.etecture.ga.service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.stereotype.Service;
+
+import de.etecture.ga.model.Appointment;
+import de.etecture.ga.repository.AppointmentRepository;
+import lombok.AllArgsConstructor;
+
+@Service
+@AllArgsConstructor
+public class AppointmentService {
+
+ private final AppointmentRepository repository;
+
+ public Optional getAppointment(long appointmentId) {
+
+ return repository.findById(appointmentId);
+
+ }
+
+ public Optional getAppointment(long appointmentId, long garageId) {
+
+ return repository.findByIdAndGarageId(appointmentId, garageId);
+
+ }
+
+ public List getAppointments(long garageId, Optional serviceId, Optional from,
+ Optional till) {
+
+ Stream appointments = repository.findByGarageId(garageId).stream();
+
+ if (serviceId.isPresent()) {
+ appointments = appointments.filter(a -> serviceId.get().equals(a.serviceId().getId()));
+ }
+ if (from.isPresent()) {
+ appointments = appointments
+ .filter(a -> a.appointmentTime().equals(from.get()) || a.appointmentTime().after(from.get()));
+ }
+ if (till.isPresent()) {
+ appointments = appointments.filter(a -> a.appointmentTime().before(till.get()));
+ }
+
+ return appointments.toList();
+ }
+}
diff --git a/src/main/java/de/etecture/ga/service/GarageImportService.java b/src/main/java/de/etecture/ga/service/GarageImportService.java
index 5d496c9..fc81380 100644
--- a/src/main/java/de/etecture/ga/service/GarageImportService.java
+++ b/src/main/java/de/etecture/ga/service/GarageImportService.java
@@ -15,9 +15,11 @@ import java.util.stream.Stream;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.stereotype.Service;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import de.etecture.ga.model.Appointment;
import de.etecture.ga.model.Garage;
@@ -88,6 +90,7 @@ public class GarageImportService {
try {
CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
CsvMapper mapper = new CsvMapper();
+
MappingIterator readValues = mapper.readerFor(CSVData.class).with(bootstrapSchema)
.readValues(file.toFile());
@@ -110,9 +113,9 @@ public class GarageImportService {
private Appointment getAppointmentForService(MDService service, Date date, Long garageId) {
return new Appointment().appointmentTime(date).serviceCode(service.code()).serviceName(service.name())
- .duration(service.duration()).garageId(AggregateReference.to(garageId));
+ .duration(service.duration()).garageId(AggregateReference.to(garageId)).serviceId(AggregateReference.to(service.id()));
}
- private record CSVData(Date APP_DATE, String SERVICE) {
+ private record CSVData(@JsonFormat(pattern="yyyy-MM-dd'T'HH:mm'Z'") Date APP_DATE, String SERVICE) {
}
}
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 594a810..c0b111a 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -8,18 +8,6 @@ CREATE TABLE GARAGE (
PRIMARY KEY (ID)
);
-CREATE TABLE APPOINTMENT (
- ID BIGINT NOT NULL AUTO_INCREMENT UNIQUE,
- GARAGE_ID BIGINT NOT NULL,
- SERVICE_CODE VARCHAR(5) DEFAULT NULL,
- SERVICE_NAME VARCHAR(50) DEFAULT NULL,
- APPOINTMENT_TIME DATE DEFAULT NULL,
- SLOT INT NOT NULL DEFAULT 1,
- DURATION BIGINT NOT NULL DEFAULT 0,
-
- PRIMARY KEY (ID),
- FOREIGN KEY (GARAGE_ID) REFERENCES GARAGE(ID)
-);
CREATE TABLE MD_SERVICE (
ID BIGINT NOT NULL AUTO_INCREMENT UNIQUE,
@@ -30,6 +18,7 @@ CREATE TABLE MD_SERVICE (
PRIMARY KEY (ID)
);
+
CREATE TABLE GARAGE_SERVICES (
GARAGE_ID BIGINT NOT NULL,
SERVICE_ID BIGINT NOT NULL,
@@ -39,4 +28,20 @@ CREATE TABLE GARAGE_SERVICES (
FOREIGN KEY (GARAGE_ID) REFERENCES GARAGE(ID),
FOREIGN KEY (SERVICE_ID) REFERENCES MD_SERVICE(ID)
+);
+
+
+CREATE TABLE APPOINTMENT (
+ ID BIGINT NOT NULL AUTO_INCREMENT UNIQUE,
+ GARAGE_ID BIGINT NOT NULL,
+ SERVICE_ID BIGINT NOT NULL,
+ SERVICE_CODE VARCHAR(5) DEFAULT NULL,
+ SERVICE_NAME VARCHAR(50) DEFAULT NULL,
+ APPOINTMENT_TIME DATE DEFAULT NULL,
+ SLOT INT NOT NULL DEFAULT 1,
+ DURATION BIGINT NOT NULL DEFAULT 0,
+
+ PRIMARY KEY (ID),
+ FOREIGN KEY (GARAGE_ID) REFERENCES GARAGE(ID),
+ FOREIGN KEY (SERVICE_ID) REFERENCES MD_SERVICE(ID)
);
\ No newline at end of file
diff --git a/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java b/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java
index 6b27d58..58988fd 100644
--- a/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java
+++ b/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java
@@ -1,18 +1,23 @@
package de.etecture.ga.api;
-import static org.junit.jupiter.api.Assertions.assertEquals;
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.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.MvcResult;
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 de.etecture.ga.model.Appointment;
+import de.etecture.ga.model.Garage;
+import de.etecture.ga.repository.AppointmentRepository;
+import de.etecture.ga.repository.GarageRepository;
+
/**
* Integrationtest for the {@link WerkstattApi}
*/
@@ -22,22 +27,85 @@ class GarageApiControllerTest {
@Autowired
private MockMvc mockMvc;
-
- @Test
- void testGetTermin() throws Exception {
-
- MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termin/{terminId}", "1", "1"))
- .andDo(MockMvcResultHandlers.print())
- .andExpect(MockMvcResultMatchers.status().isOk())
-// .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Hello World!!!"))
- .andReturn();
-
- assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType());
+
+ @Autowired
+ private GarageRepository garageRepository;
+
+ @Autowired
+ private AppointmentRepository appointmentRepository;
+
+ private Garage testGarage;
+
+ private Appointment testAppointment;
+
+ @BeforeEach
+ void setupData() {
+
+ testGarage = garageRepository.findByCode("test-data").get();
+ testAppointment = appointmentRepository.findByServiceCodeAndGarageId("WHE", testGarage.id()).get(0);
}
@Test
- void testGetTermine() {
- fail("Not yet implemented");
+ void testGetTermin() throws Exception {
+
+ this.mockMvc
+ .perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termin/{terminId}", testGarage.id(),
+ testAppointment.id()))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
+ .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"));
+
+ }
+
+ @Test
+ void testGetInvalidTermin() throws Exception {
+
+ this.mockMvc
+ .perform(
+ MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termin/{terminId}", testGarage.id(), "20"))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isNotFound());
+
+ this.mockMvc
+ .perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termin/{terminId}", 2,
+ testAppointment.id()))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isNotFound());
+
+ }
+
+ @Test
+ void testGetTermine() throws Exception {
+
+ this.mockMvc
+ .perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termine", testGarage.id()).param("thing",
+ "somewhere"))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$", hasSize(19)));
+ }
+
+ @Test
+ void testGetTermineWithFilter() throws Exception {
+
+ this.mockMvc
+ .perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termine", testGarage.id())
+ .param("leistungsId", "3"))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$", hasSize(7)));
+
+ this.mockMvc
+ .perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termine", testGarage.id())
+ .param("leistungsId", "3").param("von", "05.01.2019 12:00"))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$", hasSize(5)));
+
+ this.mockMvc
+ .perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termine", testGarage.id())
+ .param("leistungsId", "3").param("von", "05.01.2019 12:00").param("bis", "08.01.2019 12:00"))
+ .andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.jsonPath("$", hasSize(1)));
+
}
@Test
@@ -49,5 +117,4 @@ class GarageApiControllerTest {
void testPostTermin() {
fail("Not yet implemented");
}
-
}