From bbd8186f9e1846cd3c158435a38010c78edd9071 Mon Sep 17 00:00:00 2001 From: Jeffrey Horton Date: Wed, 19 Apr 2023 16:16:40 +0100 Subject: [PATCH] [HOCS-6478] Work in prog report skeleton --- .../mapping/WorkInProgressDataMapper.java | 63 +++++++++ .../domain/reports/WorkInProgressData.java | 66 ++++++++++ .../domain/reports/WorkInProgressRow.java | 86 ++++++++++++ .../repository/WorkInProgressRepository.java | 31 +++++ .../reports/reports/WorkInProgressReport.java | 124 ++++++++++++++++++ ...78_create_work_in_progress_report_view.sql | 36 +++++ 6 files changed, 406 insertions(+) create mode 100644 src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/mapping/WorkInProgressDataMapper.java create mode 100644 src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressData.java create mode 100644 src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressRow.java create mode 100644 src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/repository/WorkInProgressRepository.java create mode 100644 src/main/java/uk/gov/digital/ho/hocs/casework/reports/reports/WorkInProgressReport.java create mode 100644 src/main/resources/db/migration/postgresql/R__6478_create_work_in_progress_report_view.sql diff --git a/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/mapping/WorkInProgressDataMapper.java b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/mapping/WorkInProgressDataMapper.java new file mode 100644 index 000000000..47b56840e --- /dev/null +++ b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/mapping/WorkInProgressDataMapper.java @@ -0,0 +1,63 @@ +package uk.gov.digital.ho.hocs.casework.reports.domain.mapping; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import uk.gov.digital.ho.hocs.casework.reports.domain.reports.WorkInProgressData; +import uk.gov.digital.ho.hocs.casework.reports.domain.reports.WorkInProgressRow; + +import java.util.Optional; + +@Service +@Slf4j +@Profile("reporting") +public class WorkInProgressDataMapper { + + private final UserNameValueMapper userNameValueMapper; + + private final TeamNameValueMapper teamNameValueMapper; + + private final StageNameValueMapper stageNameValueMapper; + + public WorkInProgressDataMapper( + UserNameValueMapper userNameValueMapper, + TeamNameValueMapper teamNameValueMapper, + StageNameValueMapper stageNameValueMapper + ) { + this.userNameValueMapper = userNameValueMapper; + this.teamNameValueMapper = teamNameValueMapper; + this.stageNameValueMapper = stageNameValueMapper; + } + + public WorkInProgressRow mapDataToRow(WorkInProgressData data) { + Optional maybeUser = userNameValueMapper.map(data.getAssignedUserUUID()); + Optional maybeTeam = teamNameValueMapper.map(data.getAssignedTeamUUID()); + + return new WorkInProgressRow( + data.getCaseUUID(), + data.getCaseReference(), + data.getCompType(), + data.getDateCreated(), + data.getDateReceived(), + data.getCaseDeadline(), + data.getOwningCSU(), + data.getDirectorate(), + data.getBusinessAreaBasedOnDirectorate(), + data.getEnquiryReason(), + data.getPrimaryCorrespondentName(), + data.getCaseSummary(), + data.getSeverity(), + data.getAssignedUserUUID(), + data.getAssignedTeamUUID(), + data.getStageUUID(), + data.getStageType(), + data.getDueWeek(), + maybeUser.orElse(null), + maybeTeam.orElse(null), + stageNameValueMapper.map(data.getStageType()).orElse(data.getStageType()), + data.getAssignedUserUUID() != null, + maybeUser.or(() -> maybeTeam).orElse(null) + ); + } + +} diff --git a/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressData.java b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressData.java new file mode 100644 index 000000000..1b1e042c1 --- /dev/null +++ b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressData.java @@ -0,0 +1,66 @@ +package uk.gov.digital.ho.hocs.casework.reports.domain.reports; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.UUID; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Entity +@Table(name = "report_work_in_progress") +public class WorkInProgressData implements Serializable { + + @Id + @Column(name = "case_uuid") + private UUID caseUUID; + + private String caseReference; + + private String compType; + + private LocalDate dateCreated; + + private LocalDate dateReceived; + + private LocalDate caseDeadline; + + @Column(name = "owning_csu") + private String owningCSU; + + private String directorate; + + private String businessAreaBasedOnDirectorate; + + private String enquiryReason; + + private String primaryCorrespondentName; + + private String caseSummary; + + private String severity; + + @Column(name = "assigned_user_uuid") + private UUID assignedUserUUID; + + @Column(name = "assigned_team_uuid") + private UUID assignedTeamUUID; + + @Column(name = "stage_uuid") + private UUID stageUUID; + + private String stageType; + + private String caseType; + + private String dueWeek; + +} diff --git a/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressRow.java b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressRow.java new file mode 100644 index 000000000..2a2f787d3 --- /dev/null +++ b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/reports/WorkInProgressRow.java @@ -0,0 +1,86 @@ +package uk.gov.digital.ho.hocs.casework.reports.domain.reports; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.UUID; + +@AllArgsConstructor +@Getter +@EqualsAndHashCode +public class WorkInProgressRow implements ReportRow, Serializable { + + @JsonProperty("case_uuid") + private UUID caseUUID; + + @JsonProperty("case_reference") + private String caseReference; + + @JsonProperty("comp_type") + private String compType; + + @JsonProperty("date_created") + private LocalDate dateCreated; + + @JsonProperty("date_received") + private LocalDate dateReceived; + + @JsonProperty("case_deadline") + private LocalDate caseDeadline; + + @JsonProperty("owning_csu") + private String owningCSU; + + @JsonProperty("directorate") + private String directorate; + + @JsonProperty("business_area_based_on_directorate") + private String businessAreaBasedOnDirectorate; + + @JsonProperty("enquiry_reason") + private String enquiryReason; + + @JsonProperty("primary_correspondent_name") + private String primaryCorrespondentName; + + @JsonProperty("case_summary") + private String caseSummary; + + @JsonProperty("severity") + private String severity; + + @JsonProperty("assigned_user_uuid") + private UUID assignedUserUUID; + + @JsonProperty("assigned_team_uuid") + private UUID assignedTeamUUID; + + @JsonProperty("stage_uuid") + private UUID stageUUID; + + @JsonProperty("stage_type") + private String stageType; + + @JsonProperty("due_week") + private String dueWeek; + + @JsonProperty("user_name") + private String userName; + + @JsonProperty("team_name") + private String teamName; + + @JsonProperty("stage_name") + private String stageName; + + @JsonProperty("allocation_status") + private boolean allocationStatus; + + @JsonProperty("allocated_to") + private String allocatedTo; + +} diff --git a/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/repository/WorkInProgressRepository.java b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/repository/WorkInProgressRepository.java new file mode 100644 index 000000000..3b9ef9be7 --- /dev/null +++ b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/domain/repository/WorkInProgressRepository.java @@ -0,0 +1,31 @@ +package uk.gov.digital.ho.hocs.casework.reports.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.repository.query.Param; +import uk.gov.digital.ho.hocs.casework.reports.domain.CaseType; +import uk.gov.digital.ho.hocs.casework.reports.domain.reports.WorkInProgressData; + +import javax.persistence.QueryHint; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.hibernate.annotations.QueryHints.READ_ONLY; +import static org.hibernate.jpa.QueryHints.HINT_CACHEABLE; +import static org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE; + +public interface WorkInProgressRepository extends JpaRepository { + + @QueryHints(value = { + @QueryHint(name = HINT_FETCH_SIZE, value = "50000"), + @QueryHint(name = HINT_CACHEABLE, value = "false"), + @QueryHint(name = READ_ONLY, value = "true") + }) + @Query( + value = "SELECT * FROM report_work_in_progress WHERE case_type = :#{#caseType?.name()}", + nativeQuery = true + ) + Stream getReportData(@Param("caseType") CaseType caseType); + +} diff --git a/src/main/java/uk/gov/digital/ho/hocs/casework/reports/reports/WorkInProgressReport.java b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/reports/WorkInProgressReport.java new file mode 100644 index 000000000..b81630275 --- /dev/null +++ b/src/main/java/uk/gov/digital/ho/hocs/casework/reports/reports/WorkInProgressReport.java @@ -0,0 +1,124 @@ +package uk.gov.digital.ho.hocs.casework.reports.reports; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import uk.gov.digital.ho.hocs.casework.reports.api.dto.ColumnType; +import uk.gov.digital.ho.hocs.casework.reports.api.dto.FilterType; +import uk.gov.digital.ho.hocs.casework.reports.api.dto.ReportColumnDto; +import uk.gov.digital.ho.hocs.casework.reports.domain.CaseType; +import uk.gov.digital.ho.hocs.casework.reports.domain.mapping.WorkInProgressDataMapper; +import uk.gov.digital.ho.hocs.casework.reports.domain.reports.WorkInProgressRow; +import uk.gov.digital.ho.hocs.casework.reports.domain.repository.WorkInProgressRepository; + +import javax.persistence.EntityManager; +import java.util.List; +import java.util.stream.Stream; + +@Component +@Profile("reporting") +public class WorkInProgressReport implements Report { + + private final WorkInProgressRepository workInProgressRepository; + + private final EntityManager entityManager; + + private final WorkInProgressDataMapper workInProgressDataMapper; + + private final ObjectMapper objectMapper; + + public WorkInProgressReport( + WorkInProgressRepository workInProgressRepository, + EntityManager entityManager, + WorkInProgressDataMapper workInProgressDataMapper, + ObjectMapper objectMapper + ) { + this.workInProgressRepository = workInProgressRepository; + this.entityManager = entityManager; + this.workInProgressDataMapper = workInProgressDataMapper; + this.objectMapper = objectMapper; + } + + @Override + public String getSlug() { + return "work-in-progress"; + } + + @Override + public String getDisplayName() { + return "Work in progress report"; + } + + @Override + public String getDescription() { + return "A report of in progress cases to allow allocation to appropriate case workers."; + } + + @Override + public List getAvailableCaseTypes() { + return List.of(CaseType.COMP, CaseType.COMP2); + } + + @Override + public List getColumnMetadata() { + return List.of(new ReportColumnDto("case_uuid", "Case UUID", ColumnType.STRING, false, true), + new ReportColumnDto("case_reference", "Reference", ColumnType.LINK, true, true).withAdditionalField( + "link_pattern", "/case/${case_uuid}/stage/${stage_uuid}"), + new ReportColumnDto("stage_uuid", "Stage UUID", ColumnType.STRING, false, false), + new ReportColumnDto("stage_name", "Stage", ColumnType.STRING, true, true).withFilter(FilterType.SELECT), + new ReportColumnDto("case_summary", "Case summary", ColumnType.STRING, true, true).withFilter( + FilterType.CONTAINS_TEXT), + new ReportColumnDto("allocation_status", "Allocation status", ColumnType.BOOLEAN, true, true) + .withAdditionalField("label_if_true", "Allocated") + .withAdditionalField("label_if_false", "Unallocated") + .withFilter(FilterType.BOOLEAN), + new ReportColumnDto("allocated_to", "Allocated to", ColumnType.STRING, true, true).withFilter( + FilterType.SELECT), new ReportColumnDto("case_deadline", "Deadline", ColumnType.DATE, true, true), + new ReportColumnDto("due_week", "Due week", ColumnType.STRING, false, false) + .withAdditionalField("filter_values", getDueDateFilterValues()) + .withFilter(FilterType.SELECT), + new ReportColumnDto("severity", "Severity", ColumnType.STRING, true, true).withFilter(FilterType.SELECT), + new ReportColumnDto("comp_type", "Complaint type", ColumnType.STRING, false, true), + new ReportColumnDto("date_created", "Created", ColumnType.DATE, false, true), + new ReportColumnDto("date_received", "Received", ColumnType.DATE, false, true), + new ReportColumnDto("owning_csu", "Owning CSU", ColumnType.STRING, false, true), + new ReportColumnDto("directorate", "Directorate", ColumnType.STRING, false, true), + new ReportColumnDto("business_area_based_on_directorate", "Business area based on directorate", + ColumnType.STRING, false, true + ), new ReportColumnDto("enquiry_reason", "Enquiry reason", ColumnType.STRING, false, true), + new ReportColumnDto("primary_correspondent_name", "Primary correspondent name", ColumnType.STRING, false, + true + ), new ReportColumnDto("stage_type", "Stage Type", ColumnType.STRING, false, true), + new ReportColumnDto("assigned_user_uuid", "Assigned user UUID", ColumnType.STRING, false, true), + new ReportColumnDto("assigned_team_uuid", "Assigned team UUID", ColumnType.STRING, false, true), + new ReportColumnDto("user_name", "User", ColumnType.STRING, false, true), + new ReportColumnDto("team_name", "Team", ColumnType.STRING, false, true) + ); + } + + private String getDueDateFilterValues() { + try { + return objectMapper.writeValueAsString( + List.of("Due this week", "Due next week", "Due week 3", "Due week 4", "Due week 5", + "Outside service standard" + )); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to serialise hardcoded Map to JSON object", e); + } + } + + @Override + public String getIdColumnKey() { + return "case_uuid"; + } + + @Override + public Stream getRows(CaseType caseType) { + return workInProgressRepository + .getReportData(caseType) + .peek(entityManager::detach) + .map(workInProgressDataMapper::mapDataToRow); + } + +} diff --git a/src/main/resources/db/migration/postgresql/R__6478_create_work_in_progress_report_view.sql b/src/main/resources/db/migration/postgresql/R__6478_create_work_in_progress_report_view.sql new file mode 100644 index 000000000..032b051af --- /dev/null +++ b/src/main/resources/db/migration/postgresql/R__6478_create_work_in_progress_report_view.sql @@ -0,0 +1,36 @@ +SET search_path TO casework; + +DROP VIEW IF EXISTS report_work_in_progress; + +CREATE OR REPLACE VIEW report_work_in_progress AS + SELECT cd.uuid AS case_uuid, + cd.reference AS case_reference, + cd.data ->> 'CompType' AS comp_type, + cd.created::date AS date_created, + cd.data ->> 'ReceivedDate' AS date_received, + cd.case_deadline AS case_deadline, + cd.data ->> 'OwningCSU' AS owning_csu, + cd.data ->> 'Directorate' AS directorate, + cd.data ->> 'BusAreaBasedOnDirectorate' AS business_area_based_on_directorate, + cd.data ->> 'EnqReason' AS enquiry_reason, + c.fullname AS primary_correspondent_name, + cd.data ->> 'CaseSummary' AS case_summary, + cd.data ->> 'Severity' AS severity, + ast.user_uuid AS assigned_user_uuid, + ast.team_uuid AS assigned_team_uuid, + ast.uuid AS stage_uuid, + ast.type AS stage_type, + cd.type AS case_type, + CASE + WHEN cd.case_deadline < now()::date THEN 'Outside service standard' + WHEN extract(WEEK FROM cd.case_deadline) = extract(WEEK FROM now()) THEN 'Due this week' + WHEN extract(WEEK FROM cd.case_deadline) = extract(WEEK FROM now() + INTERVAL '1 WEEK') THEN 'Due next week' + WHEN extract(WEEK FROM cd.case_deadline) = extract(WEEK FROM now() + INTERVAL '2 WEEK') THEN 'Due week 3' + WHEN extract(WEEK FROM cd.case_deadline) = extract(WEEK FROM now() + INTERVAL '3 WEEK') THEN 'Due week 4' + WHEN extract(WEEK FROM cd.case_deadline) = extract(WEEK FROM now() + INTERVAL '4 WEEK') THEN 'Due week 5' + END AS due_week + FROM case_data cd + LEFT JOIN active_stage ast ON cd.uuid = ast.case_uuid + LEFT JOIN correspondent c ON cd.uuid = c.case_uuid + WHERE NOT completed; +