Skip to content

Commit

Permalink
TMI2-422 download spotlight files in zip folder (#83)
Browse files Browse the repository at this point in the history
* download spotlight files in zip folder

* Update src/main/java/gov/cabinetoffice/gap/adminbackend/services/GrantMandatoryQuestionService.java

Co-authored-by: dominicwest <[email protected]>

* pr comments

---------

Co-authored-by: dominicwest <[email protected]>
  • Loading branch information
rachelswart and dominicwest authored Nov 22, 2023
1 parent 35c8f97 commit eccf93b
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 122 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 DueDiligenceHeaders {

public static final List<String> DUE_DILIGENCE_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)", "Organisation type (required)",
"Similarities to other applications (optional)");

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
Expand All @@ -15,7 +16,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;
import java.io.ByteArrayOutputStream;

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

Expand All @@ -35,23 +36,35 @@ public ResponseEntity<Boolean> hasCompletedMandatoryQuestions(@PathVariable Inte
return ResponseEntity.ok(grantMandatoryQuestionService.hasCompletedMandatoryQuestions(schemeId));
}

@GetMapping(value = "/due-diligence/{schemeId}", produces = EXPORT_CONTENT_TYPE)
public ResponseEntity<InputStreamResource> exportDueDiligenceData(@PathVariable Integer schemeId) {
final ByteArrayOutputStream stream = grantMandatoryQuestionService.getDueDiligenceData(schemeId);
final String exportFileName = grantMandatoryQuestionService.generateExportFileName(schemeId, null);
return getInputStreamResourceResponseEntity(schemeId, stream, exportFileName);
}

@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);
final ByteArrayOutputStream stream = grantMandatoryQuestionService.getSpotlightChecks(schemeId);
final String exportFileName = "spotlight_checks.zip";
return getInputStreamResourceResponseEntity(schemeId, stream, exportFileName);
}

