diff --git a/packages/database/src/__tests__/changeHandlers/TaskCompletionHandler.test.js b/packages/database/src/__tests__/changeHandlers/TaskCompletionHandler.test.js index e6a36ec052..33a0f75c97 100644 --- a/packages/database/src/__tests__/changeHandlers/TaskCompletionHandler.test.js +++ b/packages/database/src/__tests__/changeHandlers/TaskCompletionHandler.test.js @@ -115,4 +115,51 @@ describe('TaskCompletionHandler', () => { await assertTaskStatus(task.id, 'to_do', null); }); }); + + describe('Repeating tasks', () => { + it('updating a survey response for a repeating task creates a new completed task', async () => { + const samoa = await findOrCreateDummyRecord(models.entity, { code: 'WS' }); + const repeatTask = await findOrCreateDummyRecord(models.task, { + entity_id: samoa.id, + survey_id: SURVEY.id, + created_at: '2024-07-08', + status: null, + repeat_schedule: { + frequency: 'daily', + }, + }); + + const responses = await createResponses([{ entity_id: samoa.id, date: '2024-07-20' }]); + // Check that the repeat task has stayed as is + await assertTaskStatus(repeatTask.id, null, null); + + const newTask = await models.task.findOne({ + survey_response_id: responses[0], + entity_id: samoa.id, + }); + await assertTaskStatus(newTask.id, 'completed', responses[0]); + }); + + it('updating a survey response for a repeating task with status do_do creates a new completed task', async () => { + const fiji = await findOrCreateDummyRecord(models.entity, { code: 'FJ' }); + const repeatTask = await findOrCreateDummyRecord(models.task, { + entity_id: fiji.id, + survey_id: SURVEY.id, + created_at: '2024-07-08', + status: 'to_do', + repeat_schedule: { + frequency: 'daily', + }, + }); + + const responses = await createResponses([{ entity_id: fiji.id, date: '2024-07-20' }]); + // Check that the repeat task has stayed as is + await assertTaskStatus(repeatTask.id, 'to_do', null); + const newTask = await models.task.findOne({ + survey_response_id: responses[0], + entity_id: fiji.id, + }); + await assertTaskStatus(newTask.id, 'completed', responses[0]); + }); + }); }); diff --git a/packages/database/src/changeHandlers/TaskCompletionHandler.js b/packages/database/src/changeHandlers/TaskCompletionHandler.js index 391909c392..da135b0eb1 100644 --- a/packages/database/src/changeHandlers/TaskCompletionHandler.js +++ b/packages/database/src/changeHandlers/TaskCompletionHandler.js @@ -7,6 +7,10 @@ import { getUniqueEntries } from '@tupaia/utils'; import { ChangeHandler } from './ChangeHandler'; import { QUERY_CONJUNCTIONS } from '../TupaiaDatabase'; +function hasValidRepeatSchedule(repeatSchedule) { + return typeof repeatSchedule === 'object' && Object.keys(repeatSchedule).length > 0; +} + export class TaskCompletionHandler extends ChangeHandler { constructor(models) { super(models, 'task-completion-handler'); @@ -42,9 +46,14 @@ export class TaskCompletionHandler extends ChangeHandler { })), ); - const tasks = await this.models.task.find({ - // only fetch tasks that have a status of 'to_do' + return this.models.task.find({ status: 'to_do', + [QUERY_CONJUNCTIONS.OR]: { + status: { + comparator: 'IS', + comparisonValue: null, + }, + }, [QUERY_CONJUNCTIONS.RAW]: { sql: `${surveyIdAndEntityIdPairs .map(() => `(survey_id = ? AND entity_id = ? AND created_at <= ?)`) @@ -56,11 +65,9 @@ export class TaskCompletionHandler extends ChangeHandler { ]), }, }); - - return tasks; } - async handleChanges(transactingModels, changedResponses) { + async handleChanges(_transactingModels, changedResponses) { // if there are no changed responses, we don't need to do anything if (changedResponses.length === 0) return; const tasksToUpdate = await this.fetchTasksForSurveyResponses(changedResponses); @@ -69,7 +76,14 @@ export class TaskCompletionHandler extends ChangeHandler { if (tasksToUpdate.length === 0) return; for (const task of tasksToUpdate) { - const { survey_id: surveyId, entity_id: entityId, created_at: createdAt, id } = task; + const { + survey_id: surveyId, + entity_id: entityId, + created_at: createdAt, + repeat_schedule: repeatSchedule, + assignee_id: assigneeId, + id, + } = task; const matchingSurveyResponse = changedResponses.find( surveyResponse => surveyResponse.survey_id === surveyId && @@ -79,6 +93,21 @@ export class TaskCompletionHandler extends ChangeHandler { if (!matchingSurveyResponse) continue; + if (hasValidRepeatSchedule(repeatSchedule)) { + // Create a new task with the same details as the current task and mark as completed + // It is theoretically possible that more than one task could be created for a repeating + // task in a reporting period which is ok from a business point of view + await this.models.task.create({ + assignee_id: assigneeId, + survey_id: surveyId, + entity_id: entityId, + repeat_schedule: repeatSchedule, + status: 'completed', + survey_response_id: matchingSurveyResponse.id, + }); + continue; + } + await this.models.task.updateById(id, { status: 'completed', survey_response_id: matchingSurveyResponse.id,