Skip to content

Commit

Permalink
TMI2-335 adds ability to download grant mandatory questions data (#59)
Browse files Browse the repository at this point in the history
* adds grant mandatory questions download

* change migration script version

* formatting

* pr comments

* pr comments and formatting

* formatting
  • Loading branch information
rachelswart authored Nov 8, 2023
1 parent ce65d74 commit 6c6e1c4
Show file tree
Hide file tree
Showing 18 changed files with 1,022 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gov.cabinetoffice.gap.adminbackend.constants;

import java.util.Arrays;
import java.util.List;

public class SpotlightHeaders {

public static final List<String> SPOTLIGHT_HEADERS = Arrays.asList("Application number (required)",
"Organisation name (required)", "Address street (optional)", "Address town (optional)",
"Address county (optional)", "Address postcode (required)", "Application amount (required)",
"Charity Commission registration number (required - if applicable)",
"Companies House registration number (required - if applicable)",
"Similarities to other applications (optional)");

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gov.cabinetoffice.gap.adminbackend.controllers;

import gov.cabinetoffice.gap.adminbackend.services.FileService;
import gov.cabinetoffice.gap.adminbackend.services.GrantMandatoryQuestionService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;

import static gov.cabinetoffice.gap.adminbackend.controllers.SubmissionsController.EXPORT_CONTENT_TYPE;

@Log4j2
@RestController
@RequestMapping("/mandatory-questions")
@Tag(name = "Mandatory questions", description = "API for handling mandatory questions")
@RequiredArgsConstructor
public class GrantMandatoryQuestionsController {

final private GrantMandatoryQuestionService grantMandatoryQuestionService;

final private FileService fileService;

@GetMapping("/scheme/{schemeId}/complete")
public ResponseEntity<Boolean> hasCompletedMandatoryQuestions(@PathVariable Integer schemeId) {
return ResponseEntity.ok(grantMandatoryQuestionService.hasCompletedMandatoryQuestions(schemeId));
}

@GetMapping(value = "/spotlight-export/{schemeId}", produces = EXPORT_CONTENT_TYPE)
public ResponseEntity<InputStreamResource> exportSpotlightChecks(@PathVariable Integer schemeId) {
log.info("Started mandatory questions export for scheme " + schemeId);
long start = System.currentTimeMillis();

final ByteArrayOutputStream stream = grantMandatoryQuestionService.exportSpotlightChecks(schemeId);
final String exportFileName = grantMandatoryQuestionService.generateExportFileName(schemeId);
final InputStreamResource resource = fileService.createTemporaryFile(stream, exportFileName);

final int length = stream.toByteArray().length;

// setting HTTP headers to tell caller we are returning a file
final HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(ContentDisposition.parse("attachment; filename=" + exportFileName));

long end = System.currentTimeMillis();
log.info("Finished mandatory questions export for scheme " + schemeId + ". Export time in millis: "
+ (end - start));

return ResponseEntity.ok().headers(headers).contentLength(length)
.contentType(MediaType.parseMediaType(EXPORT_CONTENT_TYPE)).body(resource);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import gov.cabinetoffice.gap.adminbackend.dtos.submission.SubmissionExportsDTO;
import gov.cabinetoffice.gap.adminbackend.enums.GrantExportStatus;
import gov.cabinetoffice.gap.adminbackend.exceptions.NotFoundException;
import gov.cabinetoffice.gap.adminbackend.services.FileService;
import gov.cabinetoffice.gap.adminbackend.services.SecretAuthService;
import gov.cabinetoffice.gap.adminbackend.services.SubmissionsService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -38,14 +39,16 @@ public class SubmissionsController {

private final SecretAuthService secretAuthService;

private final FileService fileService;

@GetMapping(value = "/spotlight-export/{applicationId}", produces = EXPORT_CONTENT_TYPE)
public ResponseEntity<InputStreamResource> exportSpotlightChecks(@PathVariable Integer applicationId) {
log.info("Started submissions export for application " + applicationId);
long start = System.currentTimeMillis();

final ByteArrayOutputStream stream = submissionsService.exportSpotlightChecks(applicationId);
final String exportFileName = submissionsService.generateExportFileName(applicationId);
final InputStreamResource resource = createTemporaryFile(stream, exportFileName);
final InputStreamResource resource = fileService.createTemporaryFile(stream, exportFileName);

submissionsService.updateSubmissionLastRequiredChecksExport(applicationId);

Expand All @@ -65,20 +68,6 @@ public ResponseEntity<InputStreamResource> exportSpotlightChecks(@PathVariable I
.contentType(MediaType.parseMediaType(EXPORT_CONTENT_TYPE)).body(resource);
}

private InputStreamResource createTemporaryFile(ByteArrayOutputStream stream, String filename) {
try {
File tempFile = File.createTempFile(filename, ".xlsx");
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile))) {
stream.writeTo(out);
}
return new InputStreamResource(new ByteArrayInputStream(stream.toByteArray()));
}
catch (Exception e) {
log.error("Error creating temporary for file {} problem reported {}", filename, e.getMessage());
throw new RuntimeException(e);
}
}

@PostMapping("/export-all/{applicationId}")
public ResponseEntity exportAllSubmissions(@PathVariable Integer applicationId) {
submissionsService.triggerSubmissionsExport(applicationId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gov.cabinetoffice.gap.adminbackend.dtos;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.UUID;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GrantMandatoryQuestionDto {

private UUID id;

private int schemeId;

private UUID submissionId;

private String name;

private String addressLine1;

private String addressLine2;

private String city;

private String county;

private String postcode;

private String charityCommissionNumber;

private String companiesHouseNumber;

private String orgType;

private String fundingAmount;

private List<String> fundingLocation;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package gov.cabinetoffice.gap.adminbackend.entities;

import com.vladmihalcea.hibernate.type.array.EnumArrayType;
import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
import gov.cabinetoffice.gap.adminbackend.dtos.submission.GrantApplicant;
import gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionFundingLocation;
import gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionOrgType;
import gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.TypeDef;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.UUID;

@EntityListeners(AuditingEntityListener.class)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "grant_mandatory_questions")
@TypeDef(typeClass = EnumArrayType.class, defaultForType = GrantMandatoryQuestionFundingLocation[].class, parameters = {
@Parameter(name = AbstractArrayType.SQL_ARRAY_TYPE, value = "grant_mandatory_question_funding_location") })
public class GrantMandatoryQuestions extends BaseEntity {

@Id
@GeneratedValue
@Column(name = "id")
private UUID id;

@ManyToOne
@JoinColumn(name = "grant_scheme_id")
private SchemeEntity schemeEntity;

@OneToOne
@JoinColumn(name = "submission_id", referencedColumnName = "id")
private Submission submission;

@Column(name = "name")
private String name;

@Column(name = "address_line_1")
private String addressLine1;

@Column(name = "address_line_2")
private String addressLine2;

@Column(name = "city")
private String city;

@Column(name = "county")
private String county;

@Column(name = "postcode")
private String postcode;

@Column(name = "org_type")
@Enumerated(EnumType.STRING)
@ColumnTransformer(write = "?::grant_mandatory_question_type")
private GrantMandatoryQuestionOrgType orgType;

@Column(name = "companies_house_number")
private String companiesHouseNumber;

@Column(name = "charity_commission_number")
private String charityCommissionNumber;

@Column(name = "funding_amount", precision = 16) // this should match your database
// column definition
private BigDecimal fundingAmount;

@Column(name = "funding_location")
private GrantMandatoryQuestionFundingLocation[] fundingLocation;

@Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
@ColumnTransformer(write = "?::grant_mandatory_question_status")
@Builder.Default
private GrantMandatoryQuestionStatus status = GrantMandatoryQuestionStatus.NOT_STARTED; // TODO
// what
// if
// the
// status
// is
// still
// in
// progress

@Column(name = "version", nullable = false)
@Builder.Default
private Integer version = 1;

@Column(name = "created", nullable = false)
@Builder.Default
private Instant created = Instant.now();

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "created_by", referencedColumnName = "id")
private GrantApplicant createdBy;

@Column(name = "last_updated")
private Instant lastUpdated;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "last_updated_by", referencedColumnName = "id")
private GrantApplicant lastUpdatedBy;

@Column
private String gapId;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package gov.cabinetoffice.gap.adminbackend.enums;

import com.fasterxml.jackson.annotation.JsonCreator;

public enum GrantMandatoryQuestionFundingLocation {

NORTH_EAST_ENGLAND("North East England"), NORTH_WEST_ENGLAND("North West England"),
YORKSHIRE_AND_THE_HUMBER("Yorkshire and the Humber"), EAST_MIDLANDS_ENGLAND("East Midlands (England)"),
WEST_MIDLANDS("West Midlands"), EAST_ENGLAND("East England"), LONDON("London"),
SOUTH_EAST_ENGLAND("South East England"), SOUTH_WEST_ENGLAND("South West England"), MIDLANDS("Midlands"),
SCOTLAND("Scotland"), WALES("Wales"), NORTHERN_IRELAND("Northern Ireland"), OUTSIDE_UK("Outside UK");

private final String name;

GrantMandatoryQuestionFundingLocation(String name) {
this.name = name;
}

public static GrantMandatoryQuestionFundingLocation valueOfName(String name) {
for (GrantMandatoryQuestionFundingLocation type : values()) {
if (type.name.equals(name)) {
return type;
}
}
return null;
}

@JsonCreator
public static GrantMandatoryQuestionFundingLocation getGrantApplicantOrganisationTypeFromName(String name) {

return valueOfName(name);

}

public String getName() {
return name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gov.cabinetoffice.gap.adminbackend.enums;

import com.fasterxml.jackson.annotation.JsonCreator;

public enum GrantMandatoryQuestionOrgType {

LIMITED_COMPANY("Limited company"), NON_LIMITED_COMPANY("Non-limited company"),
REGISTERED_CHARITY("Registered charity"), UNREGISTERED_CHARITY("Unregistered charity"), OTHER("Other");

private String name;

private GrantMandatoryQuestionOrgType(String name) {
this.name = name;
}

@Override
public String toString() {
return this.name;
}

public static GrantMandatoryQuestionOrgType valueOfName(String name) {
for (GrantMandatoryQuestionOrgType type : values()) {
if (type.name.equals(name)) {
return type;
}
}
return null;
}

@JsonCreator
public static GrantMandatoryQuestionOrgType getGrantApplicantOrganisationTypeFromName(String name) {

return valueOfName(name);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gov.cabinetoffice.gap.adminbackend.enums;

public enum GrantMandatoryQuestionStatus {

NOT_STARTED, IN_PROGRESS, COMPLETED

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gov.cabinetoffice.gap.adminbackend.repositories;

import gov.cabinetoffice.gap.adminbackend.entities.GrantMandatoryQuestions;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.UUID;

public interface GrantMandatoryQuestionRepository extends JpaRepository<GrantMandatoryQuestions, UUID> {

@Query("select g " + "from GrantMandatoryQuestions g " + "where g.schemeEntity.id = ?1 "
+ "and g.status = gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionStatus.COMPLETED")
List<GrantMandatoryQuestions> findBySchemeEntity_IdAndCompletedStatus(Integer id);

}
Loading

0 comments on commit 6c6e1c4

Please sign in to comment.