Skip to content
Closed
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: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.iemr.common.flw</groupId>
<artifactId>flw-api</artifactId>
<version>3.8.4</version>
<version>3.9.1</version>
<packaging>war</packaging>

<name>FLW-API</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.domain.iemr;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import java.sql.Timestamp;

/**
* One row per applied {@code com.iemr.flw.seeder.migration.FormStructureMigration}, keyed by its
* stable migrationId β€” lets {@code DynamicFormMigrationRunner} skip a migration it has already run.
*/
@Entity
@Table(name = "t_dynamic_form_migration_log", schema = "db_iemr")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DynamicFormMigrationLog {

@Id
@Column(name = "migrationId", length = 50)
private String migrationId;

@CreationTimestamp
@Column(name = "appliedAt", nullable = false, updatable = false)
private Timestamp appliedAt;
}
5 changes: 5 additions & 0 deletions src/main/java/com/iemr/flw/domain/iemr/FormSection.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public class FormSection {
@Column(name = "isEditable", nullable = false)
private Boolean isEditable = false;

/** Soft-delete flag: false means removed from its version. Never hard-deleted β€” historical
* section responses reference it. */
@Column(name = "isActive", nullable = false)
private Boolean isActive = true;

@Column(name = "created_by", length = 100)
private String createdBy;

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/iemr/flw/domain/iemr/FormVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public class FormVersion {
@Column(name = "isLatest", nullable = false)
private Boolean isLatest = true;

/** Soft-delete flag: false means the version is retired. Never hard-deleted β€” historical
* form responses reference their version. */
@Column(name = "isActive", nullable = false)
private Boolean isActive = true;

@Column(name = "createdAt", nullable = false, updatable = false)
private Timestamp createdAt;

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/iemr/flw/domain/iemr/OptionCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public class OptionCondition {
@ToString.Exclude
private FormSection targetSection;

/** Soft-delete flag: false means removed from its option. Never hard-deleted. */
@Column(name = "isActive", nullable = false)
private Boolean isActive = true;

@Column(name = "created_by", length = 100)
private String createdBy;

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/iemr/flw/domain/iemr/QuestionOption.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ public class QuestionOption {
@Column(name = "displayOrder", nullable = false)
private Integer displayOrder;

/** Soft-delete flag: false means removed from its question. Never hard-deleted β€” historical
* QuestionResponse rows reference optionId as a plain Long outside JPA's cascade graph. */
@Column(name = "isActive", nullable = false)
private Boolean isActive = true;

@Column(name = "created_by", length = 100)
private String createdBy;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ public class QuestionValidation {
@Column(name = "errorMessage", nullable = false, length = 500)
private String errorMessage;

/** Soft-delete flag: false means removed from its question. Never hard-deleted. */
@Column(name = "isActive", nullable = false)
private Boolean isActive = true;

@Column(name = "created_by", length = 100)
private String createdBy;

Expand Down
5 changes: 5 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,11 @@ public class SectionQuestion {
@Column(name = "containsPii", nullable = false)
private Boolean containsPii = 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)
private Boolean isActive = true;

@Column(name = "created_by", length = 100)
private String createdBy;

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/iemr/flw/masterEnum/ValidationType.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ public enum ValidationType {
REGEX,
/** Field becomes mandatory when another field equals a specific value.
* validationParam format: "QUESTION_UUID=OPTION_VALUE" e.g. "Q-GENDER=FEMALE". */
MANDATORY_IF
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.repo.iemr;

import com.iemr.flw.domain.iemr.DynamicFormMigrationLog;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
* Repository for applied form-structure migration records.
* Id type is the migrationId String itself β€” existsById/save need no custom methods.
*/
@Repository
public interface DynamicFormMigrationLogRepo extends JpaRepository<DynamicFormMigrationLog, String> {
}
16 changes: 16 additions & 0 deletions src/main/java/com/iemr/flw/repo/iemr/FormSectionRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import com.iemr.flw.domain.iemr.FormSection;
import com.iemr.flw.masterEnum.SectionPhase;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
* Repository for form sections.
Expand All @@ -40,6 +42,20 @@ public interface FormSectionRepo extends JpaRepository<FormSection, Long> {

List<FormSection> findByFormVersion_VersionIdOrderByDisplayOrderAsc(Long versionId);

/** Read-path variant excluding removed (isActive=false) sections. */
List<FormSection> findByFormVersion_VersionIdAndIsActiveTrueOrderByDisplayOrderAsc(Long versionId);

Optional<FormSection> findByFormVersion_VersionIdAndSectionUuid(Long versionId, String sectionUuid);

Optional<FormSection> findTopByFormVersion_VersionIdOrderByDisplayOrderDesc(Long versionId);

/** Shifts displayOrder by delta for every section in [from, to] within a version β€” used to make room for/close a gap around an inserted or moved sibling. */
@Modifying
@Query("UPDATE FormSection s SET s.displayOrder = s.displayOrder + :delta " +
"WHERE s.formVersion.versionId = :versionId AND s.displayOrder BETWEEN :from AND :to")
void shiftDisplayOrder(@Param("versionId") Long versionId, @Param("from") int from,
@Param("to") int to, @Param("delta") int delta);

@Query("SELECT fs.formVersion.versionId, COUNT(fs) FROM FormSection fs " +
"WHERE fs.formVersion.versionId IN :versionIds " +
"AND fs.sectionPhase = :sectionPhase " +
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/com/iemr/flw/repo/iemr/OptionConditionRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
* Repository for option conditions.
Expand All @@ -38,6 +39,13 @@ public interface OptionConditionRepo extends JpaRepository<OptionCondition, Long

List<OptionCondition> findByQuestionOption_OptionId(Long optionId);

/** No surrogate natural key on conditions β€” matched by (actionType, target) within an option. */
Optional<OptionCondition> findByQuestionOption_OptionIdAndActionTypeAndTargetQuestion_QuestionUuid(
Long optionId, String actionType, String targetQuestionUuid);

Optional<OptionCondition> findByQuestionOption_OptionIdAndActionTypeAndTargetSection_SectionUuid(
Long optionId, String actionType, String targetSectionUuid);

@Query("SELECT oc.targetQuestion.questionId FROM OptionCondition oc "
+ "WHERE oc.questionOption.sectionQuestion.formSection.formVersion.versionId = :versionId "
+ "AND oc.targetQuestion IS NOT NULL")
Expand All @@ -47,9 +55,11 @@ public interface OptionConditionRepo extends JpaRepository<OptionCondition, Long
* Loads all conditions for a set of options in one query.
* JOIN FETCH ensures questionOption, targetQuestion, and targetSection are hydrated
* so callers can group and resolve references without extra queries.
* Excludes removed (isActive=false) conditions β€” used only by read paths; reconciliation matching
* uses the natural-key finders above, which must see inactive rows too.
*/
@Query("SELECT c FROM OptionCondition c JOIN FETCH c.questionOption "
+ "LEFT JOIN FETCH c.targetQuestion LEFT JOIN FETCH c.targetSection "
+ "WHERE c.questionOption.optionId IN :optionIds")
+ "WHERE c.questionOption.optionId IN :optionIds AND c.isActive = true")
List<OptionCondition> findByOptionIds(@Param("optionIds") Collection<Long> optionIds);
}
17 changes: 16 additions & 1 deletion src/main/java/com/iemr/flw/repo/iemr/QuestionOptionRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@

import com.iemr.flw.domain.iemr.QuestionOption;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
* Repository for question options.
Expand All @@ -38,11 +40,24 @@ public interface QuestionOptionRepo extends JpaRepository<QuestionOption, Long>

List<QuestionOption> findBySectionQuestion_QuestionIdOrderByDisplayOrderAsc(Long questionId);

Optional<QuestionOption> findBySectionQuestion_QuestionIdAndOptionValue(Long questionId, String optionValue);

Optional<QuestionOption> findTopBySectionQuestion_QuestionIdOrderByDisplayOrderDesc(Long questionId);

/** Shifts displayOrder by delta for every option in [from, to] within a question β€” used to make room for/close a gap around an inserted or moved sibling. */
@Modifying
@Query("UPDATE QuestionOption o SET o.displayOrder = o.displayOrder + :delta " +
"WHERE o.sectionQuestion.questionId = :questionId AND o.displayOrder BETWEEN :from AND :to")
void shiftDisplayOrder(@Param("questionId") Long questionId, @Param("from") int from,
@Param("to") int to, @Param("delta") int delta);

/**
* Loads all options for a set of questions in one query.
* JOIN FETCH ensures sectionQuestion is hydrated so callers can group by questionId without extra queries.
* Excludes removed (isActive=false) options β€” used only by read paths; reconciliation matching
* uses the natural-key finder above, which must see inactive rows too.
*/
@Query("SELECT o FROM QuestionOption o JOIN FETCH o.sectionQuestion "
+ "WHERE o.sectionQuestion.questionId IN :questionIds ORDER BY o.displayOrder ASC")
+ "WHERE o.sectionQuestion.questionId IN :questionIds AND o.isActive = true ORDER BY o.displayOrder ASC")
List<QuestionOption> findByQuestionIdsOrderByDisplayOrderAsc(@Param("questionIds") Collection<Long> questionIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
package com.iemr.flw.repo.iemr;

import com.iemr.flw.domain.iemr.QuestionValidation;
import com.iemr.flw.masterEnum.ValidationType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
* Repository for question validation rules.
Expand All @@ -38,11 +40,17 @@ public interface QuestionValidationRepo extends JpaRepository<QuestionValidation

List<QuestionValidation> findBySectionQuestion_QuestionId(Long questionId);

/** No surrogate natural key on validations β€” matched by the (type, param) content tuple within a question. */
Optional<QuestionValidation> findBySectionQuestion_QuestionIdAndValidationTypeAndValidationParam(
Long questionId, ValidationType validationType, String validationParam);

/**
* Loads all validations for a set of questions in one query.
* JOIN FETCH ensures sectionQuestion is hydrated so callers can group by questionId without extra queries.
* Excludes removed (isActive=false) validations β€” used only by read paths; reconciliation matching
* uses the natural-key finder above, which must see inactive rows too.
*/
@Query("SELECT v FROM QuestionValidation v JOIN FETCH v.sectionQuestion "
+ "WHERE v.sectionQuestion.questionId IN :questionIds")
+ "WHERE v.sectionQuestion.questionId IN :questionIds AND v.isActive = true")
List<QuestionValidation> findByQuestionIds(@Param("questionIds") Collection<Long> questionIds);
}
20 changes: 19 additions & 1 deletion src/main/java/com/iemr/flw/repo/iemr/SectionQuestionRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@

import com.iemr.flw.domain.iemr.SectionQuestion;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
* Repository for section questions.
Expand All @@ -38,11 +40,27 @@ public interface SectionQuestionRepo extends JpaRepository<SectionQuestion, Long

List<SectionQuestion> findByFormSection_SectionIdOrderByDisplayOrderAsc(Long sectionId);

Optional<SectionQuestion> findByFormSection_SectionIdAndQuestionUuid(Long sectionId, String questionUuid);

/** Version-scoped lookup β€” a condition's target question may live in a different section of the same version. */
Optional<SectionQuestion> findByFormSection_FormVersion_VersionIdAndQuestionUuid(Long versionId, String questionUuid);

Optional<SectionQuestion> findTopByFormSection_SectionIdOrderByDisplayOrderDesc(Long sectionId);

/** Shifts displayOrder by delta for every question in [from, to] within a section β€” used to make room for/close a gap around an inserted or moved sibling. */
@Modifying
@Query("UPDATE SectionQuestion q SET q.displayOrder = q.displayOrder + :delta " +
"WHERE q.formSection.sectionId = :sectionId AND q.displayOrder BETWEEN :from AND :to")
void shiftDisplayOrder(@Param("sectionId") Long sectionId, @Param("from") int from,
@Param("to") int to, @Param("delta") int delta);

/**
* Loads all questions for a set of sections in one query.
* JOIN FETCH ensures formSection is hydrated so callers can group by sectionId without extra queries.
* Excludes unlinked (isActive=false) questions β€” used only by read paths; reconciliation matching
* uses the natural-key finders above, which must see inactive rows too.
*/
@Query("SELECT q FROM SectionQuestion q JOIN FETCH q.formSection "
+ "WHERE q.formSection.sectionId IN :sectionIds ORDER BY q.displayOrder ASC")
+ "WHERE q.formSection.sectionId IN :sectionIds AND q.isActive = true ORDER BY q.displayOrder ASC")
List<SectionQuestion> findBySectionIdsOrderByDisplayOrderAsc(@Param("sectionIds") Collection<Long> sectionIds);
}
Loading
Loading