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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

/**
* Server-side validation rule for a question.
* validationType: MAX_LENGTH | MIN_DATE | MAX_DATE | REGEX | MANDATORY_IF
* validationType: MAX_LENGTH | MIN_DATE | MAX_DATE | REGEX | MANDATORY_IF | UNIT | HELPER_TEXT_ENGLISH | HELPER_TEXT_HINDI | AUTO_FILL
*/
@Entity
@Table(name = "t_question_validation", schema = "db_iemr")
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/iemr/flw/domain/iemr/SectionQuestion.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ public class SectionQuestion {
@Column(name = "containsPii", nullable = false)
private Boolean containsPii = false;

/** If true, the client must make sure this question has a value at least once after a new form
* version is sent — e.g. a field added in that version that earlier responses never captured. */
@Column(name = "requiresBackFill", nullable = false)
private Boolean requiresBackFill = false;

/** If true, the client lets the user change this question's answer after the form is submitted. */
@Column(name = "editableAfterSubmit", nullable = false)
private Boolean editableAfterSubmit = false;

/** Soft-delete flag: false means unlinked from its section. Never hard-deleted — historical
* QuestionResponse rows reference questionId as a plain Long outside JPA's cascade graph. */
@Column(name = "isActive", nullable = false)
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/iemr/flw/dto/iemr/SectionQuestionDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public class SectionQuestionDTO {

private Boolean containsPii = false;

/** If true, the client must make sure this question has a value at least once after a new form version is sent. */
private Boolean requiresBackFill = false;

/** If true, the client lets the user change this question's answer after the form is submitted. */
private Boolean editableAfterSubmit = false;

/** Computed at read time: false if this question is a target of any OptionCondition. */
private Boolean visibleByDefault = true;

Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/iemr/flw/masterEnum/ValidationType.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@ public enum ValidationType {
MANDATORY_IF,
/** Display unit for a numeric answer. validationParam = unit label e.g. "sq. m.".
* Informational only — not a rule the answer is checked against. */
UNIT
UNIT,
/** One English helper-text bullet shown under the question. validationParam = the bullet text.
* A question may have several; display them in validationId order. Informational only. */
HELPER_TEXT_ENGLISH,
/** Hindi counterpart of {@link #HELPER_TEXT_ENGLISH}, one bullet per row, in validationId order. */
HELPER_TEXT_HINDI,
/** The client must fill this answer itself instead of asking the user. validationParam = the source,
* e.g. "TODAY" or "CAMP_XRAY_RESULT". Informational only — not a rule the answer is checked against. */
AUTO_FILL
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ private FormSectionDTO buildScreeningSection() {
qs.add(yesNoQuestion("CFU_VISITED_FACILITY", "Have you visited the health facility",
"क्या आपने स्वास्थ्य सुविधा का दौरा किया", 2));

// Status of Clinical Screening — "TPT Eligible" opens the TPT Follow Up form.
// "Full Treatment"/"No Treatment" are meant to route this record elsewhere (confirmed-case
// listing / no action) — that record-routing is out of scope here, so no condition is wired
// for those two options.
SectionQuestionDTO screeningStatus = new SectionQuestionDTO();
screeningStatus.setQuestionUuid("CFU_CLINICAL_SCREENING_STATUS");
screeningStatus.setQuestionText("Status of Clinical Screening");
Expand Down
96 changes: 96 additions & 0 deletions src/main/java/com/iemr/flw/seeder/migration/FormVersionCloner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* AMRIT – Accessible Medical Records via Integrated Technology
* Integrated EHR (Electronic Health Records) Solution
*
* Copyright (C) "Piramal Swasthya Management and Research Institute"
*
* This file is part of AMRIT.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
package com.iemr.flw.seeder.migration;

import com.iemr.flw.domain.iemr.FormVersion;
import com.iemr.flw.dto.iemr.DynamicFormDTO;
import com.iemr.flw.dto.iemr.FormSectionDTO;
import com.iemr.flw.dto.iemr.OptionConditionDTO;
import com.iemr.flw.dto.iemr.QuestionOptionDTO;
import com.iemr.flw.dto.iemr.QuestionValidationDTO;
import com.iemr.flw.dto.iemr.SectionQuestionDTO;
import com.iemr.flw.service.DynamicFormDefinitionService;
import com.iemr.flw.service.DynamicFormReconciliationService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

/**
* Clones a form's latest version into a new one for migrations that must leave the earlier version
* untouched — same mechanism as {@link V001_1_CreateContactTracingVersion2}. Lets several migrations
* in one batch share a single new version: the first to call {@link #ensureVersion} for a form
* creates it, the rest find it already there.
*
* Uses {@link DynamicFormDefinitionService#updateForm}, which copies every active section, question,
* option, validation and condition into a new version and marks the current one isLatest=false. All
* ids are cleared from the copied DTO first: the mapper copies them onto the new entities, and saving
* an entity with an existing id would move that row into the new version instead of copying it.
* Conditions are re-wired by targetQuestionUuid/targetSectionUuid, which are kept.
*/
@Component
@RequiredArgsConstructor
public class FormVersionCloner {

private final DynamicFormDefinitionService definitionService;

/**
* Ensures {@code formUuid}'s latest version is {@code targetVersion}, cloning the previous one into
* it if needed, and returns it. Fails if the latest is neither the target nor the one just before it.
*/
public FormVersion ensureVersion(DynamicFormReconciliationService svc, String formUuid, int targetVersion) {
FormVersion latest = svc.latestVersion(formUuid)
.orElseThrow(() -> new IllegalStateException(formUuid + " not found — must run after its seeder"));
if (latest.getVersionNumber() == targetVersion) {
return latest;
}
if (latest.getVersionNumber() != targetVersion - 1) {
throw new IllegalStateException(formUuid + " is at version " + latest.getVersionNumber()
+ ", expected " + (targetVersion - 1) + " or " + targetVersion);
}

Long formId = latest.getDynamicForm().getFormId();
DynamicFormDTO current = definitionService.getFormDefinition(formId);
clearIds(current);
definitionService.updateForm(formId, current);
return svc.latestVersion(formUuid)
.orElseThrow(() -> new IllegalStateException(formUuid + " version " + targetVersion + " not created"));
}

private void clearIds(DynamicFormDTO form) {
for (FormSectionDTO section : form.getSections()) {
section.setSectionId(null);
for (SectionQuestionDTO question : section.getQuestions()) {
question.setQuestionId(null);
for (QuestionValidationDTO validation : question.getValidations()) {
validation.setValidationId(null);
}
for (QuestionOptionDTO option : question.getOptions()) {
option.setOptionId(null);
for (OptionConditionDTO condition : option.getConditions()) {
condition.setConditionId(null);
condition.setTargetQuestionId(null);
condition.setTargetSectionId(null);
}
}
}
}
}
}
Loading
Loading