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
4 changes: 4 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<description>API project for Lamp</description>

<dependencies>
<dependency>
<groupId>org.openmrs.api</groupId>
<artifactId>openmrs-api</artifactId>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.PatientProgram;
import org.openmrs.PatientState;
import org.openmrs.Program;
import org.openmrs.ProgramWorkflow;
import org.openmrs.ProgramWorkflowState;
Expand Down Expand Up @@ -75,7 +76,13 @@ public void execute(Encounter encounter, User currentUser, Date currentDate, Str
targetState.setTerminal(false);
}

Utils.updateProgram(patientProgram, encounter, currentDate, targetState);
PatientState patientState = patientProgram.getCurrentState(programWorkflow);
if (patientState != null && patientState.getState() != null
&& patientState.getState().getConcept().getUuid().equals(targetState.getConcept().getUuid())) {
return;
}

Utils.updateProgram(patientProgram, encounter, targetState);
patientProgram.setLocation(encounter.getLocation());
programWorkflowService.savePatientProgram(patientProgram);
}
Expand Down
4 changes: 4 additions & 0 deletions api/src/main/java/org/openmrs/module/lamp/LampConfig.java

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class shouldn't be a Spring bean but rather just a constants Class.

Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public class LampConfig {
public static final String PROGRAM_PRENATAL_UUID = "3531501f-bbdf-4e49-be19-6c87220f71ee";

public static final String WORKFLOW_PRENATAL_UUID = "3009b582-1745-46bc-8886-7ea20f4675f2";

public static final String CONCEPT_18_WEEKS_IN_CHILD_NUTRITION_PROGRAM = "74f45a8a-4128-4eb6-b8ca-f4b641c6de3a";

public static final String CONCEPT_10_MONTHS_IN_PRENATAL_PROGRAM = "20cfecf2-d01f-4bd8-b71e-ad112ce0d7ce";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.PatientProgram;
import org.openmrs.PatientState;
import org.openmrs.Program;
import org.openmrs.ProgramWorkflow;
import org.openmrs.ProgramWorkflowState;
Expand Down Expand Up @@ -46,7 +47,13 @@ public void execute(Encounter encounter, User currentUser, Date currentDate, Str
return;
}

Utils.updateProgram(patientProgram, encounter, currentDate, targetState);
PatientState patientState = patientProgram.getCurrentState(programWorkflow);
if (patientState != null && patientState.getState() != null
&& patientState.getState().getConcept().getUuid().equals(targetState.getConcept().getUuid())) {
return;
}

Utils.updateProgram(patientProgram, encounter, targetState);
patientProgram.setLocation(encounter.getLocation());
programWorkflowService.savePatientProgram(patientProgram);
}
Expand Down
29 changes: 8 additions & 21 deletions api/src/main/java/org/openmrs/module/lamp/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.PatientProgram;
import org.openmrs.PatientState;
import org.openmrs.Program;
import org.openmrs.ProgramWorkflow;
import org.openmrs.ProgramWorkflowState;
Expand Down Expand Up @@ -63,34 +64,20 @@ public static ProgramWorkflow getWorkflowByUuid(Program program, String workflow

public static ProgramWorkflowState getStateByConcept(ProgramWorkflow programWorkflow, Concept concept) {
for (ProgramWorkflowState programWorkflowState : programWorkflow.getStates()) {
if (concept.equals(programWorkflowState.getConcept())) {
if (concept.getUuid().equals(programWorkflowState.getConcept().getUuid())) {
return programWorkflowState;
}
}
return null;
}

public static Date getProgramStatusUpdateDate(PatientProgram patientProgram, Encounter encounter, Date currentDate) {
Date programStatusUpdateDate;
Date enrolled = patientProgram.getDateEnrolled();
if (enrolled != null && encounter.getEncounterDatetime() != null
&& encounter.getEncounterDatetime().before(enrolled)) {
programStatusUpdateDate = enrolled;
} else if (encounter.getEncounterDatetime() != null) {
programStatusUpdateDate = encounter.getEncounterDatetime();
} else {
programStatusUpdateDate = currentDate;
public static void updateProgram(PatientProgram patientProgram, Encounter encounter, ProgramWorkflowState targetState) {
for (PatientState ps : patientProgram.getStates()) {
if (ps.getActive() && ps.getState().getProgramWorkflow().equals(targetState.getProgramWorkflow())) {
ps.setEndDate(encounter.getEncounterDatetime());
}
}
return programStatusUpdateDate;
}

public static void updateProgram(PatientProgram patientProgram, Encounter encounter, Date currentDate,
ProgramWorkflowState targetState) {
Date programStatusUpdateDate = getProgramStatusUpdateDate(patientProgram, encounter, currentDate);

if (targetState.getTerminal()) {
patientProgram.setDateCompleted(new Date());
}
patientProgram.transitionToState(targetState, programStatusUpdateDate);
patientProgram.transitionToState(targetState, encounter.getEncounterDatetime());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.openmrs.module.lamp.scheduler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.PatientProgram;
import org.openmrs.PatientState;
import org.openmrs.Program;
import org.openmrs.ProgramWorkflow;
import org.openmrs.ProgramWorkflowState;
import org.openmrs.api.ProgramWorkflowService;
import org.openmrs.api.context.Context;
import org.openmrs.module.lamp.LampConfig;
import org.openmrs.module.lamp.Utils;
import org.openmrs.scheduler.tasks.AbstractTask;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Component
public class CompleteProgramsTask extends AbstractTask {

private static final Log log = LogFactory.getLog(CompleteProgramsTask.class);

@Override
public void execute() {
log.debug("Executing CompletePrograms Task");
ProgramWorkflowService service = Context.getProgramWorkflowService();

completeProgramIfExists(service, LampConfig.PROGRAM_CHILD_NUTRITION_UUID, 18);
completeProgramIfExists(service, LampConfig.PROGRAM_PRENATAL_UUID, 44);
}

@Override
public void shutdown() {
log.debug("Shutting down CompletePrograms Task");
stopExecuting();
}

private void completeProgramIfExists(ProgramWorkflowService service, String programUuid, int weeksThreshold) {
Program program = service.getProgramByUuid(programUuid);
if (program != null) {
completeProgramsStartedBefore(service, program, getThresholdDateWeeksAgo(weeksThreshold));
}
}

private Date getThresholdDateWeeksAgo(int weeks) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.WEEK_OF_YEAR, -weeks);
return cal.getTime();
}

private void completeProgramsStartedBefore(ProgramWorkflowService service, Program program, Date thresholdDate) {
List<PatientProgram> patientPrograms = service.getPatientPrograms(null, program, null, null, null, null, false);

for (PatientProgram pp : patientPrograms) {
if (pp == null || Boolean.TRUE.equals(pp.getVoided()) || pp.getDateCompleted() != null
|| pp.getDateEnrolled() == null) {
continue;
}

if (pp.getDateEnrolled().before(thresholdDate)) {
completePatientProgram(pp, program);
}
}
}

private void completePatientProgram(PatientProgram pp, Program program) {
String programUuid = pp.getProgram().getUuid();

if (LampConfig.PROGRAM_CHILD_NUTRITION_UUID.equals(programUuid)) {
transitionProgramState(pp, program, LampConfig.WORKFLOW_CHILD_NUTRITION_UUID,
LampConfig.CONCEPT_18_WEEKS_IN_CHILD_NUTRITION_PROGRAM, pp.getProgram().getName());
} else if (LampConfig.PROGRAM_PRENATAL_UUID.equals(programUuid)) {
transitionProgramState(pp, program, LampConfig.WORKFLOW_PRENATAL_UUID,
LampConfig.CONCEPT_10_MONTHS_IN_PRENATAL_PROGRAM, pp.getProgram().getName());
}
}

private void transitionProgramState(PatientProgram pp, Program program, String workflowUuid, String conceptUuid,
String programName) {
ProgramWorkflow workflow = Utils.getWorkflowByUuid(program, workflowUuid);
if (workflow == null) {
return;
}

ProgramWorkflowState programWorkflowState = Utils.getStateByConcept(workflow, Context.getConceptService()
.getConceptByUuid(conceptUuid));
if (programWorkflowState == null) {
return;
}
for (PatientState ps : pp.getStates()) {
if (ps.getActive() && ps.getState().getProgramWorkflow().equals(programWorkflowState.getProgramWorkflow())) {
ps.setEndDate(new Date());
}
}

pp.transitionToState(programWorkflowState, new Date());

log.info("Auto-completed " + programName + " program");
}
}
41 changes: 41 additions & 0 deletions api/src/main/resources/liquibase.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">

<!--
See http://wiki.openmrs.org/display/docs/Module+liquibase+File for
documentation on this file.

See http://www.liquibase.org/manual/home#available_database_refactorings
for a list of supported elements and attributes
-->

<changeSet id="create-complete-program-task-2025-11-04" author="siddharth">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">
SELECT COUNT(*) FROM scheduler_task_config
WHERE schedulable_class = 'org.openmrs.module.lamp.scheduler.CompleteProgramsTask'
And name = 'Complete LAMP Program Task'
</sqlCheck>
</preConditions>
<comment>Inserting CompletePrograms Task into 'schedule_task_config' table</comment>
<insert tableName="scheduler_task_config">
Comment on lines +16 to +25
Copy link

@corneliouzbett corneliouzbett Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, I would like us to migrate from using this approach (built-in scheduled task engine). This is because if the module is removed for some reason, there will be always scheduled task configuration hanging around, which throws errors due to classes not being found

<column name="name" value="Complete LAMP Program Task" />
<column name="description" value="Completes open Programs in Ozone LAMP" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this module be used outside Ozone Lamp distro? If yes, let's remove 'Ozone Lamp' in the description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. This module is tied to Ozone LAMP programs.

<column name="schedulable_class" value="org.openmrs.module.lamp.scheduler.CompleteProgramsTask" />
<column name="start_time_pattern" value="MM/dd/yyyy HH:mm:ss" />
<column name="start_time" valueDate="now()" />
<column name="repeat_interval" value="60" />
<column name="date_created" valueDate="CURRENT_TIMESTAMP" />
<column name="created_by" value="1" />
<column name="start_on_startup" value="1"/>
<column name="started" value="0"/>
<column name="uuid" value="e8ed7c5d-80f2-4f77-beeb-d12c5f0badc6" />
</insert>
</changeSet>
</databaseChangeLog>


Loading