Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
88 changes: 74 additions & 14 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,30 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
<classifier>jakarta</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<classifier>jakarta</classifier>
<version>5.0.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand All @@ -90,6 +91,66 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<user>sa</user>
<password>password</password>
<url>jdbc:h2:mem:todoapp</url>
<cleanDisabled>false</cleanDisabled>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>report-only</goal>
</goals>
</execution>
</executions>
<configuration>
<aggregate>true</aggregate>
</configuration>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down Expand Up @@ -144,5 +205,4 @@
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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;

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

default List<AppointmentEntity> findByCriteria(AppointmentCriteria criteria) {
Objects.requireNonNull(criteria, "criteria must not be null");

CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<AppointmentEntity> cq = cb.createQuery(AppointmentEntity.class);
Root<AppointmentEntity> root = cq.from(AppointmentEntity.class);
List<Predicate> predicates = new ArrayList<>();

if (criteria.treatmentName() != null && !criteria.treatmentName().trim().isEmpty()) {
Join<AppointmentEntity, TreatmentEntity> 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<AppointmentEntity, ClientEntity> clientJoin = root.join("client", JoinType.LEFT);
predicates.add(cb.equal(clientJoin.get("id"), criteria.clientId()));
}

if (criteria.specialistId() != null) {
Join<AppointmentEntity, TreatmentEntity> treatmentJoin = root.join("treatment", JoinType.LEFT);
Join<TreatmentEntity, SpecialistEntity> 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<AppointmentEntity> 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<AppointmentEntity> 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);

}
Original file line number Diff line number Diff line change
@@ -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<T, ID> extends JpaRepository<T, ID> {

EntityManager getEntityManager();

}
Original file line number Diff line number Diff line change
@@ -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<ClientEntity, Long> {

default List<ClientEntity> 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();
}

}
Original file line number Diff line number Diff line change
@@ -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<SpecialistEntity, Long>,
QuerydslPredicateExecutor<SpecialistEntity> {

List<SpecialistEntity> findBySpecialization(Specialization specialization);

default List<SpecialistEntity> 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();
}

}
Loading
Loading