true if exporting the given {@link Questionnaire}
* object worked, false otherwise.
*/
- public boolean export(final Encounter encounter, final Questionnaire questionnaire, boolean isTest) {
+ public boolean export(final Encounter encounter, final Questionnaire questionnaire,
+ boolean isTest) {
assert encounter != null : "The Encounter was null";
assert questionnaire != null : "The Questionnaire was null";
Setnull.
- * @param exportTemplate The {@link ExportTemplate} object to export. Must not be
- * null.
- * @return {@link ExportStatus} for the given {@link ExportTemplate}.
+ * @param encounter the {@link Encounter} whose responses should be exported. Must not be
+ * {@code null}.
+ * @param exportTemplate the {@link ExportTemplate} to export. Must not be {@code null}.
+ * @return the assembled export content as a {@link String}
+ * @throws Exception if no exporter implementation exists for the export template's type, or if
+ * preparing or building the export content fails
+ */
+ public String buildExportContent(Encounter encounter, ExportTemplate exportTemplate)
+ throws Exception {
+ return prepareExporter(encounter, exportTemplate).getExportContent();
+ }
+
+ /**
+ * @param encounter
+ * @param exportTemplate
*/
- private ExportStatus exportEncounter(final Encounter encounter, final ExportTemplate exportTemplate) throws Exception{
+ private EncounterExporterTemplate prepareExporter(Encounter encounter,
+ ExportTemplate exportTemplate) throws Exception {
ExportTemplateType exportTemplateType = exportTemplate.getExportTemplateType();
// Instantiate a new object based on the type of the export template
// with the ConfigurationGroupDao and ConfigurationDao from the
// context
EncounterExporterTemplate exporter = exportTemplateType.createNewExporterInstance(
configurationDao);
- // If no implementation for the exporter exists throw an exception
+
if (exporter == null) {
- LOGGER.error("No Implementation found for {}",
- exportTemplate.getExportTemplateType());
+ LOGGER.error("No Implementation found for {}", exportTemplate.getExportTemplateType());
throw new Exception(
"No Implementation found for " + exportTemplate.getExportTemplateType());
}
+
// Initialize the exporter
exporter.load(encounter, exportTemplate);
@@ -227,9 +241,23 @@ private ExportStatus exportEncounter(final Encounter encounter, final ExportTemp
String value = this.getFormattedValue(encounter, rule);
exporter.write(rule.getExportField(), value);
}
+ return exporter;
+ }
+ /**
+ * Exports the {@link ExportTemplate} object of a given {@link Encounter Encounter} object.
+ *
+ * @param encounter An object of {@link Encounter}. Must not be
+ * null.
+ * @param exportTemplate The {@link ExportTemplate} object to export. Must not be
+ * null.
+ * @return {@link ExportStatus} for the given {@link ExportTemplate}.
+ */
+ private ExportStatus exportEncounter(final Encounter encounter,
+ final ExportTemplate exportTemplate) throws Exception {
+ ExportTemplateType exportTemplateType = exportTemplate.getExportTemplateType();
// Flush out the export template to the export folder
- return exporter.flush();
+ return prepareExporter(encounter, exportTemplate).flush();
}
/**
@@ -254,7 +282,7 @@ private String getFormattedValue(final Encounter encounter, final ExportRule exp
String value = "";
// rule is of the type answer
if (exportRule instanceof ExportRuleAnswer ruleAnswer) {
- // there exists an response to the answer
+ // there exists a response to the answer
if (answerResponseMap.containsKey(ruleAnswer.getAnswer())) {
// get the response value based on the export rule
value = this.getAnswerValue(ruleAnswer,
diff --git a/src/main/java/de/imi/mopat/io/EncounterExporterTemplate.java b/src/main/java/de/imi/mopat/io/EncounterExporterTemplate.java
index f584f918..455a7fce 100644
--- a/src/main/java/de/imi/mopat/io/EncounterExporterTemplate.java
+++ b/src/main/java/de/imi/mopat/io/EncounterExporterTemplate.java
@@ -41,4 +41,20 @@ public interface EncounterExporterTemplate {
* @throws java.lang.Exception if flush to disk went wrong
*/
ExportStatus flush() throws Exception;
+
+ /**
+ * Builds and returns the fully assembled export content for the currently loaded and filled
+ * export template, without triggering any of the side effects performed by {@link #flush()}
+ * (e.g. writing the export to disk, sending it to a communication server, or delivering it via
+ * HL7v2). This method is intended for on-demand, read-only access to the export data, such as
+ * a manual download triggered by the user.
+
+ * Repeated calls to this method are safe and will not cause duplicate exports, since no data
+ * is persisted or transmitted as part of its execution.
+ *
+ * @return the assembled export content as a {@link String}, ready to be presented to the user
+ * (e.g. as a file download)
+ * @throws java.lang.Exception if the export content could not be built
+ */
+ String getExportContent() throws Exception;
}
diff --git a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirDstu3.java b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirDstu3.java
index 7843d9b1..9c17549d 100644
--- a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirDstu3.java
+++ b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirDstu3.java
@@ -36,7 +36,7 @@
public class EncounterExporterTemplateFhirDstu3 implements EncounterExporterTemplate {
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(
- EncounterExporterTemplateHL7v2.class);
+ EncounterExporterTemplateFhirDstu3.class);
private static final SimpleDateFormat FILENAMEDATEFORMAT = new SimpleDateFormat(
"dd.MM.yyyy_HH.mm.ss");
@@ -307,6 +307,11 @@ public ExportStatus flush() throws Exception {
return exportStatus;
}
+ @Override
+ public String getExportContent() throws Exception {
+ return FhirDstu3Helper.decodeResourceToString(questionnaireResponse, false);
+ }
+
/**
* Handles the HL7 export process by generating and transmitting an HL7 message.
*
diff --git a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR4b.java b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR4b.java
index 7d6e6488..148d1af5 100644
--- a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR4b.java
+++ b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR4b.java
@@ -298,6 +298,11 @@ public ExportStatus flush() throws Exception {
return exportStatus;
}
+//TODO
+ @Override
+ public String getExportContent() throws Exception {
+ return FhirR4bHelper.decodeResourceToString(questionnaireResponse, false);
+ }
/**
* Handles the HL7 export process by generating and transmitting an HL7 message.
diff --git a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR5.java b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR5.java
index 080c7a81..ac717bc1 100644
--- a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR5.java
+++ b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateFhirR5.java
@@ -307,6 +307,11 @@ public ExportStatus flush() throws Exception {
return exportStatus;
}
+//TODO
+ @Override
+ public String getExportContent() throws Exception {
+ return FhirR5Helper.decodeResourceToString(questionnaireResponse, false);
+ }
/**
* Handles the HL7 export process by generating and transmitting an HL7 message.
diff --git a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateHL7v2.java b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateHL7v2.java
index f9462fb6..86e774cf 100644
--- a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateHL7v2.java
+++ b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateHL7v2.java
@@ -52,7 +52,7 @@ public class EncounterExporterTemplateHL7v2 implements EncounterExporterTemplate
private ExportTemplate exportTemplate;
/**
- * Constructor with given {@link ConfigurationDao} to get configuration informations within this
+ * Constructor with given {@link ConfigurationDao} to get configuration information within this
* instance.
*
* @param configurationDao The {@link ConfigurationDao} from the context.
@@ -121,10 +121,7 @@ public ExportStatus flush() throws Exception {
String clientPKCSPath = null;
String clientPKCSPassword = null;
String serverCertificatePath = null;
- String sendingFacility = null;
- String receivingApplication = null;
- String receivingFacility = null;
- String obrFillerOrderNumber = null;
+
// Get export configurations
for (Configuration configuration : exportTemplate.getConfigurationGroup()
.getConfigurations()) {
@@ -163,25 +160,31 @@ public ExportStatus flush() throws Exception {
if (configuration.getAttribute().equals("clientPKCSPassword")) {
clientPKCSPassword = configuration.getValue();
}
- if (configuration.getAttribute().equals("sendingFacility")) {
- sendingFacility = configuration.getValue();
- }
- if (configuration.getAttribute().equals("receivingApplication")) {
- receivingApplication = configuration.getValue();
- }
- if (configuration.getAttribute().equals("receivingFacility")) {
- receivingFacility = configuration.getValue();
- }
- if (configuration.getAttribute().equals("OBRFillerOrderNumber")) {
- obrFillerOrderNumber = configuration.getValue();
- }
}
- return doHandleExports(
- isExportInDirectory, exportPathDirectory, isExportServer, hostname,
+ HL7MessageConfig hl7MessageConfig = readHL7MessageConfig();
+
+ return doHandleExports(isExportInDirectory, exportPathDirectory, isExportServer, hostname,
port, useTLS, useClientAuth, clientPKCSPath, clientPKCSPassword, serverCertificatePath,
- sendingFacility, receivingApplication, receivingFacility, obrFillerOrderNumber
- );
+ hl7MessageConfig.sendingFacility(), hl7MessageConfig.receivingApplication(),
+ hl7MessageConfig.receivingFacility(), hl7MessageConfig.obrFillerOrderNumber());
+ }
+
+ @Override
+ public String getExportContent() throws Exception {
+ HL7MessageConfig config = readHL7MessageConfig();
+ if (config.sendingFacility() == null || config.receivingApplication() == null
+ || config.receivingFacility() == null || config.obrFillerOrderNumber() == null) {
+ // if any of the config values is missing: throw error
+ throw new Exception(
+ "Missing configuration for sendingFacility, receivingApplication, receivingFacility or OBRFillerOrderNumber. "
+ + "Could not build ExportContent");
+ }
+ // else build message for export
+ ORU_R01 hl7Message = buildHL7Message(config.sendingFacility(),
+ config.receivingApplication(), config.receivingFacility(),
+ config.obrFillerOrderNumber());
+ return hl7Message.encode();
}
/**
@@ -213,27 +216,18 @@ public ExportStatus flush() throws Exception {
* @throws Exception If an error occurs during message construction, file export, or server
* communication.
*/
- private ExportStatus doHandleExports(
- Boolean isExportInDirectory, String exportPathDirectory, Boolean isExportServer,
- String hostname, Integer port, Boolean useTLS, Boolean useClientAuth,
- String clientPKCSPath, String clientPKCSPassword, String serverCertificatePath,
- String sendingFacility, String receivingApplication,
- String receivingFacility, String obrFillerOrderNumber
- ) throws Exception {
- HL7MessageHelper hl7MessageHelper = new HL7MessageHelper();
+ private ExportStatus doHandleExports(Boolean isExportInDirectory, String exportPathDirectory,
+ Boolean isExportServer, String hostname, Integer port, Boolean useTLS,
+ Boolean useClientAuth, String clientPKCSPath, String clientPKCSPassword,
+ String serverCertificatePath, String sendingFacility, String receivingApplication,
+ String receivingFacility, String obrFillerOrderNumber) throws Exception {
+
+ ORU_R01 hl7Message = buildHL7Message(sendingFacility, receivingApplication,
+ receivingFacility, obrFillerOrderNumber);
//Properties have to be set, at least empty strings
- if (sendingFacility != null && receivingApplication != null
- && receivingFacility != null && obrFillerOrderNumber != null) {
-
- // Build Template specific message
- String output = buildHL7MessageContent();
- ORU_R01 hl7Message = hl7MessageHelper.createMessageWithBlob(
- exportTemplate, encounter, sendingFacility, receivingApplication,
- receivingFacility, obrFillerOrderNumber, output
- );
- hl7Message = hl7MessageHelper.overwriteMsh3NamespaceId(hl7Message,
- getNode("Formname").getTextContent());
+ if (sendingFacility != null && receivingApplication != null && receivingFacility != null
+ && obrFillerOrderNumber != null) {
//Handle Server Export
try {
@@ -255,12 +249,68 @@ private ExportStatus doHandleExports(
//Return Success if no exception was thrown
return ExportStatus.SUCCESS;
} else {
- LOGGER.error("Missing configuration for sendingFacility, receivingApplication, " +
- "receivingFacility or OBRFillerOrderNumber. Could not export message.");
+ LOGGER.error("Missing configuration for sendingFacility, receivingApplication, "
+ + "receivingFacility or OBRFillerOrderNumber. Could not export message.");
return ExportStatus.FAILURE;
}
}
+ /**
+ * Builds HL7 message without sending or saving it.
+ *
+ * @param sendingFacility The identifier of the facility sending the message.
+ * @param receivingApplication Application identifier of the recipient.
+ * @param receivingFacility Identifier of the facility receiving the message.
+ * @param obrFillerOrderNumber Unique order number for the associated medical order.
+ * @return hl7Message
+ * @throws Exception If an error occurs during message construction, file export, or server
+ * communication.
+ */
+ private ORU_R01 buildHL7Message(String sendingFacility, String receivingApplication,
+ String receivingFacility, String obrFillerOrderNumber) throws Exception {
+ // Build Template specific message
+ String output = buildHL7MessageContent();
+ HL7MessageHelper hl7MessageHelper = new HL7MessageHelper();
+ ORU_R01 hl7Message = hl7MessageHelper.createMessageWithBlob(exportTemplate, encounter,
+ sendingFacility, receivingApplication, receivingFacility, obrFillerOrderNumber, output);
+ hl7Message = hl7MessageHelper.overwriteMsh3NamespaceId(hl7Message,
+ getNode("Formname").getTextContent());
+ return hl7Message;
+ }
+
+ /**
+ * Reads configuration values, that are relevant for building an HL7 Message.
+ *
+ * @return a {@param HL7MessageConfig} holding facility related values, may be null
+ */
+
+ private HL7MessageConfig readHL7MessageConfig() {
+ String sendingFacility = null;
+ String receivingApplication = null;
+ String receivingFacility = null;
+ String obrFillerOrderNumber = null;
+
+ for (Configuration configuration : exportTemplate.getConfigurationGroup()
+ .getConfigurations()) {
+ if (configuration.getAttribute().equals("sendingFacility")) {
+ sendingFacility = configuration.getValue();
+ }
+ if (configuration.getAttribute().equals("receivingApplication")) {
+ receivingApplication = configuration.getValue();
+ }
+ if (configuration.getAttribute().equals("receivingFacility")) {
+ receivingFacility = configuration.getValue();
+ }
+ if (configuration.getAttribute().equals("OBRFillerOrderNumber")) {
+ obrFillerOrderNumber = configuration.getValue();
+ }
+ }
+
+ return new HL7MessageConfig(sendingFacility, receivingApplication, receivingFacility,
+ obrFillerOrderNumber);
+
+ }
+
/**
* Handles the export of an HL7 message to a server, optionally utilizing TLS and client
* authentication for secure communication. The method performs the export only if the
@@ -292,15 +342,12 @@ private ExportStatus doHandleExports(
* @throws Exception If an error occurs during the export process, such as failure in message
* transmission, keystore creation, or TLS setup.
*/
- private void doHandleServerExport(
- Boolean isExportServer, String hostname, Integer port, Boolean useTLS,
- Boolean useClientAuth, String clientPKCSPath, String clientPKCSPassword,
- String serverCertificatePath, ORU_R01 hl7Message
- ) throws Exception {
+ private void doHandleServerExport(Boolean isExportServer, String hostname, Integer port,
+ Boolean useTLS, Boolean useClientAuth, String clientPKCSPath, String clientPKCSPassword,
+ String serverCertificatePath, ORU_R01 hl7Message) throws Exception {
HL7MessageHelper hl7MessageHelper = new HL7MessageHelper();
- if (Boolean.TRUE.equals(isExportServer) &&
- hostname != null && !hostname.isEmpty() && port != null
- ) {
+ if (Boolean.TRUE.equals(isExportServer) && hostname != null && !hostname.isEmpty()
+ && port != null) {
KeyStore keyStore = null;
if (Boolean.TRUE.equals(useTLS)) {
if (Boolean.TRUE.equals(useClientAuth)) {
@@ -311,8 +358,8 @@ private void doHandleServerExport(
}
}
- hl7MessageHelper.sendMessageViaComServer(hostname, port, hl7Message, useTLS,
- keyStore, clientPKCSPassword);
+ hl7MessageHelper.sendMessageViaComServer(hostname, port, hl7Message, useTLS, keyStore,
+ clientPKCSPassword);
}
}
@@ -330,11 +377,8 @@ private void doHandleServerExport(
* @throws Exception If an error occurs during the export process, such as issues with encoding
* the message or writing to the file system.
*/
- private void doHandleFilebasedExport(
- Boolean isExportInDirectory,
- String exportPathDirectory,
- ORU_R01 hl7Message
- ) throws Exception {
+ private void doHandleFilebasedExport(Boolean isExportInDirectory, String exportPathDirectory,
+ ORU_R01 hl7Message) throws Exception {
if (Boolean.TRUE.equals(isExportInDirectory)) {
// Make sure the path exists
File path = new File(exportPathDirectory);
@@ -476,4 +520,9 @@ private String createHL7FileName() {
+ UNDERSCORE + HL7XMLFileNameDateFormat.format(new Date()) + DOT + HL7_SUFFIX;
return result;
}
+
+ private record HL7MessageConfig(String sendingFacility, String receivingApplication,
+ String receivingFacility, String obrFillerOrderNumber) {
+
+ }
}
diff --git a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateODM.java b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateODM.java
index e10373d2..8ea58fc1 100644
--- a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateODM.java
+++ b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateODM.java
@@ -24,6 +24,7 @@
import de.unimuenster.imi.org.cdisc.odm.v132.ODMcomplexTypeDefinitionSubjectData;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
+import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -31,6 +32,7 @@
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -471,6 +473,13 @@ public ExportStatus flush() throws Exception {
return exportStatus;
}
+ @Override
+ public String getExportContent() throws Exception {
+ ByteArrayOutputStream outputstream = new ByteArrayOutputStream();
+ odmProcessor.marshal(exportODM, outputstream);
+ return outputstream.toString(StandardCharsets.UTF_8);
+ }
+
/**
* Exports the resultant ODM to a given path.
*
diff --git a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateREDCap.java b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateREDCap.java
index 9ef89b29..b76547d2 100644
--- a/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateREDCap.java
+++ b/src/main/java/de/imi/mopat/io/impl/EncounterExporterTemplateREDCap.java
@@ -179,6 +179,11 @@ public ExportStatus flush() throws Exception {
return exportStatus;
}
+ @Override
+ public String getExportContent() throws Exception {
+ return mapper.writeValueAsString(List.of(exportJSON));
+ }
+
/**
* Exports the resultant REDCap JSON to a given path
*
@@ -202,6 +207,7 @@ public void exportToDirectory(final String exportPath) throws Exception {
// Write to disk
File exportFile = new File(subDirectory, this.createFileName());
+ //TODO: check if, JSON or string should be written mapper.writeValue(exportFile, List.of(exportJSON));
mapper.writeValue(exportFile, "[" + mapper.writeValueAsString(exportJSON) + "]");
}
diff --git a/src/main/java/de/imi/mopat/io/importer/MoPatQuestionnaireImporter.java b/src/main/java/de/imi/mopat/io/importer/MoPatQuestionnaireImporter.java
index 62409894..09052957 100644
--- a/src/main/java/de/imi/mopat/io/importer/MoPatQuestionnaireImporter.java
+++ b/src/main/java/de/imi/mopat/io/importer/MoPatQuestionnaireImporter.java
@@ -5,7 +5,7 @@
import de.imi.mopat.dao.OperatorDao;
import de.imi.mopat.dao.QuestionnaireDao;
import de.imi.mopat.helper.controller.Constants;
-import de.imi.mopat.helper.controller.QuestionnaireVersionGroupService;
+import de.imi.mopat.service.QuestionnaireVersionGroupService;
import de.imi.mopat.helper.controller.StringUtilities;
import de.imi.mopat.model.Answer;
import de.imi.mopat.model.ImageAnswer;
diff --git a/src/main/java/de/imi/mopat/io/importer/fhir/FhirDstu3Helper.java b/src/main/java/de/imi/mopat/io/importer/fhir/FhirDstu3Helper.java
index f4b17b04..68c046d3 100644
--- a/src/main/java/de/imi/mopat/io/importer/fhir/FhirDstu3Helper.java
+++ b/src/main/java/de/imi/mopat/io/importer/fhir/FhirDstu3Helper.java
@@ -196,11 +196,13 @@ public static boolean validateFileWithFhirInstanceValidator(final String fhirRes
ValidationResult result = validator.validateWithResult(fhirResourceString);
ListOnly bundles that are published and assigned to at least one clinic are included. + * Incomplete encounters are added only if their bundle is present in the resulting map.
+ * + * @param encounterDTO encounter containing the case number used to load incomplete encounters + * @return sorted map of bundles to language-specific lists of incomplete encounters + */ + public SortedMapAn encounter is treated as unavailable if the UUID is missing, no encounter exists for it, + * the encounter is already completed, or no scheduled encounter is assigned.
+ * + * @param uuid UUID of the encounter to validate + * @return {@code true} if the encounter is missing, completed, or otherwise unavailable + */ + public boolean isEncounterForUUIDCompletedOrUnavailable(String uuid) { + if (uuid == null || uuid.isEmpty()) { + return true; + } + + Encounter encounter = encounterDao.getElementByUUID(uuid); + return isCompletedOrUnavailable(encounter); + } + + /** + * Returns whether the given encounter is completed or cannot be used. + * + * @param encounter encounter to check + * @return {@code true} if the encounter is {@code null}, already ended, + * or has no scheduled encounter assigned + */ + public boolean isCompletedOrUnavailable(Encounter encounter) { + return encounter == null + || encounter.getEndTime() != null + || encounter.getEncounterScheduled() == null; + } + + /** + * Loads the encounter identified by the given UUID and maps it to an {@link EncounterDTO}. + * + * @param uuid UUID of the encounter to load + * @return mapped encounter DTO + */ + public EncounterDTO getEncounterDTOForUUID(String uuid) { + return encounterDTOMapper.apply(true, encounterDao.getElementByUUID(uuid)); + } + + /** + * Sets and persists the start time of the given encounter if it is accessed for the first time. + * + *An encounter is considered to be accessed for the first time if it is not completed + * and no question has been seen yet.
+ * + * @param encounterDTO encounter to initialize + */ + public void startEncounterIfFirstAccess(EncounterDTO encounterDTO) { + if (!isFirstAccess(encounterDTO)) { + return; + } + + Timestamp startTime = new Timestamp(new Date().getTime()); + encounterDTO.setStartTime(startTime); + + Encounter encounter = encounterDao.getElementById(encounterDTO.getId()); + encounter.setStartTime(startTime); + encounterDao.merge(encounter); + } + + /** + * Returns whether the given encounter is being accessed for the first time. + * + * @param encounterDTO encounter to check + * @return {@code true} if the encounter is not completed and has no last seen question + */ + private boolean isFirstAccess(EncounterDTO encounterDTO) { + return encounterDTO.getEndTime() == null + && encounterDTO.getLastSeenQuestionId() == null; + } + + /** + * Returns whether the language selection step can be skipped for the given encounter. + * + *The selection is skipped if the encounter is resumed or if the bundle provides only one + * available language.
+ * + * @param encounterDTO encounter to evaluate + * @return {@code true} if language selection is not needed + */ + public boolean shouldSkipLanguageSelection(EncounterDTO encounterDTO) { + return encounterDTO.getLastSeenQuestionId() != null + || hasOnlyOneAvailableLanguage(encounterDTO); + } + + /** + * Returns whether the bundle of the given encounter provides exactly one available language. + * + * @param encounterDTO encounter whose bundle is checked + * @return {@code true} if only one language is available + */ + private boolean hasOnlyOneAvailableLanguage(EncounterDTO encounterDTO) { + return encounterDTO.getBundleDTO().getAvailableLanguages().size() == 1; + } + + /** + * Returns the bundle language of the given encounter, initializing it if necessary. + * + *If no bundle language is set yet, a matching survey locale is determined and assigned + * to the encounter DTO.
+ * + * @param encounterDTO encounter whose bundle language is resolved + * @return resolved bundle language + */ + public String resolveBundleLanguage(EncounterDTO encounterDTO) { + if (encounterDTO.getBundleLanguage() != null) { + return encounterDTO.getBundleLanguage(); + } + + String resolvedLanguage = determineBundleLanguage(encounterDTO); + encounterDTO.setBundleLanguage(resolvedLanguage); + return resolvedLanguage; + } + + /** + * Determines the most suitable bundle language for the given encounter. + * + *The first available bundle language is matched against the survey locales. + * If no match is found, the current request locale is used as fallback.
+ * + * @param encounterDTO encounter whose bundle language should be determined + * @return resolved language code + */ + private String determineBundleLanguage(EncounterDTO encounterDTO) { + String configuredLanguage = encounterDTO.getBundleDTO().getAvailableLanguages().get(0); + + for (String locale : LocaleHelper.getLocalesUsedInSurvey()) { + if (matchesLanguage(locale, configuredLanguage)) { + return locale; + } + } + + return LocaleContextHolder.getLocale().toString(); + } + + /** + * Returns whether the given locale matches the specified language. + * + *A match is assumed if the locale string contains the full language code + * or its two-character prefix.
+ * + * @param locale locale string to check + * @param language language code to match against + * @return {@code true} if the locale matches the language + */ + private boolean matchesLanguage(String locale, String language) { + return locale.contains(language) + || locale.contains(language.substring(0, 2)); + } + + /** + * Adds all published bundles with at least one assigned clinic to the given map. + * + *Each matching bundle is inserted with an empty language-to-encounters map as its + * value.
+ * + * @param encountersByBundleAndLanguage target map to populate with eligible bundles + */ + private void addPublishedAndAssignedBundlesToMap( + SortedMapEach incomplete encounter is inserted only if its bundle is already present + * in the target map.
+ * + * @param caseNumber caseNumber to fetch encounters for + * @param encountersByBundleAndLanguage target map to enrich with incomplete encounters + */ + private void addIncompleteEncountersForCaseToMap( + String caseNumber, + SortedMapIf the bundle is missing, the encounter is ignored. This typically means the bundle + * is not visible in the current context.
+ * + * @param encountersByBundleAndLanguage map of bundles to their language-specific encounters + * @param incompleteEncounter the incomplete encounter to insert + */ + private void addIncompleteEncounterToMap( + SortedMap