@@ -1971,4 +1971,83 @@ describe('due tasks', () => {
19711971 } ) ;
19721972 } ) ;
19731973
1974+ it ( 'should not set already-sent tasks to pending when another task with the same due date is scheduled' , ( ) => {
1975+ // Reproduction of https://github.com/medic/cht-core/issues/10802
1976+ //
1977+ // Scenario: A document has two scheduled_tasks with the same due date.
1978+ // Task A (e.g. "Vaccination Day") has been sent successfully.
1979+ // Task B (e.g. "Age Based") is stuck in scheduled state because it cannot generate messages.
1980+ // The view returns the doc because of Task B. dueTasks collects the due date,
1981+ // then iterates ALL scheduled_tasks matching that due date — including Task A.
1982+ // Bug: Task A gets reset from sent back to pending.
1983+ const due = moment ( ) ;
1984+ const id = 'report-with-duplicate-due' ;
1985+
1986+ const doc = {
1987+ scheduled_tasks : [
1988+ {
1989+ // Task A: already sent successfully
1990+ due : due . toISOString ( ) ,
1991+ state : 'sent' ,
1992+ state_history : [
1993+ { state : 'scheduled' , timestamp : moment ( ) . subtract ( 10 , 'days' ) . toISOString ( ) } ,
1994+ { state : 'pending' , timestamp : moment ( ) . subtract ( 1 , 'day' ) . toISOString ( ) } ,
1995+ { state : 'sent' , timestamp : moment ( ) . subtract ( 1 , 'day' ) . toISOString ( ) } ,
1996+ ] ,
1997+ type : 'Immunization Reminders Vaccination Day' ,
1998+ messages : [
1999+ {
2000+ to : '+1234567890' ,
2001+ uuid : 'msg-uuid-1' ,
2002+ message : 'Please visit the health facility' ,
2003+ } ,
2004+ ] ,
2005+ } ,
2006+ {
2007+ // Task B: stuck in scheduled, same due date, no messages (generation fails)
2008+ due : due . toISOString ( ) ,
2009+ state : 'scheduled' ,
2010+ state_history : [
2011+ { state : 'scheduled' , timestamp : moment ( ) . subtract ( 10 , 'days' ) . toISOString ( ) } ,
2012+ ] ,
2013+ type : 'Immunization Reminders Age Based' ,
2014+ message_key : 'some.translation.key' ,
2015+ recipient : 'clinic' ,
2016+ // no messages — generation will fail
2017+ } ,
2018+ ] ,
2019+ } ;
2020+
2021+ // The view returns this doc because Task B is in 'scheduled' state
2022+ const view = sinon . stub ( request , 'get' ) . resolves ( {
2023+ rows : [
2024+ {
2025+ id : id ,
2026+ key : [ 'scheduled' , due . valueOf ( ) ] ,
2027+ doc : doc ,
2028+ } ,
2029+ ] ,
2030+ } ) ;
2031+
2032+ sinon . stub ( schedule . _lineage , 'hydrateDocs' ) . resolves ( [ doc ] ) ;
2033+ sinon . stub ( utils , 'getRegistrations' ) . resolves ( [ ] ) ;
2034+ // translate returns empty to simulate failed message generation for Task B
2035+ sinon . stub ( utils , 'translate' ) . returns ( '' ) ;
2036+
2037+ const saveDoc = sinon . stub ( db . medic , 'put' ) . resolves ( { } ) ;
2038+
2039+ return schedule . execute ( ) . then ( ( ) => {
2040+ assert . equal ( view . callCount , 1 ) ;
2041+
2042+ // Task A should NOT have been touched — it's already sent
2043+ assert . equal ( doc . scheduled_tasks [ 0 ] . state , 'sent' , 'Task A (already sent) should not have its state changed' ) ;
2044+
2045+ // Task B should still be scheduled (message generation failed)
2046+ assert . equal ( doc . scheduled_tasks [ 1 ] . state , 'scheduled' , 'Task B should remain scheduled' ) ;
2047+
2048+ // The document should NOT be saved since no valid state changes occurred
2049+ assert . equal ( saveDoc . callCount , 0 , 'Document should not be saved when no tasks were validly updated' ) ;
2050+ } ) ;
2051+ } ) ;
2052+
19742053} ) ;
0 commit comments