diff --git a/.gitattributes b/.gitattributes
index 3b41682..44153cd 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,9 @@
/mvnw text eol=lf
*.cmd text eol=crlf
+* text eol=lf
+*.bat eol=crlf
+*.htm text diff=html
+*.html text diff=html
+*.xhtml text diff=html
+*.java text diff=java
+*.css text diff=css
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2b97fb0..9694756 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,29 +55,30 @@
org.projectlombok
lombok
- true
+ provided
org.springframework.boot
spring-boot-starter-test
test
+
+ com.querydsl
+ querydsl-apt
+ 5.0.0
+ jakarta
+ provided
+
+
+ com.querydsl
+ querydsl-jpa
+ jakarta
+ 5.0.0
+
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
org.springframework.boot
spring-boot-maven-plugin
@@ -90,6 +91,66 @@
+
+ org.flywaydb
+ flyway-maven-plugin
+
+ sa
+ password
+ jdbc:h2:mem:todoapp
+ false
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.2
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.2
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ 3.5.2
+
+
+ verify
+
+ report-only
+
+
+
+
+ true
+
+
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.mysema.query.apt.jpa.JPAAnnotationProcessor
+
+
+
+
org.apache.maven.plugins
maven-surefire-plugin
@@ -144,5 +205,4 @@
-
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/config/DataaccessConfiguration.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/config/DataaccessConfiguration.java
new file mode 100644
index 0000000..429151b
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/config/DataaccessConfiguration.java
@@ -0,0 +1,10 @@
+package com.capgemini.training.appointmentbooking.dataaccess.config;
+
+import com.capgemini.training.appointmentbooking.dataaccess.repository.impl.BaseJpaRepositoryImpl;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@Configuration
+@EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class, basePackages = "com.capgemini.training.appointmentbooking.dataaccess.repository")
+public class DataaccessConfiguration {
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java
index 28e888f..12eeb0c 100644
--- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java
@@ -1,6 +1,7 @@
package com.capgemini.training.appointmentbooking.dataaccess.entity;
import java.time.Instant;
+import java.util.Objects;
import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
@@ -38,7 +39,30 @@ public class AppointmentEntity extends BaseEntity {
@Column(name = "DATE_TIME")
private Instant dateTime;
+ @Column(name = "END_DATE_TIME")
+ private Instant endsAt;
+
@Enumerated(EnumType.STRING)
private AppointmentStatus status;
+ @Override
+ public void prePersist() {
+ super.prePersist();
+ validateDates();
+ }
+
+ @Override
+ public void preUpdate() {
+ super.preUpdate();
+ validateDates();
+ }
+
+ private void validateDates() {
+ Objects.requireNonNull(this.dateTime);
+ Objects.requireNonNull(this.endsAt);
+ if (!this.endsAt.isAfter(this.dateTime)) {
+ throw new IllegalStateException(
+ String.format("Starting date: %s must be before end date: %s", this.dateTime, this.endsAt));
+ }
+ }
}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java
index 63f8ead..4281a83 100644
--- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java
@@ -1,28 +1,18 @@
package com.capgemini.training.appointmentbooking.dataaccess.entity;
-import java.util.List;
-
import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
import com.capgemini.training.appointmentbooking.dataaccess.converter.SpecializationConverter;
-
-import jakarta.persistence.CascadeType;
-import jakarta.persistence.Convert;
-import jakarta.persistence.Entity;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.OneToMany;
-import jakarta.persistence.OneToOne;
-import jakarta.persistence.SequenceGenerator;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
+import java.util.List;
+
@Entity
@Table(name = "SPECIALIST")
@Getter
@Setter
+@NamedQuery(name = "SpecialistEntity.findBySpecialization", query = "select s from SpecialistEntity s where specialization =:specialization")
public class SpecialistEntity extends BaseEntity {
@Id
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java
index 25239cd..0cc936c 100644
--- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java
@@ -7,6 +7,7 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
+import jakarta.persistence.NamedQuery;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import lombok.Getter;
@@ -16,6 +17,7 @@
@Table(name = "TREATMENT")
@Getter
@Setter
+@NamedQuery(name = "TreatmentEntity.findByNameNamedQuery", query = "select t from TreatmentEntity t where name =:name")
public class TreatmentEntity extends BaseEntity {
@Id
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java
new file mode 100644
index 0000000..03541a8
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java
@@ -0,0 +1,86 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria;
+import jakarta.persistence.criteria.*;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Repository
+public interface AppointmentRepository extends BaseJpaRepository {
+
+ default List findByCriteria(AppointmentCriteria criteria) {
+ Objects.requireNonNull(criteria, "criteria must not be null");
+
+ CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
+ CriteriaQuery cq = cb.createQuery(AppointmentEntity.class);
+ Root root = cq.from(AppointmentEntity.class);
+ List predicates = new ArrayList<>();
+
+ if (criteria.treatmentName() != null && !criteria.treatmentName().trim().isEmpty()) {
+ Join treatmentJoin = root.join("treatment", JoinType.LEFT);
+ predicates.add(cb.like(cb.lower(treatmentJoin.get("name")),
+ "%" + criteria.treatmentName().trim().toLowerCase() + "%"));
+ }
+
+ if (criteria.startDate() != null) {
+ predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), criteria.startDate()));
+ }
+
+ if (criteria.endDate() != null) {
+ predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), criteria.endDate()));
+ }
+
+ if (criteria.status() != null) {
+ predicates.add(cb.equal(root.get("status"), criteria.status()));
+ }
+
+ if (criteria.clientId() != null) {
+ Join clientJoin = root.join("client", JoinType.LEFT);
+ predicates.add(cb.equal(clientJoin.get("id"), criteria.clientId()));
+ }
+
+ if (criteria.specialistId() != null) {
+ Join treatmentJoin = root.join("treatment", JoinType.LEFT);
+ Join specialistJoin = treatmentJoin.join("specialist", JoinType.LEFT);
+ predicates.add(cb.equal(specialistJoin.get("id"), criteria.specialistId()));
+ }
+
+ cq.where(predicates.toArray(new Predicate[0]));
+ return getEntityManager().createQuery(cq).getResultList();
+ }
+
+ List findByDateTimeBetweenAndStatus(Instant start, Instant end, AppointmentStatus status);
+
+ @Query("""
+ SELECT a FROM AppointmentEntity a
+ JOIN a.treatment t
+ WHERE t.specialist.id = :specialistId
+ AND a.dateTime < :date
+ ORDER BY a.dateTime DESC
+ """)
+ List findAppointmentsBySpecialistIdBeforeDate(@Param("specialistId") Long specialistId,
+ @Param("date") Instant date);
+
+ @Query("""
+ SELECT CASE WHEN COUNT(a) > 0 THEN TRUE ELSE FALSE END
+ FROM AppointmentEntity a
+ JOIN a.treatment t
+ WHERE t.specialist.id = :specialistId
+ AND a.dateTime = :date
+ AND a.status <> com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus.CANCELLED
+ """)
+ boolean hasConflictingAppointmentBySpecialistIdAndDateTime(@Param("specialistId") Long specialistId,
+ @Param("date") Instant date);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java
new file mode 100644
index 0000000..9acca13
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java
@@ -0,0 +1,12 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import jakarta.persistence.EntityManager;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.NoRepositoryBean;
+
+@NoRepositoryBean
+public interface BaseJpaRepository extends JpaRepository {
+
+ EntityManager getEntityManager();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java
new file mode 100644
index 0000000..550b5b3
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java
@@ -0,0 +1,24 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ClientRepository extends BaseJpaRepository {
+
+ default List findByName(String firstName, String lastName) {
+ JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());
+
+ QClientEntity client = QClientEntity.clientEntity;
+ QUserEntity user = QUserEntity.userEntity;
+
+ return queryFactory.selectFrom(client).leftJoin(client.user, user)
+ .where(user.firstname.eq(firstName).and(user.lastname.eq(lastName))).fetch();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java
new file mode 100644
index 0000000..bd0efb7
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java
@@ -0,0 +1,31 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QSpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface SpecialistRepository
+ extends
+ BaseJpaRepository,
+ QuerydslPredicateExecutor {
+
+ List findBySpecialization(Specialization specialization);
+
+ default List findSpecialistByName(String firstName, String lastName) {
+ JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());
+
+ QSpecialistEntity specialist = QSpecialistEntity.specialistEntity;
+ QUserEntity user = QUserEntity.userEntity;
+
+ return queryFactory.selectFrom(specialist).leftJoin(specialist.user, user)
+ .where(user.firstname.eq(firstName).and(user.lastname.eq(lastName))).fetch();
+ }
+
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java
new file mode 100644
index 0000000..9911a91
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java
@@ -0,0 +1,43 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QSpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QTreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.jpa.impl.JPAQuery;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Objects;
+
+@Repository
+public interface TreatmentRepository extends BaseJpaRepository {
+
+ List findAllByNameStartingWithIgnoringCase(String name);
+
+ List findByNameNamedQuery(String name);
+
+ default List findByCriteria(TreatmentCriteria treatmentCriteria) {
+ Objects.requireNonNull(treatmentCriteria, "treatmentCriteria cannot be null");
+
+ QTreatmentEntity treatment = QTreatmentEntity.treatmentEntity;
+ QSpecialistEntity specialist = QSpecialistEntity.specialistEntity;
+
+ JPAQuery query = new JPAQuery<>(getEntityManager());
+ BooleanExpression predicate = treatment.isNotNull();
+
+ if (treatmentCriteria.name() != null) {
+ predicate = predicate.and(treatment.name.like(treatmentCriteria.name()));
+ }
+
+ if (treatmentCriteria.specialization() != null) {
+ predicate = predicate.and(specialist.specialization.eq(treatmentCriteria.specialization()));
+ }
+
+ return query.select(treatment).from(treatment).leftJoin(treatment.specialist, specialist) // Join with
+ // specialist
+ .where(predicate).fetch();
+ }
+
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java
new file mode 100644
index 0000000..be8eeb6
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java
@@ -0,0 +1,43 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Root;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Repository
+public interface UserRepository extends BaseJpaRepository {
+ default List findByCriteria(UserCriteria criteria) {
+ Objects.requireNonNull(criteria, "criteria must not be null");
+
+ EntityManager entityManager = getEntityManager();
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery cq = cb.createQuery(UserEntity.class);
+ Root root = cq.from(UserEntity.class);
+ List predicates = new ArrayList<>();
+
+ if (criteria.firstName() != null) {
+ predicates.add(cb.like(root.get("firstname"), criteria.firstName()));
+ }
+
+ if (criteria.lastName() != null) {
+ predicates.add(cb.like(root.get("lastname"), criteria.lastName()));
+ }
+
+ if (criteria.email() != null) {
+ predicates.add(cb.like(root.get("email"), criteria.email()));
+ }
+
+ cq.where(predicates.toArray(new Predicate[0]));
+ return entityManager.createQuery(cq).getResultList();
+ }
+
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java
new file mode 100644
index 0000000..17ae7ef
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java
@@ -0,0 +1,11 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria;
+
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import lombok.Builder;
+
+import java.time.Instant;
+
+@Builder
+public record AppointmentCriteria(String treatmentName, Instant startDate, Instant endDate, AppointmentStatus status,
+ Long clientId, Long specialistId) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java
new file mode 100644
index 0000000..032a97f
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java
@@ -0,0 +1,8 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria;
+
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import lombok.Builder;
+
+@Builder
+public record TreatmentCriteria(String name, Specialization specialization) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java
new file mode 100644
index 0000000..cdd1638
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java
@@ -0,0 +1,7 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria;
+
+import lombok.Builder;
+
+@Builder
+public record UserCriteria(String firstName, String lastName, String email) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java
new file mode 100644
index 0000000..32a2270
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java
@@ -0,0 +1,22 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.impl;
+
+import com.capgemini.training.appointmentbooking.dataaccess.repository.BaseJpaRepository;
+import jakarta.persistence.EntityManager;
+import org.springframework.data.jpa.repository.support.JpaEntityInformation;
+import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
+
+public class BaseJpaRepositoryImpl extends SimpleJpaRepository implements BaseJpaRepository {
+
+ private final EntityManager entityManager;
+
+ BaseJpaRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
+ super(entityInformation, entityManager);
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public EntityManager getEntityManager() {
+ return this.entityManager;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index b4e3d67..141d23b 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -13,3 +13,7 @@ management.endpoints.web.exposure.include=*
spring.flyway.locations=classpath:db/migration
spring.flyway.enabled=true
spring.flyway.clean-on-validation-error=true
+
+spring.jpa.hibernate.ddl-auto=none
+
+spring.banner.location=classpath:/banner/jbf_banner.txt
diff --git a/src/main/resources/banner/jbf_banner.txt b/src/main/resources/banner/jbf_banner.txt
new file mode 100644
index 0000000..32241af
--- /dev/null
+++ b/src/main/resources/banner/jbf_banner.txt
@@ -0,0 +1,8 @@
+ ,--. ,-----. ,--. ,--. ,------. ,--. ,--. ,--.
+ | | ,--,--.,--. ,--.,--,--. | |) /_ ,--,--.,---.| |,-. ,---. ,--,--, ,-| | | .---',--.,--.,--,--, ,-| | ,--,--.,-' '-.`--' ,---. ,--,--,
+,--. | |' ,-. | \ `' /' ,-. | | .-. \' ,-. | .--'| /| .-. :| \' .-. | | `--, | || || \' .-. |' ,-. |'-. .-',--.| .-. || \
+| '-' /\ '-' | \ / \ '-' | | '--' /\ '-' \ `--.| \ \\ --.| || |\ `-' | | |` ' '' '| || |\ `-' |\ '-' | | | | |' '-' '| || |
+ `-----' `--`--' `--' `--`--' `------' `--`--'`---'`--'`--'`----'`--''--' `---' `--' `----' `--''--' `---' `--`--' `--' `--' `---' `--''--'
+
+${application.title} ${application.version}
+Powered by Spring Boot ${spring-boot.version}
\ No newline at end of file
diff --git a/src/main/resources/db/migration/1.0/V0001__Create_schema.sql b/src/main/resources/db/migration/1.0/V0001__Create_schema.sql
index cea91c1..b3ed7d7 100644
--- a/src/main/resources/db/migration/1.0/V0001__Create_schema.sql
+++ b/src/main/resources/db/migration/1.0/V0001__Create_schema.sql
@@ -49,6 +49,7 @@ CREATE TABLE APPOINTMENT (
ID NUMBER(19,0) NOT NULL,
VERSION INTEGER NOT NULL,
DATE_TIME TIMESTAMP NOT NULL,
+ END_DATE_TIME TIMESTAMP NOT NULL,
STATUS VARCHAR(128) NOT NULL DEFAULT 'SCHEDULED',
CLIENT_ID NUMBER(19,0) NOT NULL,
TREATMENT_ID NUMBER(19,0) NOT NULL,
@@ -69,6 +70,6 @@ CREATE SEQUENCE APPOINTMENT_SEQ START WITH 1 INCREMENT BY 100 NOCYCLE;
-- CREATING INDEX
CREATE INDEX IDX_CLIENT_USER ON CLIENT(USER_ID);
CREATE INDEX IDX_SPECIAL_USER ON SPECIALIST(USER_ID);
-CREATE INDEX IDX_TREATM_SPECIAL ON TREATMENT(SPECIALIST_ID);
+CREATE INDEX IDX_TREATM_SPECIAL ON TREATMENT(SPECIALIST_ID, ID);
CREATE INDEX IDX_APPOINT_CLIENT ON APPOINTMENT(CLIENT_ID);
-CREATE INDEX IDX_APPOINT_TREATM ON APPOINTMENT(TREATMENT_ID);
\ No newline at end of file
+CREATE INDEX IDX_APPOINT_TREATM ON APPOINTMENT(TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS);
\ No newline at end of file
diff --git a/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql b/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql
index 24c169f..ffe8296 100644
--- a/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql
+++ b/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql
@@ -37,23 +37,23 @@ INSERT INTO TREATMENT(ID, VERSION, NAME, DESCRIPTION, DURATION_MINUTES, SPECIALI
INSERT INTO TREATMENT(ID, VERSION, NAME, DESCRIPTION, DURATION_MINUTES, SPECIALIST_ID, CREATED, LAST_UPDATED) VALUES (-12, 0, 'Rekonstrukcja więzadła ACL', 'Rekonstrukcją więzadła krzyżowego przedniego (ACL) z zastąpieniem uszkodzonego więzadła nowym więzadłem ze ścięgien pacjenta.', 180, -4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-- APPOINTMENTS
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-1, 0, -1, -1, '2024-03-01 09:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-2, 0, -2, -3, '2024-03-02 10:30:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-3, 0, -3, -5, '2024-03-03 14:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-4, 0, -4, -10, '2024-03-04 08:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-5, 0, -1, -2, '2024-03-05 11:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-6, 0, -2, -4, '2024-03-06 16:30:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-7, 0, -3, -6, '2024-03-07 09:30:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-8, 0, -4, -11, '2024-03-08 13:45:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-9, 0, -1, -7, '2024-03-09 10:00:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-10, 0, -2, -8, '2024-03-10 12:30:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-11, 0, -3, -9, '2024-03-11 15:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-12, 0, -4, -12, '2024-03-12 17:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-13, 0, -1, -1, '2024-03-13 08:30:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-14, 0, -2, -3, '2024-03-14 11:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-15, 0, -3, -5, '2024-03-15 13:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-16, 0, -4, -10, '2024-03-16 09:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-17, 0, -1, -2, '2024-03-17 14:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-18, 0, -2, -4, '2024-03-18 16:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-19, 0, -3, -6, '2024-03-19 10:45:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-20, 0, -4, -11, '2024-03-20 12:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
\ No newline at end of file
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-1, 0, -1, -1, '2024-03-01 09:00:00', '2024-03-01 09:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-2, 0, -2, -3, '2024-03-02 10:30:00', '2024-03-02 10:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-3, 0, -3, -5, '2024-03-03 14:00:00', '2024-03-03 14:15:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-4, 0, -4, -10, '2024-03-04 08:15:00', '2024-03-04 08:30:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-5, 0, -1, -2, '2024-03-05 11:45:00', '2024-03-05 12:00:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-6, 0, -2, -4, '2024-03-06 16:30:00', '2024-03-06 16:45:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-7, 0, -3, -6, '2024-03-07 09:30:00', '2024-03-07 09:45:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-8, 0, -4, -11, '2024-03-08 13:45:00', '2024-03-08 14:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-9, 0, -1, -7, '2024-03-09 10:00:00', '2024-03-09 10:15:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-10, 0, -2, -8, '2024-03-10 12:30:00', '2024-03-10 12:45:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-11, 0, -3, -9, '2024-03-11 15:00:00', '2024-03-11 15:15:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-12, 0, -4, -12,'2024-03-12 17:15:00', '2024-03-12 17:30:00','SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-13, 0, -1, -1, '2024-03-13 08:30:00', '2024-03-13 08:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-14, 0, -2, -3, '2024-03-14 11:00:00', '2024-03-14 11:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-15, 0, -3, -5, '2024-03-15 13:00:00', '2024-03-15 13:15:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-16, 0, -4, -10,'2024-03-16 09:15:00', '2024-03-16 09:30:00','SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-17, 0, -1, -2, '2024-03-17 14:45:00', '2024-03-17 15:00:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-18, 0, -2, -4, '2024-03-18 16:00:00', '2024-03-18 16:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-19, 0, -3, -6, '2024-03-19 10:45:00', '2024-03-19 11:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-20, 0, -4, -11,'2024-03-20 12:15:00', '2024-03-20 12:30:00','SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
\ No newline at end of file
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseDataJpaTest.java b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseDataJpaTest.java
new file mode 100644
index 0000000..aebe70e
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseDataJpaTest.java
@@ -0,0 +1,10 @@
+package com.capgemini.training.appointmentbooking.common;
+
+import com.capgemini.training.appointmentbooking.dataaccess.config.DataaccessConfiguration;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.Import;
+
+@DataJpaTest
+@Import(DataaccessConfiguration.class)
+public class BaseDataJpaTest extends BaseTest {
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java
new file mode 100644
index 0000000..a6a0e2a
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java
@@ -0,0 +1,17 @@
+package com.capgemini.training.appointmentbooking.common;
+
+import org.assertj.core.api.WithAssertions;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+public class BaseTest implements WithAssertions {
+
+ protected Instant toInstant(String date) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ return LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()).toInstant();
+ }
+
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java
index 3b7a4a9..b6ad16f 100644
--- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java
@@ -10,8 +10,9 @@
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
+import org.springframework.data.repository.config.BootstrapMode;
-@DataJpaTest
+@DataJpaTest(bootstrapMode = BootstrapMode.LAZY)
public class EntitySmokeIT {
@PersistenceContext
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryIT.java
new file mode 100644
index 0000000..eaf926a
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryIT.java
@@ -0,0 +1,176 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest;
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+
+public class AppointmentRepositoryIT extends BaseDataJpaTest {
+
+ @Inject
+ private AppointmentRepository appointmentRepository;
+
+ @Test
+ void shouldFindAll() {
+ // given
+ // when
+ List result = appointmentRepository.findAll();
+
+ // then
+ assertThat(result).hasSize(20);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentName() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria.builder().treatmentName("Konsultacja pediatryczna").build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentNameAndStartDate() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria.builder().treatmentName("Konsultacja pediatryczna")
+ .startDate(toInstant("2024-03-03 14:00:00")).build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria.builder().treatmentName("Konsultacja pediatryczna")
+ .startDate(toInstant("2024-03-03 14:00:00")).endDate(toInstant("2024-03-03 15:00:00")).build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentNameAndAppointmentStatus() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria.builder().treatmentName("Konsultacja pediatryczna")
+ .status(AppointmentStatus.CANCELLED).build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ void shouldFindAppointmentsByClientId() {
+ // given
+ Long clientId = -1L;
+ AppointmentCriteria criteria = AppointmentCriteria.builder().clientId(clientId).build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(5);
+ assertThat(result).extracting(AppointmentEntity::getClient).extracting(ClientEntity::getId)
+ .containsOnly(clientId);
+ }
+
+ @Test
+ void shouldFindAppointmentsBySpecialistId() {
+ // given
+ Long specialistId = -1L;
+ AppointmentCriteria criteria = AppointmentCriteria.builder().specialistId(specialistId).build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(4);
+ assertThat(result).extracting(AppointmentEntity::getTreatment).extracting(TreatmentEntity::getSpecialist)
+ .extracting(SpecialistEntity::getId).containsOnly(specialistId);
+ }
+
+ @Test
+ void shouldFindAppointmentsByDateTimeBetweenAndStatus() {
+ // given
+ Instant startDate = toInstant("2024-03-10 00:00:00");
+ Instant endDate = toInstant("2024-03-14 23:59:59");
+ AppointmentStatus status = AppointmentStatus.SCHEDULED;
+
+ // when
+ List result = appointmentRepository.findByDateTimeBetweenAndStatus(startDate, endDate,
+ status);
+
+ // then
+ assertThat(result).hasSize(3);
+ assertThat(result).extracting(AppointmentEntity::getStatus).containsOnly(status);
+ }
+
+ @Test
+ void shouldFindAppointmentById() {
+ // given
+ Long appointmentId = -1L;
+
+ // when
+ Optional result = appointmentRepository.findById(appointmentId);
+
+ // then
+ assertThat(result).hasValueSatisfying(appointment -> {
+ assertThat(appointment.getId()).isEqualTo(appointmentId);
+ assertThat(appointment.getStatus()).isEqualTo(AppointmentStatus.SCHEDULED);
+ assertThat(appointment.getClient()).isNotNull();
+ assertThat(appointment.getTreatment()).isNotNull();
+ assertThat(appointment.getDateTime()).isEqualTo(toInstant("2024-03-01 09:00:00"));
+ });
+ }
+
+ @Test
+ void shouldFindAppointmentsBySpecialistIdBeforeDate() {
+ // given
+ Long specialistId = -1L;
+ Instant date = toInstant("2024-03-12 09:00:00");
+
+ // when
+ List appointments = appointmentRepository
+ .findAppointmentsBySpecialistIdBeforeDate(specialistId, date);
+
+ // then
+ assertThat(appointments).hasSize(2)
+ .allMatch(a -> a.getTreatment().getSpecialist().getId().equals(specialistId),
+ "All appointments belong to the correct specialist")
+ .allMatch(a -> a.getDateTime().isBefore(date), "All appointments should be before the given date");
+ }
+
+ @Test
+ void shouldFindConflictedAppointment() {
+ // given
+ Long specialistId = -1L;
+ Instant date = toInstant("2024-03-05 11:45:00");
+
+ // when
+ boolean conflict = appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(specialistId, date);
+
+ // then
+ assertThat(conflict).isTrue();
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryIT.java
new file mode 100644
index 0000000..2340b77
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryIT.java
@@ -0,0 +1,32 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class ClientRepositoryIT extends BaseDataJpaTest {
+
+ @Inject
+ private ClientRepository clientRepository;
+
+ @Test
+ void shouldFindClientsByName() {
+ // given
+ String firstName = "Stefan";
+ String lastName = "Kowalski";
+
+ // when
+ List clients = clientRepository.findByName(firstName, lastName);
+
+ // then
+ assertThat(clients).hasSize(1).first().satisfies(client -> {
+ assertThat(client.getUser().getFirstname()).isEqualTo(firstName)
+ .as("Expected client to have the specified first name");
+ assertThat(client.getUser().getLastname()).isEqualTo(lastName)
+ .as("Expected client to have the specified last name");
+ });
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryIT.java
new file mode 100644
index 0000000..7b88e04
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryIT.java
@@ -0,0 +1,46 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest;
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class SpecialistRepositoryIT extends BaseDataJpaTest {
+
+ @Inject
+ private SpecialistRepository specialistRepository;
+
+ @Test
+ void shouldFindSpecialistsBySpecialization() {
+ // given
+ Specialization specialization = Specialization.DENTIST;
+
+ // when
+ List specialists = specialistRepository.findBySpecialization(specialization);
+
+ // then
+ assertThat(specialists).hasSize(1).allMatch(specialist -> specialist.getSpecialization() == specialization,
+ "Specialists should all have specialization " + specialization);
+ }
+
+ @Test
+ void shouldFindSpecialistsByName() {
+ // given
+ String firstName = "Dobromir";
+ String lastName = "Zegula";
+
+ // when
+ List specialists = specialistRepository.findSpecialistByName(firstName, lastName);
+
+ // then
+ assertThat(specialists).hasSize(1).first().satisfies(specialist -> {
+ assertThat(specialist.getUser().getFirstname()).isEqualTo(firstName)
+ .as("Expected specialist to have the specified first name");
+ assertThat(specialist.getUser().getLastname()).isEqualTo(lastName)
+ .as("Expected specialist to have the specified last name");
+ });
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryIT.java
new file mode 100644
index 0000000..9d4f7bb
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryIT.java
@@ -0,0 +1,149 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest;
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+public class TreatmentRepositoryIT extends BaseDataJpaTest {
+
+ @Inject
+ private TreatmentRepository treatmentRepository;
+
+ @Inject
+ private SpecialistRepository specialistRepository;
+
+ @Test
+ void shouldFindTreatmentsByName() {
+ // given
+ String treatmentName = "konsUltacja de";
+
+ // when
+ List result = treatmentRepository.findAllByNameStartingWithIgnoringCase(treatmentName);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsByNameNamedQuery() {
+ // given
+ String treatmentName = "Konsultacja dentystyczna";
+
+ // when
+ List result = treatmentRepository.findByNameNamedQuery(treatmentName);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsByNameQueryDSL() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria.builder().name("Konsultacja kardiologiczna").build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsByNameAndSpecialization() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria.builder().name("Konsultacja dentystyczna")
+ .specialization(Specialization.DENTIST).build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsBySpecialization() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria.builder().specialization(Specialization.PEDIATRICIAN).build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(5);
+ }
+
+ @Test
+ void shouldFindAllTreatments() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria.builder().build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(12);
+ }
+
+ @Test
+ void shouldFindTreatmentById() {
+ // given
+ Long treatmentId = -1L;
+
+ // when
+ Optional result = treatmentRepository.findById(treatmentId);
+
+ // then
+ assertThat(result).hasValueSatisfying(treatment -> {
+ assertThat(treatment.getId()).isEqualTo(treatmentId);
+ assertThat(treatment.getName()).isEqualTo("Konsultacja dentystyczna");
+ assertThat(treatment.getDescription())
+ .isEqualTo("Konsultacja dentystyczna z diagnostyką i planem leczenia");
+ assertThat(treatment.getDurationMinutes()).isEqualTo(30);
+
+ SpecialistEntity specialist = treatment.getSpecialist();
+ assertThat(specialist.getId()).isEqualTo(-1L);
+ assertThat(specialist.getUser()).isNotNull();
+ assertThat(specialist.getSpecialization()).isEqualTo(Specialization.DENTIST);
+ assertThat(specialist.getTreatments()).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldSaveTreatment() {
+ // given
+ Long treatmentId = -1L;
+ Optional optionalSpecialist = specialistRepository.findById(treatmentId);
+
+ assertThat(optionalSpecialist).hasValueSatisfying(specialist -> {
+ TreatmentEntity treatmentEntity = new TreatmentEntity();
+ treatmentEntity.setName("Wypełnienie ubytku");
+ treatmentEntity.setDescription("Usunięcie próchnicy i wypełnienie zęba kompozytem");
+ treatmentEntity.setDurationMinutes(45);
+ treatmentEntity.setSpecialist(specialist);
+
+ // when
+ TreatmentEntity result = treatmentRepository.save(treatmentEntity);
+
+ // then
+ assertThat(result.getId()).isOne();
+ assertThat(result.getName()).isEqualTo("Wypełnienie ubytku");
+ assertThat(result.getDescription()).isEqualTo("Usunięcie próchnicy i wypełnienie zęba kompozytem");
+ assertThat(result.getDurationMinutes()).isEqualTo(45);
+
+ SpecialistEntity savedSpecialist = result.getSpecialist();
+ assertThat(savedSpecialist).isNotNull().extracting(SpecialistEntity::getId).isEqualTo(specialist.getId());
+
+ assertThat(savedSpecialist.getUser()).isNotNull();
+ assertThat(savedSpecialist.getSpecialization()).isEqualTo(Specialization.DENTIST);
+ assertThat(savedSpecialist.getTreatments()).isNotNull();
+ });
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryIT.java
new file mode 100644
index 0000000..3edbdb1
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryIT.java
@@ -0,0 +1,33 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class UserRepositoryIT extends BaseDataJpaTest {
+
+ @Inject
+ private UserRepository userRepository;
+
+ @Test
+ void shouldFindUsersByCriteria() {
+ // given
+ String firstName = "Stefan";
+ String lastName = "Kowalski";
+ String email = "stefan.kowalski@gmail.com";
+ UserCriteria userCriteria = UserCriteria.builder().firstName(firstName).lastName(lastName).email(email).build();
+
+ // when
+ List users = userRepository.findByCriteria(userCriteria);
+
+ // then
+ assertThat(users).hasSize(1).first().satisfies(user -> {
+ assertThat(user.getFirstname()).isEqualTo(firstName).as("Expected user to have the specified first name");
+ assertThat(user.getLastname()).isEqualTo(lastName).as("Expected user to have the specified last name");
+ });
+ }
+}