Skip to content

Commit

Permalink
Objectives/Goals are persisted
Browse files Browse the repository at this point in the history
 * When a report is saved the goals and objectives from the report are saved to
the DB, udpated or created if needed
 * An ActivityReportObjective record is created for every objective
associating the report to objective
 * Objectives that are no longer used in the report are removed from the
DB, along with the ActivityReportObjective
 * If the goal hasn't been used on a previous report and is removed from
the current report it is also removed
 * When a report is approved a GrantGoal record is created for every
goal/grant pair
  • Loading branch information
jasalisbury committed Mar 8, 2021
1 parent cf78ad4 commit cb1b865
Show file tree
Hide file tree
Showing 18 changed files with 650 additions and 81 deletions.
27 changes: 24 additions & 3 deletions docs/logical_data_model.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
Logical Data Model
==================

![logical data model diagram](http://www.plantuml.com/plantuml/png/nLTjRzis4FwkNy5lUm5Y31k60KLGDCtS8S2M1N7w0yxIHUP6yRZZAQsHvBzFD1FPjEI4jR3rIm8zz_xmZaU_ym8iRMCANu91zSFibv-BuXi5TwGhvPYM1XklFcbPAR2rmKgLJ9-ykaMKml-jhGE6HXbjKTOE5R6ig2XA0zwhZnvPtHTAVQywRs-Kje9r1vsUrcYHGR1w-tOZNddqGViYg7bXRUi4jz3WhYJWPsqNYhLez9q8c65z6XM2ptqBjoABbsspHDw5iG5jTW7HOl2DGjis8GurU8sr9dknKy2tF_tudRgUVvXVpo_FvmlbsgNHvImg-fckDhaRyF8xrTfsXg8Rz0ADsqXAnb3xcGmahQj698_FXCO8UPIc1FGk5YKvFCQ3KXNwahKJJRP79vLpXTnexxDBQZronZc4XkbFWOyyFHzuoZ3Ry1ciffxiFss7pPc9VGuRvCA79hjxGYthtSy6PKN9H74CyvwgTGfxHzRZ8VoSAkpnYOMI6Mk_brhsXfmkispICD4kgxEOJCvX1w_zMKjmVr6zPhF9Odg3_4PWyZXGoWj8wtUHuF2MzzIq_y3KB1fWyIj4kH6uCX0QQaba4EoaZEEnlPHstSaoI0yklXBfb_XP5e9kEv5f1L2AljEolaP7BA-dTPReFwTdIJ0PhW3Sh1sEJzp16iRxXi4APexS5B9Yf8Dn1flts8kvZWNbD4xK38Q3cOW8owyWk5Sescg8JYOcT776NNHCWIarkNR1wjWnd31HP6FMnxn1feLEXoydFAEdbilD4pHPer1LEknUC9Sac7HLRFG_Rw-0HPVQ1QQJMQZGw3lazH-3vLor_KQliDGDcAGRGt5zsoOGz9Lr0JLQ2PV7oDf7JgaSpeHZgXcQO3_sg1RW6kMAN6iscOxfC88dEo9m2IFxudYpZme4OZq3IKbMyBCa1K2D2udi_EN9Tb03jWCvFQcZtvC6l7oCZVP-_Hy_Hf_CfhVyjXcp-dwusDGHc-tBhgDeOllh8SDkUvTC9YfD_voCWIKn2F7_oDSNDMGm6Ky_VzLCJVk83VAKTgX0FJoSqBsAhHwHHpevURZV-LrOn9-hGpnQZGd8FEl75sYn-QiU7pPGoA68NTEhzVkv27y6Q7GvcwdmVyVbE1I3RhsbI0mSAkz3F9MIOYdFmSdX3etKlQmozDS_RL-DbTHoI1JftHCdvxmdIZyJdUI-Yfm5-RXuUmaSrdc7jceRyoy0)
<img src="http://www.plantuml.com/plantuml/png/nLTVRzis47_Nf-3R_g0nYkrXG14KJJDt2B2vHfn-05gwoEme7dSyfJ6IVFUP8aA8YfpGrfQ-9F3l_j_zZgG-E9R4RQCBLqeDuktijxT5yOEGDwWgOGdN6XedlYdU26bg3PKe2RyvSGt0XVjR6Ij9Da8h0xor891uWqLHQkcS-EA0n5qXnCz2LUATK8QXta6dfpKO8CbGlN_VYJSEdDU-y6gEIPawmOrie2_n-5cx2qMA5RYTI1B9xMbGy3w75dQ31XPjimRgmz18fVi0AzRmJe1f6ny76xY4Mg6vw1Nmuw-__2Gkvv_cb_F5-NmbRBlMgfm5HJTJLPUxbex_n5gPdI6h1XpBndQIai3NViPF9AsjHYGFZmqD4V9QcZOWkbYLQE4O3q9Ku4fIbXMQY4ugPmdKAEUoIseTKCOSF67Y5EZ8wJdVfmophf_UQkfa_sbts9m8oULq0wt_eT3q9zIIwlziW3UOR1I5C1-nsgQmisWSxCFFiOA8JomKzHZzdgFi5LfLvjl4CC4kYrc4EcVOtckSDNlt5zLBDbqMqmdo1qgTrmCX74jkNH_n3gzfeARS9y9crKXYjm7WAvPBBJsXreWQf9cKSHZRSsdNErb6qxsBxzJq1TpoYi6wM49QWInPtIZUNe95ufVJEalq7zCpo90ibmUkjXR6Dsvl3UEy8MaOybPkKhD2fBrnXZVlqGTp7GhA8KxGz8OZE-4Hujybmw1GCDK8EhKndiLvPz711QQjoRNpLWUluVIDGXEoFl8DCY5qkdmvuGaSjbHsJz3a6fLLfLYXaNg130ch0j4_NoybiIgLzPrETg42hMw0jdyzkPohDjxe5PIgaJfXeoykqZQ1PAni5r21ONzuZ5pxG4ahHtR24jOa6sy_zgXcIJNm4cshkqp7T0YaGpCYi6b71k9mieyAMFfyHYGb6ePRJWgGDQmuTEUhGxE36cbs8AMJElezgQNZBu5e7xV_ysDqyhPzNdy5pusCPJlm90YfiM8DiQTlmh2MX3UgWYh11rHxyz-iSc_9yYeSNjZYY1lxi6Csc9Xux6NOBQBoP0AawP-ZrdAD91dfjVfrwA9vaptfUzwzLjh7xCEUjECIlchk-1WrmqFCS0BV_Ep4_Vq9tebmU0Na0KzExkzwN_f5zy4yWWUhCG7ggD-zwulkhtYuxo3V_IBiIrIBBwx2_tc21fUpcV3_Uox7eH0qUXiW8R2lF8PuB2N1qYbv9_jtCxJkMPKHV_5ZvSLOIItxHA6z0t4GXJ57rXJx-1xwdg-OYXDE3KJfpqGdkHN2VBnyNDnxZkxL-G2cQXlzBm00" alt="logical data model diagram">

UML Source
----------

```
@startuml
scale 0.75
scale 0.70
' avoid problems with angled crows feet
skinparam linetype ortho
Expand Down Expand Up @@ -170,6 +170,16 @@ class ActivityReport {
* updatedAt : timestamp
}
class Objective {
* id : integer <<generated>>
* goalId : integer(32) REFERENCES public.Goal.id
title : string,
ttaProvided : string,
status : string,
* createdAt : timestamp
* updatedAt : timestamp
}
class ActivityParticipant {
* id : integer <<generated>>
* activityReportId : integer(32) REFERENCES public.ActivityReport.id
Expand All @@ -191,6 +201,14 @@ class ActivityReportGoal {
* goalId : integer(32) REFERENCES public.Goal.id
}
class ActivityReportObjective {
* id : integer <<generated>>
* activityReportId : integer(32) REFERENCES public.ActivityReport.id
* objectiveId : integer(32) REFERENCES public.Objective.id
* createdAt : timestamp
* updatedAt : timestamp
}
User ||-o{ Region
User }o--|{ Permission
Scope }o--|{ Permission
Expand All @@ -211,6 +229,9 @@ ActivityReport .. NextSteps
ActivityReport .. ActivityReportGoal
Goal .. ActivityReportGoal
Goal }|--|{ ActivityReport
Goal ||-o{ Objective
ActivityReportObjective }o--|{ Objective
ActivityReportObjective }o--|{ ActivityReport
User ||-o{ ActivityReport
ActivityReport ||-o{ ActivityParticipant
Expand All @@ -222,7 +243,7 @@ NonGrantee ||-{ ActivityParticipant
Instructions
------------

1. [Edit this diagram with plantuml.com](http://www.plantuml.com/plantuml/uml/nLTjRzis4FwkNy5lUm5Y31k60KLGDCtS8S2M1N7w0yxIHUP6yRZZAQsHvBzFD1FPjEI4jR3rIm8zz_xmZaU_ym8iRMCANu91zSFibv-BuXi5TwGhvPYM1XklFcbPAR2rmKgLJ9-ykaMKml-jhGE6HXbjKTOE5R6ig2XA0zwhZnvPtHTAVQywRs-Kje9r1vsUrcYHGR1w-tOZNddqGViYg7bXRUi4jz3WhYJWPsqNYhLez9q8c65z6XM2ptqBjoABbsspHDw5iG5jTW7HOl2DGjis8GurU8sr9dknKy2tF_tudRgUVvXVpo_FvmlbsgNHvImg-fckDhaRyF8xrTfsXg8Rz0ADsqXAnb3xcGmahQj698_FXCO8UPIc1FGk5YKvFCQ3KXNwahKJJRP79vLpXTnexxDBQZronZc4XkbFWOyyFHzuoZ3Ry1ciffxiFss7pPc9VGuRvCA79hjxGYthtSy6PKN9H74CyvwgTGfxHzRZ8VoSAkpnYOMI6Mk_brhsXfmkispICD4kgxEOJCvX1w_zMKjmVr6zPhF9Odg3_4PWyZXGoWj8wtUHuF2MzzIq_y3KB1fWyIj4kH6uCX0QQaba4EoaZEEnlPHstSaoI0yklXBfb_XP5e9kEv5f1L2AljEolaP7BA-dTPReFwTdIJ0PhW3Sh1sEJzp16iRxXi4APexS5B9Yf8Dn1flts8kvZWNbD4xK38Q3cOW8owyWk5Sescg8JYOcT776NNHCWIarkNR1wjWnd31HP6FMnxn1feLEXoydFAEdbilD4pHPer1LEknUC9Sac7HLRFG_Rw-0HPVQ1QQJMQZGw3lazH-3vLor_KQliDGDcAGRGt5zsoOGz9Lr0JLQ2PV7oDf7JgaSpeHZgXcQO3_sg1RW6kMAN6iscOxfC88dEo9m2IFxudYpZme4OZq3IKbMyBCa1K2D2udi_EN9Tb03jWCvFQcZtvC6l7oCZVP-_Hy_Hf_CfhVyjXcp-dwusDGHc-tBhgDeOllh8SDkUvTC9YfD_voCWIKn2F7_oDSNDMGm6Ky_VzLCJVk83VAKTgX0FJoSqBsAhHwHHpevURZV-LrOn9-hGpnQZGd8FEl75sYn-QiU7pPGoA68NTEhzVkv27y6Q7GvcwdmVyVbE1I3RhsbI0mSAkz3F9MIOYdFmSdX3etKlQmozDS_RL-DbTHoI1JftHCdvxmdIZyJdUI-Yfm5-RXuUmaSrdc7jceRyoy0)
1. [Edit this diagram with plantuml.com](http://www.plantuml.com/plantuml/uml/nLTVRzis47_Nf-3R_g0nYkrXG14KJJDt2B2vHfn-05gwoEme7dSyfJ6IVFUP8aA8YfpGrfQ-9F3l_j_zZgG-E9R4RQCBLqeDuktijxT5yOEGDwWgOGdN6XedlYdU26bg3PKe2RyvSGt0XVjR6Ij9Da8h0xor891uWqLHQkcS-EA0n5qXnCz2LUATK8QXta6dfpKO8CbGlN_VYJSEdDU-y6gEIPawmOrie2_n-5cx2qMA5RYTI1B9xMbGy3w75dQ31XPjimRgmz18fVi0AzRmJe1f6ny76xY4Mg6vw1Nmuw-__2Gkvv_cb_F5-NmbRBlMgfm5HJTJLPUxbex_n5gPdI6h1XpBndQIai3NViPF9AsjHYGFZmqD4V9QcZOWkbYLQE4O3q9Ku4fIbXMQY4ugPmdKAEUoIseTKCOSF67Y5EZ8wJdVfmophf_UQkfa_sbts9m8oULq0wt_eT3q9zIIwlziW3UOR1I5C1-nsgQmisWSxCFFiOA8JomKzHZzdgFi5LfLvjl4CC4kYrc4EcVOtckSDNlt5zLBDbqMqmdo1qgTrmCX74jkNH_n3gzfeARS9y9crKXYjm7WAvPBBJsXreWQf9cKSHZRSsdNErb6qxsBxzJq1TpoYi6wM49QWInPtIZUNe95ufVJEalq7zCpo90ibmUkjXR6Dsvl3UEy8MaOybPkKhD2fBrnXZVlqGTp7GhA8KxGz8OZE-4Hujybmw1GCDK8EhKndiLvPz711QQjoRNpLWUluVIDGXEoFl8DCY5qkdmvuGaSjbHsJz3a6fLLfLYXaNg130ch0j4_NoybiIgLzPrETg42hMw0jdyzkPohDjxe5PIgaJfXeoykqZQ1PAni5r21ONzuZ5pxG4ahHtR24jOa6sy_zgXcIJNm4cshkqp7T0YaGpCYi6b71k9mieyAMFfyHYGb6ePRJWgGDQmuTEUhGxE36cbs8AMJElezgQNZBu5e7xV_ysDqyhPzNdy5pusCPJlm90YfiM8DiQTlmh2MX3UgWYh11rHxyz-iSc_9yYeSNjZYY1lxi6Csc9Xux6NOBQBoP0AawP-ZrdAD91dfjVfrwA9vaptfUzwzLjh7xCEUjECIlchk-1WrmqFCS0BV_Ep4_Vq9tebmU0Na0KzExkzwN_f5zy4yWWUhCG7ggD-zwulkhtYuxo3V_IBiIrIBBwx2_tc21fUpcV3_Uox7eH0qUXiW8R2lF8PuB2N1qYbv9_jtCxJkMPKHV_5ZvSLOIItxHA6z0t4GXJ57rXJx-1xwdg-OYXDE3KJfpqGdkHN2VBnyNDnxZkxL-G2cQXlzBm00)
2. Copy and paste the final UML into the UML Source section
3. Update the img src and edit link target to the current values

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ const goalUrl = join('api', 'activity-reports', 'goals');

const RenderGoalsObjectives = ({
// eslint-disable-next-line react/prop-types
grantIds, activityRecipientType, initialData,
grantIds, activityRecipientType,
}) => {
// eslint-disable-next-line react/prop-types
const activityRecipients = grantIds.map((activityRecipientId) => ({ activityRecipientId }));
const data = { activityRecipientType, activityRecipients };
const hookForm = useForm({
mode: 'onChange',
defaultValues: { goals: [], ...initialData },
defaultValues: { goals: [], ...data },
});
// eslint-disable-next-line react/prop-types
const activityRecipients = grantIds.map((id) => ({ activityRecipientId: id }));
const data = { ...initialData, activityRecipientType, activityRecipients };
return (
<FormProvider {...hookForm}>
{goalsObjectives.render({}, data)}
{goalsObjectives.render()}
</FormProvider>
);
};
Expand Down Expand Up @@ -61,21 +61,21 @@ describe('goals objectives', () => {
afterEach(() => fetchMock.restore());
describe('when activity recipient type is "grantee"', () => {
it('the display goals section is displayed', async () => {
renderGoals([1], 'grantee', {});
renderGoals([1], 'grantee');
await screen.findByText('Context');
expect(await screen.findByText('Goals and objectives')).toBeVisible();
});

it('the display goals section does not show if no grants are selected', async () => {
renderGoals([], 'grantee', {});
renderGoals([], 'grantee');
await screen.findByText('Context');
expect(screen.queryByText('Goals and objectives')).toBeNull();
});
});

describe('when activity recipient type is not "grantee"', () => {
it('the display goals section is not displayed', async () => {
renderGoals([1], 'nonGrantee', {});
renderGoals([1], 'nonGrantee');
await screen.findByText('Context');
expect(screen.queryByText('Goals and objectives')).toBeNull();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const GoalPicker = ({

const onRemoveGoal = (id) => {
const newGoals = selectedGoals.filter((selectedGoal) => selectedGoal.id !== id);
updateNewAvailableGoals((goals) => goals.filter((goal) => goal !== id));
updateNewAvailableGoals(newGoals);
setValue('goals', newGoals);
};

Expand Down
42 changes: 14 additions & 28 deletions frontend/src/pages/ActivityReport/Pages/goalsObjectives.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import {
Fieldset, Label, Textarea,
Expand All @@ -14,18 +13,21 @@ import GoalPicker from './components/GoalPicker';
import { getGoals } from '../../../fetchers/activityReports';
import { validateGoals } from './components/goalValidator';

const GoalsObjectives = ({
grantIds, activityRecipientType,
}) => {
const GoalsObjectives = () => {
const {
register,
register, watch,
} = useFormContext();
const recipients = watch('activityRecipients');
const activityRecipientType = watch('activityRecipientType');
const recipientGrantee = activityRecipientType === 'grantee';
const grantIds = recipientGrantee ? recipients.map((r) => r.activityRecipientId) : [];

const [availableGoals, updateAvailableGoals] = useState([]);
const hasGrants = grantIds.length > 0;

useDeepCompareEffect(() => {
const fetch = async () => {
if (activityRecipientType === 'grantee' && hasGrants) {
if (recipientGrantee && hasGrants) {
const fetchedGoals = await getGoals(grantIds);
updateAvailableGoals(fetchedGoals);
}
Expand All @@ -38,7 +40,7 @@ const GoalsObjectives = ({
<Helmet>
<title>Goals and objectives</title>
</Helmet>
{activityRecipientType === 'grantee' && hasGrants
{recipientGrantee && hasGrants
&& (
<Fieldset className="smart-hub--report-legend smart-hub--form-section" legend="Goals and objectives">
<div id="goals-and-objectives" />
Expand All @@ -55,15 +57,7 @@ const GoalsObjectives = ({
);
};

GoalsObjectives.propTypes = {
grantIds: PropTypes.arrayOf(PropTypes.number),
activityRecipientType: PropTypes.string,
};

GoalsObjectives.defaultProps = {
activityRecipientType: '',
grantIds: [],
};
GoalsObjectives.propTypes = {};

const ReviewSection = () => {
const { watch } = useFormContext();
Expand Down Expand Up @@ -140,17 +134,9 @@ export default {
label: 'Goals and objectives',
path: 'goals-objectives',
review: false,
isPageComplete: (formData) => validateGoals(formData.goals) === true,
isPageComplete: (formData) => formData.activityRecipientType !== 'grantee' || validateGoals(formData.goals) === true,
reviewSection: () => <ReviewSection />,
render: (additionalData, formData) => {
const recipients = formData.activityRecipients || [];
const { activityRecipientType } = formData;
const grantIds = recipients.map((r) => r.activityRecipientId);
return (
<GoalsObjectives
activityRecipientType={activityRecipientType}
grantIds={grantIds}
/>
);
},
render: () => (
<GoalsObjectives />
),
};
9 changes: 9 additions & 0 deletions src/migrations/20210305172128-add-grant-goal-unique-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
up: async (queryInterface) => {
queryInterface.addIndex('GrantGoals', ['grantId', 'goalId', 'granteeId'], { unique: true });
},

down: async (queryInterface) => {
queryInterface.removeIndex('GrantGoals', ['grantId', 'goalId', 'granteeId']);
},
};
43 changes: 43 additions & 0 deletions src/migrations/20210305181116-create-objectives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
queryInterface.createTable('Objectives', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
goalId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Goals',
},
key: 'id',
},
},
title: {
type: Sequelize.TEXT,
},
ttaProvided: {
type: Sequelize.TEXT,
},
status: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
});
},

down: (queryInterface) => queryInterface.dropTable('Objectives'),
};
42 changes: 42 additions & 0 deletions src/migrations/20210305181122-create-activity-report-objectives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
queryInterface.createTable('ActivityReportObjectives', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
activityReportId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'ActivityReports',
},
},
},
objectiveId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Objectives',
},
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
});
},

down: (queryInterface) => queryInterface.dropTable('ActivityReportObjectives'),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module.exports = {
up: async (queryInterface) => {
await queryInterface.dropTable('ActivityReportGoals');
},

down: async (queryInterface, Sequelize) => {
await queryInterface.createTable('ActivityReportGoals', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
activityReportId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'ActivityReports',
},
},
},
goalId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Goals',
},
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
});
},
};
6 changes: 3 additions & 3 deletions src/models/activityRecipient.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export default (sequelize, DataTypes) => {
activityRecipientId: {
type: DataTypes.VIRTUAL,
get() {
if (this.grant) {
return this.grant.id;
if (this.grantId) {
return this.grantId;
}
return this.nonGrantee.id;
return this.nonGranteeId;
},
},
name: {
Expand Down
16 changes: 12 additions & 4 deletions src/models/activityReport.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Model } from 'sequelize';
import { uniqBy } from 'lodash';
import moment from 'moment';
import { REPORT_STATUSES } from '../constants';

Expand Down Expand Up @@ -31,11 +32,11 @@ export default (sequelize, DataTypes) => {
ActivityReport.hasMany(models.File, { foreignKey: 'activityReportId', as: 'otherResources' });
ActivityReport.hasMany(models.NextStep, { foreignKey: 'activityReportId', as: 'specialistNextSteps' });
ActivityReport.hasMany(models.NextStep, { foreignKey: 'activityReportId', as: 'granteeNextSteps' });
ActivityReport.belongsToMany(models.Goal, {
through: models.ActivityReportGoal,
ActivityReport.belongsToMany(models.Objective, {
through: models.ActivityReportObjective,
foreignKey: 'activityReportId',
otherKey: 'goalId',
as: 'goals',
otherKey: 'objectiveId',
as: 'objectives',
});
}
}
Expand Down Expand Up @@ -151,6 +152,13 @@ export default (sequelize, DataTypes) => {
allowNull: false,
type: DataTypes.DATE,
},
goals: {
type: DataTypes.VIRTUAL,
get() {
const objectives = this.objectives || [];
return uniqBy(objectives.map((o) => o.goal), 'id');
},
},
lastSaved: {
type: DataTypes.VIRTUAL,
get() {
Expand Down
Loading

0 comments on commit cb1b865

Please sign in to comment.