From f151402e48cc675bac3b1e854adb0532b6868f52 Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Thu, 18 Jun 2026 14:49:46 -0400 Subject: [PATCH 1/8] Fix for disappearing goals on RTTAPA --- .../StandardGoalForm/RestartStandardGoal.js | 84 ++--------- .../RestartStandardGoalForm.js | 121 ++++++++++++++++ .../StandardGoalForm/UpdateStandardGoal.js | 94 ++---------- .../UpdateStandardGoalForm.js | 135 ++++++++++++++++++ .../__tests__/UpdateStandardGoal.js | 16 +++ src/services/standardGoals.ts | 2 + 6 files changed, 295 insertions(+), 157 deletions(-) create mode 100644 frontend/src/pages/StandardGoalForm/RestartStandardGoalForm.js create mode 100644 frontend/src/pages/StandardGoalForm/UpdateStandardGoalForm.js diff --git a/frontend/src/pages/StandardGoalForm/RestartStandardGoal.js b/frontend/src/pages/StandardGoalForm/RestartStandardGoal.js index e1c1a26c02..c383ed1a49 100644 --- a/frontend/src/pages/StandardGoalForm/RestartStandardGoal.js +++ b/frontend/src/pages/StandardGoalForm/RestartStandardGoal.js @@ -1,21 +1,13 @@ import { GOAL_STATUS } from '@ttahub/common/src/constants'; -import { uniqueId } from 'lodash'; import PropTypes from 'prop-types'; -import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { useHistory, useLocation, useParams } from 'react-router'; import AppLoadingContext from '../../AppLoadingContext'; import { ROUTES } from '../../Constants'; -import { - GOAL_FORM_BUTTON_LABELS, - GOAL_FORM_BUTTON_TYPES, - GOAL_FORM_BUTTON_VARIANTS, -} from '../../components/SharedGoalComponents/constants'; -import GoalFormUpdateOrRestart from '../../components/SharedGoalComponents/GoalFormUpdateOrRestart'; import { HTTPError } from '../../fetchers'; -import { addStandardGoal, getStandardGoal } from '../../fetchers/standardGoals'; +import { getStandardGoal } from '../../fetchers/standardGoals'; import useGoalTemplatePrompts from '../../hooks/useGoalTemplatePrompts'; -import { GOAL_FORM_FIELDS, mapObjectivesAndRootCauses } from './constants'; +import RestartStandardGoalForm from './RestartStandardGoalForm'; export default function RestartStandardGoal({ recipient }) { const { goalTemplateId, regionId, grantId } = useParams(); @@ -29,13 +21,6 @@ export default function RestartStandardGoal({ recipient }) { const [goal, setGoal] = useState(null); const fetchAttempted = useRef(false); - const hookForm = useForm({ - defaultValues: { - [GOAL_FORM_FIELDS.OBJECTIVES]: [], - [GOAL_FORM_FIELDS.ROOT_CAUSES]: null, - }, - }); - const [goalTemplatePrompts] = useGoalTemplatePrompts(goalTemplateId); useEffect(() => { @@ -49,14 +34,6 @@ export default function RestartStandardGoal({ recipient }) { throw new HTTPError('Goal not found', 404); } setGoal(g); - - // We want the user to start fresh with objectives and root causes. - const resetFormData = { - // eslint-disable-next-line max-len - [GOAL_FORM_FIELDS.OBJECTIVES]: [], - }; - - hookForm.reset(resetFormData); } catch (err) { // eslint-disable-next-line no-console console.error(err); @@ -78,62 +55,21 @@ export default function RestartStandardGoal({ recipient }) { fetchAttempted.current = true; fetchStandardGoal(); } - }, [goal, goalTemplateId, goalTemplatePrompts, grantId, history, hookForm, setIsAppLoading]); - - const standardGoalFormButtons = useMemo( - () => [ - { - id: uniqueId('goal-form-button-'), - type: GOAL_FORM_BUTTON_TYPES.SUBMIT, - variant: GOAL_FORM_BUTTON_VARIANTS.PRIMARY, - label: GOAL_FORM_BUTTON_LABELS.RESTART, - }, - { - id: uniqueId('goal-form-button-'), - type: GOAL_FORM_BUTTON_TYPES.LINK, - variant: GOAL_FORM_BUTTON_VARIANTS.OUTLINE, - label: GOAL_FORM_BUTTON_LABELS.CANCEL, - to: backLinkTo, - }, - ], - [backLinkTo] - ); - - const onSubmit = async (data) => { - try { - setIsAppLoading(true); - - // submit to backend - await addStandardGoal({ - goalTemplateId, - grantId, - status: GOAL_STATUS.IN_PROGRESS, - ...mapObjectivesAndRootCauses(data), - }); - - history.push(backLinkTo); - } catch (err) { - // eslint-disable-next-line no-console - console.log(err); - } finally { - setIsAppLoading(false); - } - }; + }, [goal, goalTemplateId, goalTemplatePrompts, grantId, history, setIsAppLoading]); if (!goal) { return null; } return ( - ); } diff --git a/frontend/src/pages/StandardGoalForm/RestartStandardGoalForm.js b/frontend/src/pages/StandardGoalForm/RestartStandardGoalForm.js new file mode 100644 index 0000000000..641ec88f16 --- /dev/null +++ b/frontend/src/pages/StandardGoalForm/RestartStandardGoalForm.js @@ -0,0 +1,121 @@ +import { GOAL_STATUS } from '@ttahub/common/src/constants'; +import { uniqueId } from 'lodash'; +import PropTypes from 'prop-types'; +import React, { useContext, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { useHistory } from 'react-router'; +import AppLoadingContext from '../../AppLoadingContext'; +import { + GOAL_FORM_BUTTON_LABELS, + GOAL_FORM_BUTTON_TYPES, + GOAL_FORM_BUTTON_VARIANTS, +} from '../../components/SharedGoalComponents/constants'; +import GoalFormUpdateOrRestart from '../../components/SharedGoalComponents/GoalFormUpdateOrRestart'; +import { addStandardGoal } from '../../fetchers/standardGoals'; +import { GOAL_FORM_FIELDS, mapObjectivesAndRootCauses } from './constants'; + +export default function RestartStandardGoalForm({ + goal, + goalTemplatePrompts, + recipient, + regionId, + goalTemplateId, + grantId, + backLinkTo, +}) { + const history = useHistory(); + const { setIsAppLoading } = useContext(AppLoadingContext); + + const hookForm = useForm({ + defaultValues: { + [GOAL_FORM_FIELDS.OBJECTIVES]: [], + [GOAL_FORM_FIELDS.ROOT_CAUSES]: null, + }, + }); + + const standardGoalFormButtons = useMemo( + () => [ + { + id: uniqueId('goal-form-button-'), + type: GOAL_FORM_BUTTON_TYPES.SUBMIT, + variant: GOAL_FORM_BUTTON_VARIANTS.PRIMARY, + label: GOAL_FORM_BUTTON_LABELS.RESTART, + }, + { + id: uniqueId('goal-form-button-'), + type: GOAL_FORM_BUTTON_TYPES.LINK, + variant: GOAL_FORM_BUTTON_VARIANTS.OUTLINE, + label: GOAL_FORM_BUTTON_LABELS.CANCEL, + to: backLinkTo, + }, + ], + [backLinkTo] + ); + + const onSubmit = async (data) => { + try { + setIsAppLoading(true); + + await addStandardGoal({ + goalTemplateId, + grantId, + status: GOAL_STATUS.IN_PROGRESS, + ...mapObjectivesAndRootCauses(data), + }); + + history.push(backLinkTo); + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } finally { + setIsAppLoading(false); + } + }; + + return ( + + ); +} + +RestartStandardGoalForm.propTypes = { + goal: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + grant: PropTypes.shape({ + numberWithProgramTypes: PropTypes.string, + }), + objectives: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number, + title: PropTypes.string, + }) + ), + }).isRequired, + goalTemplatePrompts: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number, + prompt: PropTypes.string, + }) + ), + recipient: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + }).isRequired, + regionId: PropTypes.string.isRequired, + goalTemplateId: PropTypes.string.isRequired, + grantId: PropTypes.string.isRequired, + backLinkTo: PropTypes.string.isRequired, +}; + +RestartStandardGoalForm.defaultProps = { + goalTemplatePrompts: null, +}; diff --git a/frontend/src/pages/StandardGoalForm/UpdateStandardGoal.js b/frontend/src/pages/StandardGoalForm/UpdateStandardGoal.js index aae1336a80..704bda8641 100644 --- a/frontend/src/pages/StandardGoalForm/UpdateStandardGoal.js +++ b/frontend/src/pages/StandardGoalForm/UpdateStandardGoal.js @@ -1,20 +1,12 @@ -import { uniqueId } from 'lodash'; import PropTypes from 'prop-types'; -import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { useHistory, useLocation, useParams } from 'react-router'; import AppLoadingContext from '../../AppLoadingContext'; import { ROUTES } from '../../Constants'; -import { - GOAL_FORM_BUTTON_LABELS, - GOAL_FORM_BUTTON_TYPES, - GOAL_FORM_BUTTON_VARIANTS, -} from '../../components/SharedGoalComponents/constants'; -import GoalFormUpdateOrRestart from '../../components/SharedGoalComponents/GoalFormUpdateOrRestart'; import { HTTPError } from '../../fetchers'; -import { getStandardGoal, updateStandardGoal } from '../../fetchers/standardGoals'; +import { getStandardGoal } from '../../fetchers/standardGoals'; import useGoalTemplatePrompts from '../../hooks/useGoalTemplatePrompts'; -import { GOAL_FORM_FIELDS } from './constants'; +import UpdateStandardGoalForm from './UpdateStandardGoalForm'; export default function UpdateStandardGoal({ recipient }) { const { goalTemplateId, regionId, grantId } = useParams(); @@ -28,13 +20,6 @@ export default function UpdateStandardGoal({ recipient }) { const [goal, setGoal] = useState(null); const fetchAttempted = useRef(false); - const hookForm = useForm({ - defaultValues: { - [GOAL_FORM_FIELDS.OBJECTIVES]: [], - [GOAL_FORM_FIELDS.ROOT_CAUSES]: null, - }, - }); - const [goalTemplatePrompts] = useGoalTemplatePrompts(goalTemplateId); useEffect(() => { @@ -42,27 +27,13 @@ export default function UpdateStandardGoal({ recipient }) { try { setIsAppLoading(true); - // we need to get closed only if we are restarting the goal const g = await getStandardGoal(goalTemplateId, grantId); - setGoal(g); if (!g) { throw new HTTPError('Goal not found', 404); } - const resetFormData = { - // eslint-disable-next-line max-len - [GOAL_FORM_FIELDS.OBJECTIVES]: g.objectives.map((o) => ({ - value: o.title, - objectiveId: o.id, - onAR: o.onAR, - status: o.status, - })), - [GOAL_FORM_FIELDS.ROOT_CAUSES]: g.responses.flatMap((responses) => - responses.response.map((r) => ({ id: r, name: r })) - ), - }; - hookForm.reset(resetFormData); + setGoal(g); } catch (err) { // eslint-disable-next-line no-console console.error(err); @@ -84,64 +55,21 @@ export default function UpdateStandardGoal({ recipient }) { fetchAttempted.current = true; fetchStandardGoal(); } - }, [goal, goalTemplateId, goalTemplatePrompts, grantId, history, hookForm, setIsAppLoading]); - - const standardGoalFormButtons = useMemo( - () => [ - { - id: uniqueId('goal-form-button-'), - type: GOAL_FORM_BUTTON_TYPES.SUBMIT, - variant: GOAL_FORM_BUTTON_VARIANTS.PRIMARY, - label: GOAL_FORM_BUTTON_LABELS.SAVE, - }, - { - id: uniqueId('goal-form-button-'), - type: GOAL_FORM_BUTTON_TYPES.LINK, - variant: GOAL_FORM_BUTTON_VARIANTS.OUTLINE, - label: GOAL_FORM_BUTTON_LABELS.CANCEL, - to: backLinkTo, - }, - ], - [backLinkTo] - ); - - const onSubmit = async (data) => { - try { - setIsAppLoading(true); - - // submit to backend - await updateStandardGoal({ - goalTemplateId, - grantId, - // eslint-disable-next-line max-len - objectives: data.objectives - ? data.objectives.map((o) => ({ title: o.value, id: o.objectiveId })) - : [], - rootCauses: data.rootCauses ? data.rootCauses.map((r) => r.id) : null, - }); - - history.push(backLinkTo); - } catch (err) { - // eslint-disable-next-line no-console - console.log(err); - } finally { - setIsAppLoading(false); - } - }; + }, [goal, goalTemplateId, goalTemplatePrompts, grantId, history, setIsAppLoading]); if (!goal) { return null; } return ( - ); } diff --git a/frontend/src/pages/StandardGoalForm/UpdateStandardGoalForm.js b/frontend/src/pages/StandardGoalForm/UpdateStandardGoalForm.js new file mode 100644 index 0000000000..99c2fe1170 --- /dev/null +++ b/frontend/src/pages/StandardGoalForm/UpdateStandardGoalForm.js @@ -0,0 +1,135 @@ +import { uniqueId } from 'lodash'; +import PropTypes from 'prop-types'; +import React, { useContext, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { useHistory } from 'react-router'; +import AppLoadingContext from '../../AppLoadingContext'; +import { + GOAL_FORM_BUTTON_LABELS, + GOAL_FORM_BUTTON_TYPES, + GOAL_FORM_BUTTON_VARIANTS, +} from '../../components/SharedGoalComponents/constants'; +import GoalFormUpdateOrRestart from '../../components/SharedGoalComponents/GoalFormUpdateOrRestart'; +import { updateStandardGoal } from '../../fetchers/standardGoals'; +import { GOAL_FORM_FIELDS } from './constants'; + +export default function UpdateStandardGoalForm({ + goal, + goalTemplatePrompts, + recipient, + regionId, + goalTemplateId, + grantId, + backLinkTo, +}) { + const history = useHistory(); + const { setIsAppLoading } = useContext(AppLoadingContext); + + const hookForm = useForm({ + defaultValues: { + [GOAL_FORM_FIELDS.OBJECTIVES]: goal.objectives.map((o) => ({ + value: o.title, + objectiveId: o.id, + onAR: o.onAR, + status: o.status, + })), + [GOAL_FORM_FIELDS.ROOT_CAUSES]: goal.responses.flatMap((responses) => + responses.response.map((r) => ({ id: r, name: r })) + ), + }, + }); + + const standardGoalFormButtons = useMemo( + () => [ + { + id: uniqueId('goal-form-button-'), + type: GOAL_FORM_BUTTON_TYPES.SUBMIT, + variant: GOAL_FORM_BUTTON_VARIANTS.PRIMARY, + label: GOAL_FORM_BUTTON_LABELS.SAVE, + }, + { + id: uniqueId('goal-form-button-'), + type: GOAL_FORM_BUTTON_TYPES.LINK, + variant: GOAL_FORM_BUTTON_VARIANTS.OUTLINE, + label: GOAL_FORM_BUTTON_LABELS.CANCEL, + to: backLinkTo, + }, + ], + [backLinkTo] + ); + + const onSubmit = async (data) => { + try { + setIsAppLoading(true); + + await updateStandardGoal({ + goalTemplateId, + grantId, + objectives: data.objectives + ? data.objectives.map((o) => ({ title: o.value, id: o.objectiveId })) + : [], + rootCauses: data.rootCauses ? data.rootCauses.map((r) => r.id) : null, + }); + + history.push(backLinkTo); + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } finally { + setIsAppLoading(false); + } + }; + + return ( + + ); +} + +UpdateStandardGoalForm.propTypes = { + goal: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + grant: PropTypes.shape({ + numberWithProgramTypes: PropTypes.string, + }), + objectives: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number, + title: PropTypes.string, + onAR: PropTypes.bool, + status: PropTypes.string, + }) + ), + responses: PropTypes.arrayOf( + PropTypes.shape({ + response: PropTypes.arrayOf(PropTypes.string), + }) + ), + }).isRequired, + goalTemplatePrompts: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number, + prompt: PropTypes.string, + }) + ), + recipient: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + }).isRequired, + regionId: PropTypes.string.isRequired, + goalTemplateId: PropTypes.string.isRequired, + grantId: PropTypes.string.isRequired, + backLinkTo: PropTypes.string.isRequired, +}; + +UpdateStandardGoalForm.defaultProps = { + goalTemplatePrompts: null, +}; diff --git a/frontend/src/pages/StandardGoalForm/__tests__/UpdateStandardGoal.js b/frontend/src/pages/StandardGoalForm/__tests__/UpdateStandardGoal.js index de593e45d3..a41aa23a1a 100644 --- a/frontend/src/pages/StandardGoalForm/__tests__/UpdateStandardGoal.js +++ b/frontend/src/pages/StandardGoalForm/__tests__/UpdateStandardGoal.js @@ -107,6 +107,22 @@ describe('UpdateStandardGoal', () => { expect(screen.getByText('Grant-123')).toBeInTheDocument(); }); + it('populates existing objectives into the form on initial render', async () => { + RenderTest(); + + await waitFor(() => { + expect(fetchMock.called('/api/goal-templates/standard/1/grant/1')).toBe(true); + }); + + expect(await screen.findByRole('button', { name: /Save/i })).toBeInTheDocument(); + + expect(await screen.findByDisplayValue('Objective 1')).toBeInTheDocument(); + + expect(screen.getByText('Objective 2', { selector: 'div' })).toBeInTheDocument(); + + expect(screen.getByText('Objectives used on reports cannot be edited.')).toBeInTheDocument(); + }); + it('redirects to error page when goal is not found', async () => { const history = createMemoryHistory({ initialEntries: [ diff --git a/src/services/standardGoals.ts b/src/services/standardGoals.ts index 453ac1e1c3..33a7366eba 100644 --- a/src/services/standardGoals.ts +++ b/src/services/standardGoals.ts @@ -792,6 +792,8 @@ export async function updateExistingStandardGoal( await Objective.destroy({ where: { goalId: goal.id, + onAR: false, + createdVia: 'rtr', id: { [Op.notIn]: updatedObjectives.map((o) => o.id), }, From 905d7a58476a2044e9cca77a0a1c8448d381ea9e Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Thu, 18 Jun 2026 15:56:22 -0400 Subject: [PATCH 2/8] test(standard-goals): align test isolation with selective objective delete --- src/services/standardGoal.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/services/standardGoal.test.js b/src/services/standardGoal.test.js index c9bc92bf03..b3e86c302b 100644 --- a/src/services/standardGoal.test.js +++ b/src/services/standardGoal.test.js @@ -409,6 +409,7 @@ describe('standardGoal service', () => { objectiveTemplateId: objectiveTemplate.id, goalId: goal.id, status: GOAL_STATUS.NOT_STARTED, + createdVia: 'rtr', }); }); @@ -588,6 +589,17 @@ describe('standardGoal service', () => { }); it('creates a new objective if its not found by id or title and goal id', async () => { + // The preceding test leaves objectiveWithoutId in the DB (it is preserved by + // the selective delete in updateExistingStandardGoal because of onApprovedAR=true, + // and remains visible to goalForRtr for the same reason). Remove it explicitly + // so this test starts from a clean slate. + if (objectiveWithoutId) { + await Objective.destroy({ + where: { id: objectiveWithoutId.id }, + force: true, + }); + } + // Create the test objecitve here so its not dlelete by other tests in this block. const g = await updateExistingStandardGoal( grant.id, From a8d9ab691b1a88ef05f777b74d049ebcff607ee8 Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Sun, 21 Jun 2026 19:06:17 -0400 Subject: [PATCH 3/8] Add migration to revert known invalid deletions --- ...21225954-revert-bad-objective-deletions.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/migrations/20260621225954-revert-bad-objective-deletions.js diff --git a/src/migrations/20260621225954-revert-bad-objective-deletions.js b/src/migrations/20260621225954-revert-bad-objective-deletions.js new file mode 100644 index 0000000000..6802d9cd5e --- /dev/null +++ b/src/migrations/20260621225954-revert-bad-objective-deletions.js @@ -0,0 +1,37 @@ +const { prepMigration } = require('../lib/migration'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.sequelize.transaction(async (transaction) => { + const sessionSig = __filename; + await prepMigration(queryInterface, transaction, sessionSig); + + await queryInterface.sequelize.query( + ` + UPDATE "Objectives" + SET "deletedAt" = NULL + WHERE id IN ( + SELECT o.id FROM "Objectives" o + INNER JOIN "Goals" g ON o."goalId" = g.id + INNER JOIN "GoalTemplates" gt ON g."goalTemplateId" = gt.id + INNER JOIN "ActivityReportObjectives" aro ON aro."objectiveId" = o.id + INNER JOIN "ActivityReports" ar ON ar.id = aro."activityReportId" + WHERE gt."standard" IS NOT NULL + AND o."onAR" = true + AND g."createdAt" >= '2025-09-01' + AND g."deletedAt" IS NULL + AND o."deletedAt" IS NOT NULL + AND ar."calculatedStatus" != 'deleted' + ); + + `, + { transaction } + ); + }); + }, + + async down() { + // This cannot be sensibly rolled bacl + }, +}; From fe630d12e170770edd656ce507a31baa54aa1a05 Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Sun, 21 Jun 2026 19:07:20 -0400 Subject: [PATCH 4/8] Remove unnecessary lookup from SQL --- src/migrations/20260621225954-revert-bad-objective-deletions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/migrations/20260621225954-revert-bad-objective-deletions.js b/src/migrations/20260621225954-revert-bad-objective-deletions.js index 6802d9cd5e..aae9935feb 100644 --- a/src/migrations/20260621225954-revert-bad-objective-deletions.js +++ b/src/migrations/20260621225954-revert-bad-objective-deletions.js @@ -18,7 +18,6 @@ module.exports = { INNER JOIN "ActivityReportObjectives" aro ON aro."objectiveId" = o.id INNER JOIN "ActivityReports" ar ON ar.id = aro."activityReportId" WHERE gt."standard" IS NOT NULL - AND o."onAR" = true AND g."createdAt" >= '2025-09-01' AND g."deletedAt" IS NULL AND o."deletedAt" IS NOT NULL From 9d5dede92f1c8bfb5271f31fc3a2421a4187abbf Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Sun, 21 Jun 2026 19:08:01 -0400 Subject: [PATCH 5/8] Linting; spelling --- .../20260621225954-revert-bad-objective-deletions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/migrations/20260621225954-revert-bad-objective-deletions.js b/src/migrations/20260621225954-revert-bad-objective-deletions.js index aae9935feb..b54cd69b54 100644 --- a/src/migrations/20260621225954-revert-bad-objective-deletions.js +++ b/src/migrations/20260621225954-revert-bad-objective-deletions.js @@ -2,7 +2,7 @@ const { prepMigration } = require('../lib/migration'); /** @type {import('sequelize-cli').Migration} */ module.exports = { - async up(queryInterface, Sequelize) { + async up(queryInterface, _Sequelize) { await queryInterface.sequelize.transaction(async (transaction) => { const sessionSig = __filename; await prepMigration(queryInterface, transaction, sessionSig); @@ -31,6 +31,6 @@ module.exports = { }, async down() { - // This cannot be sensibly rolled bacl + // This cannot be sensibly rolled back }, }; From 0d8ac5b473c25fe107efc52ac9024cc70cc19088 Mon Sep 17 00:00:00 2001 From: thewatermethod Date: Sun, 21 Jun 2026 19:37:24 -0400 Subject: [PATCH 6/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/services/standardGoal.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/standardGoal.test.js b/src/services/standardGoal.test.js index b3e86c302b..06a590250e 100644 --- a/src/services/standardGoal.test.js +++ b/src/services/standardGoal.test.js @@ -589,10 +589,10 @@ describe('standardGoal service', () => { }); it('creates a new objective if its not found by id or title and goal id', async () => { - // The preceding test leaves objectiveWithoutId in the DB (it is preserved by - // the selective delete in updateExistingStandardGoal because of onApprovedAR=true, - // and remains visible to goalForRtr for the same reason). Remove it explicitly - // so this test starts from a clean slate. + // The preceding test leaves objectiveWithoutId in the DB (it is still returned by goalForRtr + // because onApprovedAR=true, but it is not removed by updateExistingStandardGoal's selective + // delete because it only deletes objectives with createdVia='rtr' and onAR=false). Remove it + // explicitly so this test starts from a clean slate. if (objectiveWithoutId) { await Objective.destroy({ where: { id: objectiveWithoutId.id }, From 829a4e588fa2f91d362546515b6ba4a429769266 Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Mon, 22 Jun 2026 11:25:57 -0400 Subject: [PATCH 7/8] Encompass AI review suggestion --- src/services/standardGoals.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/standardGoals.ts b/src/services/standardGoals.ts index 33a7366eba..9bfa91eeee 100644 --- a/src/services/standardGoals.ts +++ b/src/services/standardGoals.ts @@ -793,7 +793,6 @@ export async function updateExistingStandardGoal( where: { goalId: goal.id, onAR: false, - createdVia: 'rtr', id: { [Op.notIn]: updatedObjectives.map((o) => o.id), }, From 37579ff07389ef101fed863638c9daaa7d5cc79b Mon Sep 17 00:00:00 2001 From: "thewatermethod@gmail.com" Date: Mon, 22 Jun 2026 11:46:31 -0400 Subject: [PATCH 8/8] Address additional PR finding --- src/services/standardGoals.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/standardGoals.ts b/src/services/standardGoals.ts index 9bfa91eeee..13a19189fe 100644 --- a/src/services/standardGoals.ts +++ b/src/services/standardGoals.ts @@ -789,13 +789,12 @@ export async function updateExistingStandardGoal( } // Delete any potentially removed objectives (regardless if we have any objectives). + const idsToKeep = updatedObjectives.map((o) => o.id); await Objective.destroy({ where: { goalId: goal.id, onAR: false, - id: { - [Op.notIn]: updatedObjectives.map((o) => o.id), - }, + ...(idsToKeep.length ? { id: { [Op.notIn]: idsToKeep } } : {}), }, });