Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Upcoming Version / (WIP)
Improvements:
* Stabilized consul registration and health checks
* [ODRC-24](https://openlmis.atlassian.net/browse/ODRC-24) Global header and translations implemented for reports
* [OLMIS-8231](https://openlmis.atlassian.net/browse/OLMIS-8231): Preserve Jasper template parameters order as declared in the .jrxml file.
* [OLMIS-8235](https://openlmis.atlassian.net/browse/OLMIS-8235): Add override query parameter to Jasper template upload to safely replace an existing template.

1.5.0 / 2025-11-27
==================
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/openlmis/report/domain/JasperTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
Expand Down Expand Up @@ -72,6 +73,7 @@ public class JasperTemplate extends BaseEntity {
fetch = FetchType.EAGER,
orphanRemoval = true)
@Fetch(FetchMode.SELECT)
@OrderBy("displayOrder ASC NULLS LAST")
private List<JasperTemplateParameter> templateParameters;

@ManyToMany(fetch = FetchType.EAGER)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ public class JasperTemplateParameter extends BaseEntity {
@Setter
private Boolean required;

@Column
@Getter
@Setter
private Integer displayOrder;

/**
* Create new instance of JasperTemplateParameter based on given {@link Importer}.
*
Expand All @@ -144,6 +149,7 @@ public static JasperTemplateParameter newInstance(Importer importer) {
jasperTemplateParameter.setSelectProperty(importer.getSelectProperty());
jasperTemplateParameter.setDisplayProperty(importer.getDisplayProperty());
jasperTemplateParameter.setRequired(importer.getRequired());
jasperTemplateParameter.setDisplayOrder(importer.getDisplayOrder());
jasperTemplateParameter.setOptions(importer.getOptions());
jasperTemplateParameter.setDependencies(importer.getDependencies()
.stream()
Expand Down Expand Up @@ -171,6 +177,7 @@ public void export(Exporter exporter) {
exporter.setSelectProperty(selectProperty);
exporter.setDisplayProperty(displayProperty);
exporter.setRequired(required);
exporter.setDisplayOrder(displayOrder);
exporter.setOptions(options);
exporter.setDependencies(dependencies
.stream()
Expand Down Expand Up @@ -212,6 +219,8 @@ public interface Exporter {

void setRequired(Boolean required);

void setDisplayOrder(Integer displayOrder);

void setOptions(List<String> options);

void setDependencies(List<JasperTemplateParameterDependencyDto> dependencies);
Expand Down Expand Up @@ -242,6 +251,8 @@ public interface Importer {

Boolean getRequired();

Integer getDisplayOrder();

List<String> getOptions();

List<JasperTemplateParameterDependencyDto> getDependencies();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class JasperTemplateParameterDto implements JasperTemplateParameter.Expor
private String displayProperty;
private String description;
private Boolean required;
private Integer displayOrder;
private List<String> options;
private List<JasperTemplateParameterDependencyDto> dependencies;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package org.openlmis.report.service;

import static org.apache.commons.lang3.BooleanUtils.isTrue;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.openlmis.report.i18n.AuthorizationMessageKeys.ERROR_RIGHT_NOT_FOUND;
Expand Down Expand Up @@ -100,17 +101,19 @@ public class JasperTemplateService {
private ReportCategoryRepository reportCategoryRepository;

/**
* Saves a template with given name.
* If template already exists, only description and required rights are updated.
* Saves a template with given name. If a template with that name already exists,
* the upload is rejected unless override=true is passed; in that case the existing
* template is updated in place (id preserved).
*
* @param file report file
* @param name name of report
* @param description report's description
* @param override when true, replace an existing template with the same name
* @return saved report template
*/
public JasperTemplate saveTemplate(
MultipartFile file, String name, String description, List<String> requiredRights,
String category) throws ReportingException {
String category, Boolean override) throws ReportingException {
validateRequiredRights(requiredRights);
JasperTemplate jasperTemplate = jasperTemplateRepository.findByName(name);

Expand All @@ -129,6 +132,10 @@ public JasperTemplate saveTemplate(
.category(reportCategory.get())
.build();
} else {
if (!isTrue(override)) {
throw new ValidationMessageException(
new Message(ERROR_REPORTING_TEMPLATE_EXIST, name));
}
jasperTemplate.setDescription(description);
jasperTemplate.getRequiredRights().clear();
jasperTemplate.getRequiredRights().addAll(requiredRights);
Expand Down Expand Up @@ -400,15 +407,12 @@ void saveWithParameters(JasperTemplate jasperTemplate) {
}

/**
* Validate ".jrmxl" file and insert if template not exist. If this name of template already
* exist, remove older template and insert new.
* Validate ".jrxml" file and persist the template. Performs UPDATE in place
* when given a managed entity (preserves id). The previous delete-then-insert
* pattern caused a managed-entity conflict on duplicate-name uploads.
*/
void validateFileAndSaveTemplate(JasperTemplate jasperTemplate, MultipartFile file)
throws ReportingException {
JasperTemplate templateTmp = jasperTemplateRepository.findByName(jasperTemplate.getName());
if (templateTmp != null) {
jasperTemplateRepository.deleteById(templateTmp.getId());
}
validateFileAndSetData(jasperTemplate, file);
saveWithParameters(jasperTemplate);
}
Expand Down Expand Up @@ -454,12 +458,14 @@ private void processJrParameters(JasperTemplate jasperTemplate, JRParameter[] jr
throws ReportingException {
ArrayList<JasperTemplateParameter> parameters = new ArrayList<>();
Set<ReportImage> images = new HashSet<>();
int order = 0;

for (JRParameter jrParameter : jrParameters) {
if (!jrParameter.isSystemDefined()) {
if (jrParameter.isForPrompting()) {
JasperTemplateParameter jasperTemplateParameter = createParameter(jrParameter);
jasperTemplateParameter.setTemplate(jasperTemplate);
jasperTemplateParameter.setDisplayOrder(order++);
parameters.add(jasperTemplateParameter);
} else if (Image.class.getName().equals(jrParameter.getValueClassName())) {
String name = jrParameter.getName();
Expand All @@ -472,8 +478,22 @@ private void processJrParameters(JasperTemplate jasperTemplate, JRParameter[] jr
}
}

jasperTemplate.setTemplateParameters(parameters);
jasperTemplate.setReportImages(images);
// Mutate existing collections instead of replacing references, otherwise
// Hibernate's orphanRemoval=true throws "collection no longer referenced"
// when called on a managed entity (override=true update path).
if (jasperTemplate.getTemplateParameters() == null) {
jasperTemplate.setTemplateParameters(new ArrayList<>());
} else {
jasperTemplate.getTemplateParameters().clear();
}
jasperTemplate.getTemplateParameters().addAll(parameters);

if (jasperTemplate.getReportImages() == null) {
jasperTemplate.setReportImages(new HashSet<>());
} else {
jasperTemplate.getReportImages().clear();
}
jasperTemplate.getReportImages().addAll(images);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ public class JasperTemplateController extends BaseController {
@ResponseStatus(HttpStatus.OK)
public void createJasperReportTemplate(
@RequestPart("file") MultipartFile file, String name, String description,
String[] requiredRights, String category) throws ReportingException {
String[] requiredRights, String category,
@RequestParam(value = "override", required = false) Boolean override)
throws ReportingException {
permissionService.canEditReportTemplates();

LOGGER.debug("Saving template with name: " + name);
Expand All @@ -120,7 +122,7 @@ public void createJasperReportTemplate(
? Collections.emptyList() : Arrays.asList(requiredRights);

JasperTemplate template = jasperTemplateService
.saveTemplate(file, name, description, rightList, category);
.saveTemplate(file, name, description, rightList, category, override);

LOGGER.debug("Saved template with id: " + template.getId());
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/api-definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ resourceTypes:
type: string
required: false
repeat: false
override:
displayName: Replace existing template with the same name
type: boolean
required: false
repeat: false
responses:
200:
403:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE template_parameters ADD COLUMN displayorder INTEGER;
2 changes: 2 additions & 0 deletions src/main/resources/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ report.error.jasper.template.notFound=Jasper template not found in the system.
report.error.jasper.report.generation=Error while generating Jasper report.
report.error.jasper.report.format.unknown=Unknown report file format [{0}] specified.

report.error.reporting.template.exists=Jasper template with name "{0}" already exists. Pass override=true to replace it.

report.error.dashboardReport.name.duplicated=Dashboard report with name {0} already exists.
report.error.dashboardReport.notFound=Dashboard report with id {0} can not be found.
report.error.dashboardReport.id.mismatch=Error dashboard report id mismatch.
Expand Down
Loading
Loading