@NotNull
private ResponseEntity<InputStreamResource> getInputStreamResourceResponseEntity(@PathVariable Integer schemeId,
ByteArrayOutputStream stream, String exportFileName) {
log.info("Started due diligence data 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: "
log.info("Finished due diligence data export for scheme " + schemeId + ". Export time in millis: "
+ (end - start));

return ResponseEntity.ok().headers(headers).contentLength(length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,21 @@ public interface GrantMandatoryQuestionRepository extends JpaRepository<GrantMan
+ "and g.status = gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionStatus.COMPLETED")
List<GrantMandatoryQuestions> findBySchemeEntity_IdAndCompletedStatus(Integer id);

@Query("select g from GrantMandatoryQuestions g " + "where g.schemeEntity.id = ?1 "
+ "and g.status = gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionStatus.COMPLETED "
+ "and g.orgType in (gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionOrgType.CHARITY, "
+ "gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionOrgType.REGISTERED_CHARITY, "
+ "gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionOrgType.UNREGISTERED_CHARITY, "
+ "gov.cabinetoffice.gap.adminbackend.enums.GrantMandatoryQuestionOrgType.LIMITED_COMPANY)")
List<GrantMandatoryQuestions> findCharitiesAndCompaniesBySchemeEntityIdAndCompletedStatus(Integer id);

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

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

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package gov.cabinetoffice.gap.adminbackend.services;

import gov.cabinetoffice.gap.adminbackend.constants.DueDiligenceHeaders;
import gov.cabinetoffice.gap.adminbackend.constants.SpotlightHeaders;
import gov.cabinetoffice.gap.adminbackend.dtos.schemes.SchemeDTO;
import gov.cabinetoffice.gap.adminbackend.entities.GrantMandatoryQuestions;
import gov.cabinetoffice.gap.adminbackend.exceptions.NotFoundException;
import gov.cabinetoffice.gap.adminbackend.exceptions.SpotlightExportException;
import gov.cabinetoffice.gap.adminbackend.models.AdminSession;
import gov.cabinetoffice.gap.adminbackend.repositories.GrantMandatoryQuestionRepository;
Expand All @@ -22,8 +22,6 @@
import java.util.Calendar;
import java.util.List;

import static java.util.Optional.ofNullable;

@RequiredArgsConstructor
@Service
@Slf4j
Expand All @@ -33,14 +31,50 @@ public class GrantMandatoryQuestionService {

private final SchemeService schemeService;

private final ZipService zipService;

public List<GrantMandatoryQuestions> getGrantMandatoryQuestionBySchemeAndCompletedStatus(Integer schemeId) {
return ofNullable(grantMandatoryQuestionRepository.findBySchemeEntity_IdAndCompletedStatus(schemeId))
.orElseThrow(() -> new NotFoundException(
String.format("No completed mandatory questions with ID %s was found", schemeId)));
return grantMandatoryQuestionRepository.findBySchemeEntity_IdAndCompletedStatus(schemeId);
}

public List<GrantMandatoryQuestions> getCharitiesAndCompaniesMandatoryQuestionsBySchemeAndCompletedStatus(
Integer schemeId) {
return grantMandatoryQuestionRepository.findCharitiesAndCompaniesBySchemeEntityIdAndCompletedStatus(schemeId);
}

public List<GrantMandatoryQuestions> getNonLimitedCompaniesMandatoryQuestionsBySchemeAndCompletedStatus(
Integer schemeId) {
return grantMandatoryQuestionRepository.findNonLimitedCompaniesBySchemeEntityIdAndCompletedStatus(schemeId);
}

public ByteArrayOutputStream getSpotlightChecks(Integer schemeId) {
final List<GrantMandatoryQuestions> companiesAndCharitiesQuestions = getCharitiesAndCompaniesMandatoryQuestionsBySchemeAndCompletedStatus(
schemeId);
final List<List<String>> charitiesAndCompanies = exportSpotlightChecks(schemeId, companiesAndCharitiesQuestions,
false);
final String charitiesAndCompaniesFilename = generateExportFileName(schemeId, " charities_and_companies");

final List<GrantMandatoryQuestions> nonLimitedCompanyQuestions = getNonLimitedCompaniesMandatoryQuestionsBySchemeAndCompletedStatus(
schemeId);
final List<List<String>> nonLimitedCompanies = exportSpotlightChecks(schemeId, nonLimitedCompanyQuestions,
false);
final String nonLimitedCompaniesFilename = generateExportFileName(schemeId, "non_limited_companies");

final List<List<List<String>>> dataList = List.of(charitiesAndCompanies, nonLimitedCompanies);
final List<String> filenames = List.of(charitiesAndCompaniesFilename, nonLimitedCompaniesFilename);
return zipService.createZip(SpotlightHeaders.SPOTLIGHT_HEADERS, dataList, filenames);
}

public ByteArrayOutputStream exportSpotlightChecks(Integer schemeId) {
AdminSession adminSession = HelperUtils.getAdminSessionForAuthenticatedUser();
public ByteArrayOutputStream getDueDiligenceData(Integer schemeId) {
final List<GrantMandatoryQuestions> mandatoryQuestions = getGrantMandatoryQuestionBySchemeAndCompletedStatus(
schemeId);
final List<List<String>> exportData = exportSpotlightChecks(schemeId, mandatoryQuestions, true);
return XlsxGenerator.createResource(DueDiligenceHeaders.DUE_DILIGENCE_HEADERS, exportData);
}

private List<List<String>> exportSpotlightChecks(Integer schemeId,
List<GrantMandatoryQuestions> grantMandatoryQuestions, boolean addOrgType) {
final AdminSession adminSession = HelperUtils.getAdminSessionForAuthenticatedUser();

try {
schemeService.getSchemeBySchemeId(schemeId);
Expand All @@ -50,22 +84,11 @@ public ByteArrayOutputStream exportSpotlightChecks(Integer schemeId) {
+ " is unable to access mandatory questions with scheme id " + schemeId);
}

final List<GrantMandatoryQuestions> grantMandatoryQuestions = getGrantMandatoryQuestionBySchemeAndCompletedStatus(
schemeId);
log.info("Found {} mandatory questions in COMPLETED state for scheme ID {}", grantMandatoryQuestions.size(),
schemeId);

List<List<String>> exportData = new ArrayList<>();
grantMandatoryQuestions.forEach(grantMandatoryQuestion -> {
try {
exportData.add(buildSingleSpotlightRow(grantMandatoryQuestion));
}
catch (SpotlightExportException e) {
log.error("Problem extracting data: " + e.getMessage());
}
});

return XlsxGenerator.createResource(SpotlightHeaders.SPOTLIGHT_HEADERS, exportData);
return grantMandatoryQuestions.stream()
.map(grantMandatoryQuestion -> buildSingleSpotlightRow(grantMandatoryQuestion, addOrgType)).toList();
}

static String mandatoryValue(Integer id, String identifier, String value) {
Expand Down Expand Up @@ -98,59 +121,42 @@ static String combineAddressLines(String addressLine1, String addressLine2) {
* headers are added or the ordering is changed in SPOTLIGHT_HEADERS, this will need
* manually reflected here.
*/
public List<String> buildSingleSpotlightRow(GrantMandatoryQuestions grantMandatoryQuestions) {
public List<String> buildSingleSpotlightRow(GrantMandatoryQuestions grantMandatoryQuestions, boolean addOrgType) {
try {

final Integer schemeId = grantMandatoryQuestions.getSchemeEntity().getId();

final String gapId = grantMandatoryQuestions.getGapId();
final String organisationName = grantMandatoryQuestions.getName();
final String addressStreet = combineAddressLines(grantMandatoryQuestions.getAddressLine1(),
grantMandatoryQuestions.getAddressLine2());
final String addressTown = grantMandatoryQuestions.getCity();
final String addressCounty = grantMandatoryQuestions.getCounty();
final String postcode = grantMandatoryQuestions.getPostcode();
final String charityNumber = grantMandatoryQuestions.getCharityCommissionNumber();
final String companyNumber = grantMandatoryQuestions.getCompaniesHouseNumber();
final String applicationAmount = grantMandatoryQuestions.getFundingAmount() == null ? null
: grantMandatoryQuestions.getFundingAmount().toString();

List<String> row = new ArrayList<>();
row.add(mandatoryValue(schemeId, "gap id", gapId));
row.add(mandatoryValue(schemeId, "organisation name", organisationName));
row.add(addressStreet);
row.add(addressTown);
row.add(addressCounty);
row.add(mandatoryValue(schemeId, "postcode", postcode));
row.add(mandatoryValue(schemeId, "application amount", applicationAmount));
row.add(charityNumber);
row.add(companyNumber);
final List<String> row = new ArrayList<>(
List.of(mandatoryValue(schemeId, "gap id", grantMandatoryQuestions.getGapId()),
mandatoryValue(schemeId, "organisation name", grantMandatoryQuestions.getName()),
combineAddressLines(grantMandatoryQuestions.getAddressLine1(),
grantMandatoryQuestions.getAddressLine2()),
grantMandatoryQuestions.getCity(), grantMandatoryQuestions.getCounty(),
mandatoryValue(schemeId, "postcode", grantMandatoryQuestions.getPostcode()),
mandatoryValue(schemeId, "application amount",
grantMandatoryQuestions.getFundingAmount().toString()),
grantMandatoryQuestions.getCharityCommissionNumber(),
grantMandatoryQuestions.getCompaniesHouseNumber()));
if (addOrgType) {
row.add(mandatoryValue(schemeId, "organisation type", grantMandatoryQuestions.getOrgType().toString()));
}
row.add(""); // similarities data - should always be blank

return row;
}
catch (NullPointerException e) {
throw new SpotlightExportException("Unable to find mandatory question data: " + e.getMessage());
}
}

public String generateExportFileName(Integer schemeId) {
SchemeDTO schemeDTO = schemeService.getSchemeBySchemeId(schemeId);
String ggisReference = schemeDTO.getGgisReference();
String schemeName = schemeDTO.getName().replace(" ", "_").replaceAll("[^A-Za-z0-9_]", "");
String dateString = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
public String generateExportFileName(Integer schemeId, String orgType) {
final SchemeDTO schemeDTO = schemeService.getSchemeBySchemeId(schemeId);
final String ggisReference = schemeDTO.getGgisReference();
final String schemeName = schemeDTO.getName().replace(" ", "_").replaceAll("[^A-Za-z0-9_]", "");
final String dateString = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());

return dateString + "_" + ggisReference + "_" + schemeName + ".xlsx";
return dateString + "_" + ggisReference + "_" + schemeName + (orgType == null ? "" : "_" + orgType) + ".xlsx";
}

public boolean hasCompletedMandatoryQuestions(Integer schemeId) {
try {
return !getGrantMandatoryQuestionBySchemeAndCompletedStatus(schemeId).isEmpty();
}
catch (NotFoundException ex) {
return false;
}

return grantMandatoryQuestionRepository.existsBySchemeEntity_IdAndCompletedStatus(schemeId);
}

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

import gov.cabinetoffice.gap.adminbackend.utils.XlsxGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
@RequiredArgsConstructor
public class ZipService {

public ByteArrayOutputStream createZip(List<String> headersList, List<List<List<String>>> dataList,
List<String> filenames) {
ByteArrayOutputStream zipStream = new ByteArrayOutputStream();

try (ZipOutputStream zipOut = new ZipOutputStream(zipStream)) {
for (int i = 0; i < dataList.size(); i++) {
List<List<String>> data = dataList.get(i);
if (!data.isEmpty()) {
final String filename = filenames.get(i);

final ByteArrayOutputStream excelStream = XlsxGenerator.createResource(headersList, data);

final ZipEntry entry = new ZipEntry(filename);
zipOut.putNextEntry(entry);
zipOut.write(excelStream.toByteArray());
zipOut.closeEntry();
}
}
}
catch (IOException e) {
throw new RuntimeException("Failed to create ZIP file: " + e.getMessage());
}

return zipStream;
}

}
Loading

0 comments on commit eccf93b

Please sign in to comment.