diff --git a/client/src/api/SwaggerApi.ts b/client/src/api/SwaggerApi.ts index 9a0961a8..9cb4a1ca 100644 --- a/client/src/api/SwaggerApi.ts +++ b/client/src/api/SwaggerApi.ts @@ -90,7 +90,15 @@ export interface CreatePatientDTO { preIllnesses?: string[]; quarantineUntil?: string; riskAreas?: string[]; - riskOccupation?: "NO_RISK_OCCUPATION" | "FIRE_FIGHTER" | "DOCTOR" | "CAREGIVER" | "NURSE"; + riskOccupation?: + | "NO_RISK_OCCUPATION" + | "FIRE_FIGHTER_POLICE" + | "TEACHER" + | "PUBLIC_ADMINISTRATION" + | "STUDENT" + | "DOCTOR" + | "CAREGIVER" + | "NURSE"; speedOfSymptomsOutbreak?: string; stayCity?: string; stayCountry?: string; @@ -346,7 +354,15 @@ export interface Patient { preIllnesses?: string[]; quarantineUntil?: string; riskAreas?: string[]; - riskOccupation?: "NO_RISK_OCCUPATION" | "FIRE_FIGHTER" | "DOCTOR" | "CAREGIVER" | "NURSE"; + riskOccupation?: + | "NO_RISK_OCCUPATION" + | "FIRE_FIGHTER_POLICE" + | "TEACHER" + | "PUBLIC_ADMINISTRATION" + | "STUDENT" + | "DOCTOR" + | "CAREGIVER" + | "NURSE"; speedOfSymptomsOutbreak?: string; stayCity?: string; stayCountry?: string; @@ -1047,72 +1063,70 @@ export class Api extends HttpClient { error = { /** * @tags basic-error-controller - * @name errorUsingGET - * @summary error + * @name errorHtmlUsingGET + * @summary errorHtml * @request GET:/error * @secure */ - errorUsingGet: (params?: RequestParams) => - this.request, any>(`/error`, "GET", params, null, true), + errorHtmlUsingGet: (params?: RequestParams) => this.request(`/error`, "GET", params, null, true), /** * @tags basic-error-controller - * @name errorUsingHEAD - * @summary error + * @name errorHtmlUsingHEAD + * @summary errorHtml * @request HEAD:/error * @secure */ - errorUsingHead: (params?: RequestParams) => - this.request, any>(`/error`, "HEAD", params, null, true), + errorHtmlUsingHead: (params?: RequestParams) => + this.request(`/error`, "HEAD", params, null, true), /** * @tags basic-error-controller - * @name errorUsingPOST - * @summary error + * @name errorHtmlUsingPOST + * @summary errorHtml * @request POST:/error * @secure */ - errorUsingPost: (params?: RequestParams) => - this.request, any>(`/error`, "POST", params, null, true), + errorHtmlUsingPost: (params?: RequestParams) => + this.request(`/error`, "POST", params, null, true), /** * @tags basic-error-controller - * @name errorUsingPUT - * @summary error + * @name errorHtmlUsingPUT + * @summary errorHtml * @request PUT:/error * @secure */ - errorUsingPut: (params?: RequestParams) => - this.request, any>(`/error`, "PUT", params, null, true), + errorHtmlUsingPut: (params?: RequestParams) => this.request(`/error`, "PUT", params, null, true), /** * @tags basic-error-controller - * @name errorUsingDELETE - * @summary error + * @name errorHtmlUsingDELETE + * @summary errorHtml * @request DELETE:/error * @secure */ - errorUsingDelete: (params?: RequestParams) => - this.request, any>(`/error`, "DELETE", params, null, true), + errorHtmlUsingDelete: (params?: RequestParams) => + this.request(`/error`, "DELETE", params, null, true), /** * @tags basic-error-controller - * @name errorUsingOPTIONS - * @summary error + * @name errorHtmlUsingOPTIONS + * @summary errorHtml * @request OPTIONS:/error * @secure */ - errorUsingOptions: (params?: RequestParams) => - this.request, any>(`/error`, "OPTIONS", params, null, true), + errorHtmlUsingOptions: (params?: RequestParams) => + this.request(`/error`, "OPTIONS", params, null, true), /** * @tags basic-error-controller - * @name errorUsingPATCH - * @summary error + * @name errorHtmlUsingPATCH + * @summary errorHtml * @request PATCH:/error * @secure */ - errorUsingPatch: (params?: RequestParams) => - this.request, any>(`/error`, "PATCH", params, null, true), + errorHtmlUsingPatch: (params?: RequestParams) => + this.request(`/error`, "PATCH", params, null, true), }; } diff --git a/client/src/models/index.ts b/client/src/models/index.ts index e4601674..c1153fb7 100644 --- a/client/src/models/index.ts +++ b/client/src/models/index.ts @@ -22,7 +22,15 @@ export type PatientStatus = 'REGISTERED' | 'PATIENT_DEAD' | 'DOCTORS_VISIT' | 'QUARANTINE_MANDATED'; -export type RiskOccupation = 'NO_RISK_OCCUPATION' | 'FIRE_FIGHTER' | 'DOCTOR' | 'CAREGIVER' | 'NURSE' +export type RiskOccupation = + 'NO_RISK_OCCUPATION' + | 'FIRE_FIGHTER_POLICE' + | 'TEACHER' + | 'PUBLIC_ADMINISTRATION' + | 'STUDENT' + | 'DOCTOR' + | 'CAREGIVER' + | 'NURSE' export interface Option { label: string; diff --git a/client/src/models/risk-occupation.ts b/client/src/models/risk-occupation.ts index cea24e99..aa2c631d 100644 --- a/client/src/models/risk-occupation.ts +++ b/client/src/models/risk-occupation.ts @@ -6,7 +6,10 @@ export interface RiskOccupationOption { } export const RISK_OCCUPATIONS: RiskOccupationOption[] = [ - { value: 'FIRE_FIGHTER', label: 'Feuerwehrmann/frau' }, + { value: 'PUBLIC_ADMINISTRATION', label: 'Öffentliche Verwaltung' }, + { value: 'STUDENT', label: 'Schüler' }, + { value: 'TEACHER', label: 'Lehrer' }, + { value: 'FIRE_FIGHTER_POLICE', label: 'Gefahrenabwehr (Polizei, Feuerwehr usw.)' }, { value: 'DOCTOR', label: 'Arzt/Ärztin' }, { value: 'NURSE', label: 'Pflegepersonal' }, { value: 'CAREGIVER', label: 'Altenpflege' }, diff --git a/server/build.gradle b/server/build.gradle index 4e7968c8..742db96b 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -37,6 +37,7 @@ dependencies { implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'org.springframework.cloud:spring-cloud-gcp-starter-storage' implementation 'org.postgresql:postgresql:42.2.11' + compile group: 'org.hibernate', name: 'hibernate-envers', version: '5.4.14.Final' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.9' implementation 'io.springfox:springfox-swagger2:2.9.2' implementation 'io.springfox:springfox-swagger-ui:2.9.2' diff --git a/server/src/main/java/de/coronavirus/imis/api/DoctorController.java b/server/src/main/java/de/coronavirus/imis/api/DoctorController.java index 7a6ee6eb..ac36ebe5 100644 --- a/server/src/main/java/de/coronavirus/imis/api/DoctorController.java +++ b/server/src/main/java/de/coronavirus/imis/api/DoctorController.java @@ -2,6 +2,7 @@ import de.coronavirus.imis.api.dto.RequestLabTestDTO; import de.coronavirus.imis.domain.PatientEvent; +import de.coronavirus.imis.services.IncidentService; import de.coronavirus.imis.services.PatientEventService; import de.coronavirus.imis.services.PatientService; import lombok.RequiredArgsConstructor; @@ -18,10 +19,12 @@ public class DoctorController { private final PatientEventService eventService; private final PatientService patientService; + private final IncidentService incidentService; @PostMapping("/create_appointment") public PatientEvent addScheduledEvent(@RequestBody RequestLabTestDTO dto) { var patient = patientService.findPatientById(dto.getPatientId()).orElseThrow(); + incidentService.addIncident(patient, dto.getLaboratoryId(), dto.getDoctorId()); return eventService.createScheduledEvent(patient, dto.getLaboratoryId(), dto.getDoctorId()); } } diff --git a/server/src/main/java/de/coronavirus/imis/api/IncidentController.java b/server/src/main/java/de/coronavirus/imis/api/IncidentController.java new file mode 100644 index 00000000..4e6f81ac --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/api/IncidentController.java @@ -0,0 +1,59 @@ +package de.coronavirus.imis.api; + +/* + +To ease migration, incidents are currently automatically created when + - Creating Tests + - Updating Tests + - Sending to Quarantine + - Creating Patients (equivalent to Initial Patient Event) + - Scheduling appointments (implemented, not tested. Could not find frontend feature) + + */ + +import de.coronavirus.imis.domain.Incident; +import de.coronavirus.imis.domain.IncidentType; +import de.coronavirus.imis.services.IncidentService; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/incidents") +@AllArgsConstructor +public class IncidentController { + + private final IncidentService incidentService; + + @GetMapping("/{id}") + public Incident getIncident(@PathVariable("id") String incidentId) { + return incidentService.getCurrent(incidentId); + } + + @GetMapping("/{id}/log") + public List getLog(@PathVariable("id") String incidentId) { + return incidentService.getLog(incidentId, false); + } + + @GetMapping("/patient/{id}") + public List getPatientCurrent(@PathVariable("id") String patientId) { + return incidentService.getCurrentByPatient(patientId); + } + + @GetMapping("/{type}/patient/{id}") + public List getPatientCurrentByType(@PathVariable("type") IncidentType incidentType, @PathVariable("id") String patientId) { + return incidentService.getCurrentByPatient(patientId, incidentType); + } + + @GetMapping("/patient/{id}/log") + public List getPatientLog(@PathVariable("id") String patientId) { + return incidentService.getLog(patientId, true); + } + + @GetMapping("/{type}/patient/{id}/log") + public List getPatientLogByType(@PathVariable("type") IncidentType incidentType, @PathVariable("id") String patientId) { + return incidentService.getLog(incidentType.IMPLEMENTATION, patientId, true); + } + +} diff --git a/server/src/main/java/de/coronavirus/imis/api/LabTestController.java b/server/src/main/java/de/coronavirus/imis/api/LabTestController.java index a5406873..75933f20 100644 --- a/server/src/main/java/de/coronavirus/imis/api/LabTestController.java +++ b/server/src/main/java/de/coronavirus/imis/api/LabTestController.java @@ -3,6 +3,7 @@ import de.coronavirus.imis.api.dto.CreateLabTestDTO; import de.coronavirus.imis.api.dto.UpdateTestStatusDTO; import de.coronavirus.imis.domain.LabTest; +import de.coronavirus.imis.services.IncidentService; import de.coronavirus.imis.services.LabTestService; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; @@ -17,9 +18,11 @@ public class LabTestController { private final LabTestService service; + private final IncidentService incidentService; @PostMapping public ResponseEntity createTestForPatient(@RequestBody CreateLabTestDTO createLabTestRequest) { + incidentService.addIncident(createLabTestRequest); return ResponseEntity.ok( service.createLabTest(createLabTestRequest) ); @@ -37,6 +40,7 @@ public Set getLabTestForPatient(@PathVariable("id") String patientId) { @PutMapping("/{laboratoryId}") public ResponseEntity updateTestStatus(@PathVariable("laboratoryId") String laboratoryId, @RequestBody UpdateTestStatusDTO statusDTO) { + incidentService.updateIncident(laboratoryId, statusDTO); return ResponseEntity.ok(service.updateTestStatus( laboratoryId, statusDTO )); diff --git a/server/src/main/java/de/coronavirus/imis/api/PatientController.java b/server/src/main/java/de/coronavirus/imis/api/PatientController.java index 3611dfcb..19a8ea1e 100644 --- a/server/src/main/java/de/coronavirus/imis/api/PatientController.java +++ b/server/src/main/java/de/coronavirus/imis/api/PatientController.java @@ -6,6 +6,7 @@ import de.coronavirus.imis.api.dto.PatientSimpleSearchParamsDTO; import de.coronavirus.imis.api.dto.SendToQuarantineDTO; import de.coronavirus.imis.domain.Patient; +import de.coronavirus.imis.services.IncidentService; import de.coronavirus.imis.domain.PatientEvent; import de.coronavirus.imis.services.PatientEventService; import de.coronavirus.imis.services.PatientService; @@ -24,6 +25,7 @@ public class PatientController { private final PatientService patientService; + private final IncidentService incidentService; private final PatientEventService eventService; @PostMapping @@ -81,6 +83,7 @@ public Long countQueryPatients(@RequestBody final PatientSearchParamsDTO patient @PostMapping("/quarantine/{id}") @PreAuthorize("hasAnyRole('DEPARTMENT_OF_HEALTH')") public ResponseEntity sendToQuarantine(@PathVariable("id") String patientId, @RequestBody SendToQuarantineDTO statusDTO) { + incidentService.addOrUpdateIncident(patientId, statusDTO); return ResponseEntity.ok(patientService.sendToQuaratine(patientId, statusDTO)); } diff --git a/server/src/main/java/de/coronavirus/imis/api/dto/UpdateLabTestDTO.java b/server/src/main/java/de/coronavirus/imis/api/dto/UpdateLabTestDTO.java deleted file mode 100644 index 8aade629..00000000 --- a/server/src/main/java/de/coronavirus/imis/api/dto/UpdateLabTestDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.coronavirus.imis.api.dto; - -import de.coronavirus.imis.domain.TestStatus; -import lombok.Data; - -@Data -public class UpdateLabTestDTO { - private String id; - private String laboratoryId; - private String labInternalId; - private TestStatus testStatus; -} diff --git a/server/src/main/java/de/coronavirus/imis/config/AuditConfiguration.java b/server/src/main/java/de/coronavirus/imis/config/AuditConfiguration.java new file mode 100644 index 00000000..a4852397 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/config/AuditConfiguration.java @@ -0,0 +1,24 @@ +package de.coronavirus.imis.config; + +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.persistence.EntityManagerFactory; + + +@Configuration +public class AuditConfiguration { + + private final EntityManagerFactory entityManagerFactory; + + AuditConfiguration(EntityManagerFactory entityManagerFactory) { + this.entityManagerFactory = entityManagerFactory; + } + + @Bean + AuditReader auditReader() { + return AuditReaderFactory.get(entityManagerFactory.createEntityManager()); + } +} \ No newline at end of file diff --git a/server/src/main/java/de/coronavirus/imis/config/AuditorAwareImpl.java b/server/src/main/java/de/coronavirus/imis/config/AuditorAwareImpl.java new file mode 100644 index 00000000..4577d065 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/config/AuditorAwareImpl.java @@ -0,0 +1,25 @@ +package de.coronavirus.imis.config; + +import de.coronavirus.imis.config.domain.User; +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Optional; + +public class AuditorAwareImpl implements AuditorAware { + @Override + public Optional getCurrentAuditor() { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if(authentication != null && SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) + { + var user = (User) authentication.getPrincipal(); + Optional opt = Optional.of(user); + return opt; + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/server/src/main/java/de/coronavirus/imis/config/PersistanceConfig.java b/server/src/main/java/de/coronavirus/imis/config/PersistanceConfig.java new file mode 100644 index 00000000..8c1b9bd0 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/config/PersistanceConfig.java @@ -0,0 +1,27 @@ +package de.coronavirus.imis.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import de.coronavirus.imis.config.domain.User; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +@EnableJpaAuditing (auditorAwareRef = "auditorAware") +class PersistenceConfig { + + @Bean + public AuditorAware auditorAware() { + return new AuditorAwareImpl(); + } + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + } + +} \ No newline at end of file diff --git a/server/src/main/java/de/coronavirus/imis/config/SpringSecurityConfig.java b/server/src/main/java/de/coronavirus/imis/config/SpringSecurityConfig.java index 8cbf114d..39862e74 100644 --- a/server/src/main/java/de/coronavirus/imis/config/SpringSecurityConfig.java +++ b/server/src/main/java/de/coronavirus/imis/config/SpringSecurityConfig.java @@ -66,6 +66,7 @@ protected void configure(HttpSecurity http) throws Exception { .antMatchers(API_PREFIX + "/doctor/*").hasAnyRole(DEPARTMENT_OF_HEALTH, DOCTORS_OFFICE, CLINIC) .mvcMatchers(POST, API_PREFIX + "/labtest").hasAnyRole(DEPARTMENT_OF_HEALTH, DOCTORS_OFFICE, CLINIC, TEST_SITE) .antMatchers(API_PREFIX + "/labtest/patient/*").hasAnyRole(DEPARTMENT_OF_HEALTH, DOCTORS_OFFICE, CLINIC, TEST_SITE) + .antMatchers(API_PREFIX + "/incidents/*").hasAnyRole(DEPARTMENT_OF_HEALTH, DOCTORS_OFFICE, CLINIC, TEST_SITE) .mvcMatchers(PUT, API_PREFIX + "/labtest/*").hasAnyRole(DEPARTMENT_OF_HEALTH, LABORATORY) .antMatchers(API_PREFIX + "/stats").authenticated() .antMatchers(API_PREFIX + "/auth/register").hasAuthority(UserRole.USER_ROLE_ADMIN.name()) diff --git a/server/src/main/java/de/coronavirus/imis/domain/AdministrativeIncident.java b/server/src/main/java/de/coronavirus/imis/domain/AdministrativeIncident.java new file mode 100644 index 00000000..1121226d --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/domain/AdministrativeIncident.java @@ -0,0 +1,35 @@ +package de.coronavirus.imis.domain; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import java.time.LocalDate; + +import static org.hibernate.envers.RelationTargetAuditMode.NOT_AUDITED; + +@Entity +@Getter +@Setter +@Accessors(chain = true) +@Audited +public class AdministrativeIncident extends Incident { + + private Illness illness; + + private String comment; + + @Audited(targetAuditMode = NOT_AUDITED) + @ManyToOne + private Doctor responsibleDoctor; + + private LocalDate dateOfReporting; + + + public AdministrativeIncident() { + super(IncidentType.administrative); + } +} diff --git a/server/src/main/java/de/coronavirus/imis/domain/Incident.java b/server/src/main/java/de/coronavirus/imis/domain/Incident.java new file mode 100644 index 00000000..52d4345d --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/domain/Incident.java @@ -0,0 +1,54 @@ +package de.coronavirus.imis.domain; + +import de.coronavirus.imis.config.domain.User; +import de.coronavirus.imis.services.RandomService; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.envers.Audited; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; + +import static org.hibernate.envers.RelationTargetAuditMode.NOT_AUDITED; + +@Getter +@Setter +@Accessors(chain = true) +@EntityListeners(AuditingEntityListener.class) +@Audited +@MappedSuperclass +public abstract class Incident { + + protected Incident (IncidentType type) + { + id = type.toString() + "_" + UUID.randomUUID().toString().replace("-", ""); + } + + @Id + private String id; + + @Audited(targetAuditMode = NOT_AUDITED) + @ManyToOne + private Patient patient; + + private String caseId; + + @Enumerated(EnumType.STRING) + private EventType eventType; + + @LastModifiedDate + private LocalDateTime versionTimestamp; + + @Audited(targetAuditMode = NOT_AUDITED) + @ManyToOne + @LastModifiedBy + private User versionUser; +} diff --git a/server/src/main/java/de/coronavirus/imis/domain/IncidentType.java b/server/src/main/java/de/coronavirus/imis/domain/IncidentType.java new file mode 100644 index 00000000..fe81db28 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/domain/IncidentType.java @@ -0,0 +1,17 @@ +package de.coronavirus.imis.domain; + +public enum IncidentType { + + // lowercase to support direct casting from api route. + + test(TestIncident.class), + quarantine(QuarantineIncident.class), + administrative(AdministrativeIncident.class); + + public final Class IMPLEMENTATION; + + IncidentType(Class implementation) { + this.IMPLEMENTATION = implementation; + } + +} diff --git a/server/src/main/java/de/coronavirus/imis/domain/Presumtion.java b/server/src/main/java/de/coronavirus/imis/domain/Presumtion.java new file mode 100644 index 00000000..173f2291 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/domain/Presumtion.java @@ -0,0 +1,13 @@ +package de.coronavirus.imis.domain; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Presumtion { + + private Illness illness; + private String comment; + +} diff --git a/server/src/main/java/de/coronavirus/imis/domain/QuarantineIncident.java b/server/src/main/java/de/coronavirus/imis/domain/QuarantineIncident.java new file mode 100644 index 00000000..2e92ec23 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/domain/QuarantineIncident.java @@ -0,0 +1,27 @@ +package de.coronavirus.imis.domain; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; +import java.time.LocalDate; + + +@Entity +@Getter +@Setter +@Accessors(chain = true) +@Audited +public class QuarantineIncident extends Incident { + + private LocalDate until; + + private String comment; + + public QuarantineIncident() { + super(IncidentType.quarantine); + } + +} \ No newline at end of file diff --git a/server/src/main/java/de/coronavirus/imis/domain/RiskOccupation.java b/server/src/main/java/de/coronavirus/imis/domain/RiskOccupation.java index a658e53f..a59c7b6d 100644 --- a/server/src/main/java/de/coronavirus/imis/domain/RiskOccupation.java +++ b/server/src/main/java/de/coronavirus/imis/domain/RiskOccupation.java @@ -1,5 +1,5 @@ package de.coronavirus.imis.domain; public enum RiskOccupation { - NO_RISK_OCCUPATION, FIRE_FIGHTER, DOCTOR, CAREGIVER, NURSE + NO_RISK_OCCUPATION, FIRE_FIGHTER_POLICE, TEACHER, PUBLIC_ADMINISTRATION, STUDENT, DOCTOR, CAREGIVER, NURSE } diff --git a/server/src/main/java/de/coronavirus/imis/domain/TestIncident.java b/server/src/main/java/de/coronavirus/imis/domain/TestIncident.java new file mode 100644 index 00000000..acd65813 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/domain/TestIncident.java @@ -0,0 +1,46 @@ +package de.coronavirus.imis.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.envers.Audited; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.ManyToOne; + +import static org.hibernate.envers.RelationTargetAuditMode.NOT_AUDITED; + +@Entity +@Getter +@Setter +@Accessors(chain = true) +@Audited +public class TestIncident extends Incident { + + private String testId; + + @Enumerated(EnumType.STRING) + private TestType type; + + @Enumerated(EnumType.STRING) + private TestStatus status; + + @ManyToOne + @JsonIgnore + @Audited(targetAuditMode = NOT_AUDITED) + private Laboratory laboratory; + + private String comment; + + @Enumerated(EnumType.STRING) + private TestMaterial testMaterial; + + private byte[] report; + + public TestIncident() { + super(IncidentType.test); + } +} \ No newline at end of file diff --git a/server/src/main/java/de/coronavirus/imis/repositories/AdministrativeIncidentRepository.java b/server/src/main/java/de/coronavirus/imis/repositories/AdministrativeIncidentRepository.java new file mode 100644 index 00000000..c84133bf --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/repositories/AdministrativeIncidentRepository.java @@ -0,0 +1,14 @@ +package de.coronavirus.imis.repositories; + +import de.coronavirus.imis.domain.AdministrativeIncident; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AdministrativeIncidentRepository extends JpaRepository { + + List findByPatientId(String patientId); + +} diff --git a/server/src/main/java/de/coronavirus/imis/repositories/QuarantineIncidentRepository.java b/server/src/main/java/de/coronavirus/imis/repositories/QuarantineIncidentRepository.java new file mode 100644 index 00000000..50541a2a --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/repositories/QuarantineIncidentRepository.java @@ -0,0 +1,15 @@ +package de.coronavirus.imis.repositories; + +import de.coronavirus.imis.domain.QuarantineIncident; +import de.coronavirus.imis.domain.TestIncident; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface QuarantineIncidentRepository extends JpaRepository { + + List findByPatientId(String patientId); + +} diff --git a/server/src/main/java/de/coronavirus/imis/repositories/TestIncidentRepository.java b/server/src/main/java/de/coronavirus/imis/repositories/TestIncidentRepository.java new file mode 100644 index 00000000..5d0328a5 --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/repositories/TestIncidentRepository.java @@ -0,0 +1,17 @@ +package de.coronavirus.imis.repositories; + +import de.coronavirus.imis.domain.QuarantineIncident; +import de.coronavirus.imis.domain.TestIncident; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface TestIncidentRepository extends JpaRepository { + + List findByTestId(String testId); + + List findByPatientId(String patientId); + +} diff --git a/server/src/main/java/de/coronavirus/imis/services/IncidentService.java b/server/src/main/java/de/coronavirus/imis/services/IncidentService.java new file mode 100644 index 00000000..c3eb8e3c --- /dev/null +++ b/server/src/main/java/de/coronavirus/imis/services/IncidentService.java @@ -0,0 +1,243 @@ +package de.coronavirus.imis.services; + +import de.coronavirus.imis.api.dto.CreateLabTestDTO; +import de.coronavirus.imis.api.dto.SendToQuarantineDTO; +import de.coronavirus.imis.api.dto.UpdateTestStatusDTO; +import de.coronavirus.imis.domain.*; +import de.coronavirus.imis.mapper.PatientMapper; +import de.coronavirus.imis.repositories.*; +import lombok.RequiredArgsConstructor; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.query.AuditEntity; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/* +TODO +This class is called deprectaed because it enables the frontend to work with LabTestDTOs and QuarantineDTOs +instead of natively using IncidentDTOs. +Next steps: Build an Incident API and migrate the Frontend. + */ +@RequiredArgsConstructor +@Service +public class IncidentService { + + private final TestIncidentRepository testIncidentRepo; + private final LaboratoryRepository laboratoryRepo; + private final PatientRepository patientRepo; + private final PatientMapper patientMapper; + private final QuarantineIncidentRepository quarantineIncidentRepo; + private final AdministrativeIncidentRepository adminIncidentRepo; + private final DoctorRepository doctorRepo; + private final AuditReader auditReader; + private final ApplicationContext ctx; + + // Reading + + @Transactional + public List getLog(String id, boolean byPatient) { + List result = new ArrayList(); + + result.addAll(getLog(TestIncident.class, id, byPatient)); + result.addAll(getLog(QuarantineIncident.class, id, byPatient)); + result.addAll(getLog(AdministrativeIncident.class, id, byPatient)); + + result.sort( + (Incident i1, Incident i2) -> i1.getVersionTimestamp().compareTo(i2.getVersionTimestamp()) + ); + + return result; + } + + @Transactional + public List getLog(Class T, String id, boolean byPatient) { + var query = auditReader.createQuery().forRevisionsOfEntity(T, true, false); + if (byPatient) { + query.add(AuditEntity.relatedId("patient").eq(id)); + } else { + query.add(AuditEntity.id().eq(id)); + } + return query.getResultList(); + } + + @Transactional + public Incident getCurrent(String id) { + + String prefix = id.split("_")[0]; + switch (IncidentType.valueOf(prefix)) { + case test: + return testIncidentRepo.findById(id).orElseThrow(); + case quarantine: + return quarantineIncidentRepo.findById(id).orElseThrow(); + case administrative: + return adminIncidentRepo.findById(id).orElseThrow(); + } + + return null; + } + + @Transactional + public List getCurrentByPatient (String patientId) { + + List result = new ArrayList<>(); + + result.addAll( testIncidentRepo.findByPatientId(patientId) ); + result.addAll( quarantineIncidentRepo.findByPatientId(patientId) ); + result.addAll( adminIncidentRepo.findByPatientId(patientId) ); + + return result; + } + + @Transactional + public List getCurrentByPatient (String patientId, IncidentType type) { + + switch (type) { + case test: + return (List)(List) testIncidentRepo.findByPatientId(patientId); + case quarantine: + return (List)(List) quarantineIncidentRepo.findByPatientId(patientId); + case administrative: + return (List)(List) adminIncidentRepo.findByPatientId(patientId); + } + + return null; + } + + // Writing + + @Transactional + public TestIncident addIncident (CreateLabTestDTO info) + { + var incident = (TestIncident) new TestIncident() + .setTestId(info.getTestId()) + .setTestMaterial(info.getTestMaterial()) + .setComment(info.getComment()) + .setType(info.getTestType()) + .setStatus(TestStatus.TEST_SUBMITTED) + .setLaboratory(laboratoryRepo.findById(info.getLaboratoryId()).orElseThrow(LaboratoryNotFoundException::new)) + .setEventType(EventType.TEST_SUBMITTED_IN_PROGRESS) + .setPatient(patientRepo.findById(info.getPatientId()).orElseThrow(PatientNotFoundException::new)); + + testIncidentRepo.saveAndFlush(incident); + return incident; + } + + @Transactional + public TestIncident updateIncident (String laboratoryId, UpdateTestStatusDTO update) + { + var incident = testIncidentRepo.findByTestId(update.getTestId()).get(0); + var laboratory = laboratoryRepo.findById(laboratoryId).orElseThrow(); + incident + .setLaboratory(laboratory) + .setStatus(update.getStatus()) + .setComment(update.getComment()) + .setReport(update.getFile()) + .setEventType(testStatusToEvent(update.getStatus())); + // ToDo bei setStatus und setEventType: Semantik? Sinnvoll so? + + testIncidentRepo.saveAndFlush(incident); + return incident; + } + + // Quarantine Incidents + @Transactional + public QuarantineIncident addOrUpdateIncident (String patientId, SendToQuarantineDTO info) + { + // Patient & Date + var patient = patientRepo.findById(patientId).orElseThrow(); + var until = patientMapper.parseDate(info.getDateUntil()); + + // There's only one QuarantineIncident per Person which is why we can find it without Incident Id here. + var incidentOptional = quarantineIncidentRepo.findByPatientId(patientId); + var incident = incidentOptional.isEmpty() + ? (QuarantineIncident) new QuarantineIncident() + : incidentOptional.get(0); + + // Apply to Incident + incident + .setComment(info.getComment()) + .setUntil(until) + .setEventType(EventType.QUARANTINE_MANDATED) + .setPatient(patient); + quarantineIncidentRepo.saveAndFlush(incident); + + return incident; + } + + // Administrative Incidents + + // Presumtion Event (Former Initial Event) + @Transactional + public AdministrativeIncident addIncident (Patient patient, + Optional illness, + EventType eventType, + LocalDate dateOfReporting) { + var concreteIllness = illness.orElse(Illness.CORONA); + + dateOfReporting = dateOfReporting==null ? LocalDate.now() : dateOfReporting; + + var incident = (AdministrativeIncident) new AdministrativeIncident() + .setDateOfReporting(dateOfReporting) + .setIllness(concreteIllness) + .setEventType(eventType) + .setPatient(patient); + + adminIncidentRepo.saveAndFlush(incident); + return incident; + } + + //SCHEDULED_FOR_TESTING + @Transactional + public AdministrativeIncident addIncident (Patient patient, String labId, String doctorId) { + + // Todo: Is this necessary? Why? Move it? + final Laboratory laboratory = laboratoryRepo.findById(labId).orElseGet(() -> { + Laboratory lab = new Laboratory(); + lab.setId(labId); + return laboratoryRepo.save(lab); + }); + final Doctor doctor = doctorRepo.findById(doctorId).orElseGet(() -> + { + var newDoctor = new Doctor(); + newDoctor.setId(doctorId); + return doctorRepo.save(newDoctor); + } + ); + + var incident = (AdministrativeIncident) new AdministrativeIncident() + .setDateOfReporting(LocalDate.now()) + .setIllness(Illness.CORONA) + .setResponsibleDoctor(doctor) + .setEventType(EventType.SCHEDULED_FOR_TESTING) + .setPatient(patient); + adminIncidentRepo.saveAndFlush(incident); + + return incident; + } + + private EventType testStatusToEvent(TestStatus input) { + EventType result; + switch (input) { + case TEST_NEGATIVE: + result = EventType.TEST_FINISHED_NEGATIVE; + break; + case TEST_SUBMITTED: + case TEST_IN_PROGRESS: + result = EventType.TEST_SUBMITTED_IN_PROGRESS; + break; + case TEST_POSITIVE: + result = EventType.TEST_FINISHED_POSITIVE; + break; + default: + result = EventType.TEST_FINISHED_INVALID; + + } + return result; + } +} diff --git a/server/src/main/java/de/coronavirus/imis/services/PatientService.java b/server/src/main/java/de/coronavirus/imis/services/PatientService.java index 0cb9b54a..a7f68be7 100644 --- a/server/src/main/java/de/coronavirus/imis/services/PatientService.java +++ b/server/src/main/java/de/coronavirus/imis/services/PatientService.java @@ -40,6 +40,7 @@ public class PatientService { private final LikeOperatorService likeOperatorService; private final RandomService randomService; private final PatientMapper patientMapper; + private final IncidentService incidentService; public List getAllPatients() { @@ -89,6 +90,10 @@ public Patient addPatient(Patient patient, final LocalDate dateOfReporting) { patient.getPatientStatus(), dateOfReporting); log.info("inserted event for patient {}", patient); + incidentService.addIncident( + patient, Optional.empty(), + patient.getPatientStatus(), + dateOfReporting); return patient; } diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 19bf3b56..3439b916 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -52,7 +52,12 @@ spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:testdb + jpa: + hibernate: + ddl-auto: create cloud: gcp: storage: enabled: false + + diff --git a/server/src/test/java/de/coronavirus/imis/services/PatientServiceTest.java b/server/src/test/java/de/coronavirus/imis/services/PatientServiceTest.java index 75fbd48f..5103f624 100644 --- a/server/src/test/java/de/coronavirus/imis/services/PatientServiceTest.java +++ b/server/src/test/java/de/coronavirus/imis/services/PatientServiceTest.java @@ -19,7 +19,8 @@ void queryPatientsNullInOrderAndOrderByProperty() { Mockito.mock(PatientEventService.class), new LikeOperatorService(), new RandomService(), - patientMapper); + patientMapper, + Mockito.mock(IncidentService.class)); final PatientSearchParamsDTO patientSearchParamsDTO = new PatientSearchParamsDTO(); patientSearchParamsDTO.setOrder(null); patientSearchParamsDTO.setOrderBy(null);