diff --git a/app/controllers/api/v2/children_controller.rb b/app/controllers/api/v2/children_controller.rb
index c869ef4ff9..1bb13700f3 100644
--- a/app/controllers/api/v2/children_controller.rb
+++ b/app/controllers/api/v2/children_controller.rb
@@ -16,8 +16,8 @@ def traces
alias select_updated_fields_super select_updated_fields
def select_updated_fields
changes = @record.saved_changes_to_record.keys
- @updated_field_names = select_updated_fields_super + @record.current_care_arrangements_changes(changes)
- @updated_field_names << 'family_details_section' if @record.family&.family_members_changed?
+ @updated_field_names = select_updated_fields_super + @record.current_care_arrangements_changes(changes) +
+ @record.family_changes(changes)
end
def create_family
diff --git a/app/javascript/components/record-form/components/record-form/component.jsx b/app/javascript/components/record-form/components/record-form/component.jsx
index a3d32e2289..f07bb6c4ba 100644
--- a/app/javascript/components/record-form/components/record-form/component.jsx
+++ b/app/javascript/components/record-form/components/record-form/component.jsx
@@ -13,19 +13,14 @@ import PageContainer from "../../../page";
import LoadingIndicator from "../../../loading-indicator";
import { clearSelectedRecord, fetchRecord, saveRecord, setSelectedRecord } from "../../../records";
import { RECORD_TYPES, RECORD_TYPES_PLURAL, REFERRAL } from "../../../../config";
-import { getIsProcessingSomeAttachment, getLoadingRecordState, getSelectedRecord } from "../../../records/selectors";
+import { getIsProcessingSomeAttachment, getLoadingRecordState } from "../../../records/selectors";
import { clearRecordAttachments, fetchRecordsAlerts } from "../../../records/action-creators";
import useIncidentFromCase from "../../../records/use-incident-form-case";
import SaveAndRedirectDialog from "../../../save-and-redirect-dialog";
import { fetchReferralUsers } from "../../../record-actions/transitions/action-creators";
import { SERVICES_SUBFORM } from "../../../record-actions/add-service/constants";
import { getLoadingState, getErrors, getSelectedForm } from "../../selectors";
-import {
- clearDataProtectionInitialValues,
- clearValidationErrors,
- setPreviousRecord,
- setSelectedForm
-} from "../../action-creators";
+import { clearDataProtectionInitialValues, clearValidationErrors, setPreviousRecord } from "../../action-creators";
import Nav from "../../nav";
import { RecordForm, RecordFormToolbar } from "../../form";
import css from "../../styles.css";
@@ -60,7 +55,6 @@ const Component = ({
}) => {
let submitForm = null;
const mobileDisplay = useMediaQuery(theme => theme.breakpoints.down("sm"));
- const [selectedRecordChanged, setSelectedRecordChanged] = useState(false);
const { state: locationState } = useLocation();
const history = useHistory();
@@ -91,7 +85,6 @@ const Component = ({
const loadingRecord = useMemoizedSelector(state => getLoadingRecordState(state, params.recordType));
const errors = useMemoizedSelector(state => getErrors(state));
const selectedForm = useMemoizedSelector(state => getSelectedForm(state));
- const selectedRecord = useMemoizedSelector(state => getSelectedRecord(state, params.recordType));
const isProcessingSomeAttachment = useMemoizedSelector(state =>
getIsProcessingSomeAttachment(state, params.recordType)
);
@@ -245,19 +238,6 @@ const Component = ({
}
}, [selectedForm]);
- useEffect(() => {
- if (params.id && selectedRecord && selectedRecord !== params.id && containerMode.isShow) {
- setSelectedRecordChanged(true);
- }
- }, [selectedRecord, containerMode.isShow, params.id]);
-
- useEffect(() => {
- if (selectedRecordChanged && containerMode.isShow && firstTab) {
- dispatch(setSelectedForm(firstTab.unique_id));
- setSelectedRecordChanged(false);
- }
- }, [selectedRecordChanged, containerMode.isShow, firstTab]);
-
const transitionProps = {
fetchable: isNotANewCase,
isReferral: REFERRAL === selectedForm,
@@ -332,6 +312,7 @@ const Component = ({
formNav={formNav}
handleToggleNav={handleToggleNav}
isNew={containerMode.isNew}
+ isShow={containerMode.isShow}
mobileDisplay={mobileDisplay}
recordType={params.recordType}
selectedForm={selectedForm}
@@ -339,6 +320,7 @@ const Component = ({
toggleNav={toggleNav}
primeroModule={selectedModule.primeroModule}
hasForms={hasForms}
+ recordId={params.id}
formikValuesForNav={formikValuesForNav}
/>
diff --git a/app/javascript/components/record-form/form/record-form.jsx b/app/javascript/components/record-form/form/record-form.jsx
index 647690343e..8b27c62045 100644
--- a/app/javascript/components/record-form/form/record-form.jsx
+++ b/app/javascript/components/record-form/form/record-form.jsx
@@ -80,7 +80,11 @@ const RecordForm = ({
const redirectToIncident = RECORD_TYPES.cases === recordType ? { redirectToIncident: false } : {};
if (record) {
- const recordFormValues = { ...initialValues, ...record.toJS(), ...redirectToIncident };
+ const recordFormValues = {
+ ...(mode.isNew ? constructInitialValues(forms.values()) : {}),
+ ...record.toJS(),
+ ...redirectToIncident
+ };
const subformValues = sortSubformValues(recordFormValues, forms.values());
diff --git a/app/javascript/components/record-form/form/record-form.unit.test.js b/app/javascript/components/record-form/form/record-form.unit.test.js
index be76e5bea9..686838172a 100644
--- a/app/javascript/components/record-form/form/record-form.unit.test.js
+++ b/app/javascript/components/record-form/form/record-form.unit.test.js
@@ -4,7 +4,7 @@ import { TextField as MuiTextField } from "formik-material-ui";
import { NUMERIC_FIELD } from "../constants";
import { getRecordForms } from "../selectors";
-import { RECORD_TYPES } from "../../../config";
+import { RECORD_TYPES, CASES } from "../../../config";
import { setupMountedComponent, stub } from "../../../test";
import { FieldRecord, FormSectionRecord } from "../records";
@@ -306,6 +306,86 @@ describe("", () => {
});
});
+ describe("when an record exists", () => {
+ const initialStateRecordExists = Map({
+ forms: Map({
+ formSections: Map({
+ 1: FormSectionRecord({
+ id: 1,
+ name: {
+ en: "Form Section 1"
+ },
+ unique_id: "form_section_1",
+ module_ids: ["some_module"],
+ visible: true,
+ is_nested: false,
+ parent_form: RECORD_TYPES.cases,
+ fields: [1, 2, 3]
+ })
+ }),
+ fields: Map({
+ 1: FieldRecord({
+ id: 1,
+ name: "field_1",
+ display_name: {
+ en: "Field 1"
+ },
+ type: TEXT_FIELD_NAME,
+ required: true,
+ visible: true
+ }),
+ 2: FieldRecord({
+ id: 2,
+ name: "field_2",
+ display_name: {
+ en: "Field 2"
+ },
+ type: TEXT_FIELD_NAME,
+ visible: true,
+ selected_value: "field_2_value"
+ }),
+ 3: FieldRecord({
+ id: 3,
+ name: "field_age",
+ display_name: {
+ en: "Field Age"
+ },
+ type: NUMERIC_FIELD,
+ visible: true
+ })
+ })
+ })
+ });
+ const queryRecordExists = {
+ recordType: RECORD_TYPES.cases,
+ primeroModule: "some_module"
+ };
+ const formsRecordsEdit = getRecordForms(initialStateRecordExists, queryRecordExists);
+
+ const recordValues = { age: "4", name_first: "test" };
+
+ it("should set the values for the case selected", () => {
+ const { component: fromCaseComponent } = setupMountedComponent(RecordForm, {
+ bindSubmitForm: () => {},
+ forms: formsRecordsEdit,
+ handleToggleNav: () => {},
+ mobileDisplay: false,
+ mode: { isNew: false, isEdit: true, isShow: false },
+ onSubmit: () => {},
+ record: fromJS(recordValues),
+ recordType: CASES,
+ selectedForm: "form_section_1",
+ incidentFromCase: {},
+ externalComponents: () => {},
+ setFormikValuesForNav: () => {}
+ });
+
+ expect(fromCaseComponent.find(FormikForm).props().values).to.deep.equal({
+ ...recordValues
+ });
+ });
+ });
+
it("renders component with valid props", () => {
const incidentsProps = { ...component.find(RecordForm).props() };
diff --git a/app/javascript/components/record-form/nav/component.jsx b/app/javascript/components/record-form/nav/component.jsx
index 5a7335abf5..5a145ef628 100644
--- a/app/javascript/components/record-form/nav/component.jsx
+++ b/app/javascript/components/record-form/nav/component.jsx
@@ -11,6 +11,7 @@ import { useI18n } from "../../i18n";
import { INCIDENT_FROM_CASE, RECORD_INFORMATION_GROUP, RECORD_TYPES, RECORD_OWNER } from "../../../config";
import {
getIncidentFromCaseForm,
+ getPreviousRecordType,
getRecordFormsByUniqueId,
getRecordInformationFormIds,
getValidationErrors
@@ -33,8 +34,10 @@ const Component = ({
hasForms,
handleToggleNav,
isNew,
+ isShow,
mobileDisplay,
recordType,
+ recordId,
selectedRecord,
toggleNav,
primeroModule,
@@ -47,6 +50,7 @@ const Component = ({
const [open, setOpen] = useState("");
const [previousGroup, setPreviousGroup] = useState("");
+ const [selectedRecordChanged, setSelectedRecordChanged] = useState(false);
const incidentFromCaseForm = useMemoizedSelector(state =>
getIncidentFromCaseForm(state, { recordType, i18n, primeroModule })
@@ -66,6 +70,8 @@ const Component = ({
const recordInformationFormIds = useMemoizedSelector(state =>
getRecordInformationFormIds(state, { recordType: RECORD_TYPES[recordType], primeroModule })
);
+ const previousRecordType = useMemoizedSelector(state => getPreviousRecordType(state));
+ const selectedRecordId = useMemoizedSelector(state => getSelectedRecord(state, recordType));
const formGroupLookup = useOptions({
source: buildFormGroupUniqueId(primeroModule, RECORD_TYPES[recordType].replace("_", "-"))
@@ -161,6 +167,25 @@ const Component = ({
}
}, [history.action, firstSelectedForm?.form_group_id]);
+ useEffect(() => {
+ if (recordId && selectedRecordId && selectedRecordId !== recordId && isShow) {
+ setSelectedRecordChanged(true);
+ }
+ }, [selectedRecord, isShow, recordId]);
+
+ useEffect(() => {
+ if (selectedRecordChanged && isShow && firstTab) {
+ dispatch(setSelectedForm(firstTab.unique_id));
+ setSelectedRecordChanged(false);
+ }
+ }, [selectedRecordChanged, isShow, firstTab]);
+
+ useEffect(() => {
+ if (firstTab && recordType && previousRecordType && recordType !== previousRecordType) {
+ dispatch(setSelectedForm(firstTab.unique_id));
+ }
+ }, [recordType, previousRecordType, firstTab]);
+
const drawerClasses = { paper: css.drawerPaper };
if (!formNav) return null;
@@ -217,8 +242,10 @@ Component.propTypes = {
handleToggleNav: PropTypes.func.isRequired,
hasForms: PropTypes.bool,
isNew: PropTypes.bool,
+ isShow: PropTypes.bool,
mobileDisplay: PropTypes.bool.isRequired,
primeroModule: PropTypes.string,
+ recordId: PropTypes.string,
recordType: PropTypes.string,
selectedForm: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
selectedRecord: PropTypes.string,
diff --git a/app/javascript/components/record-form/selectors.js b/app/javascript/components/record-form/selectors.js
index 76f8db8687..567cdf5817 100644
--- a/app/javascript/components/record-form/selectors.js
+++ b/app/javascript/components/record-form/selectors.js
@@ -530,6 +530,10 @@ export const getShouldFetchRecord = (state, { id, recordType }) => {
return !state.getIn([NAMESPACE, "previousRecord"], fromJS({})).equals(fromJS({ id, recordType }));
};
+export const getPreviousRecordType = state => {
+ return state.getIn([NAMESPACE, "previousRecord", "recordType"]);
+};
+
export const getWritableFields = createCachedSelector(
(state, query) => getRecordForms(state, { ...query, writable: true, checkPermittedForms: true }),
formSections => formSections.flatMap(formSection => formSection.fields)
diff --git a/app/javascript/components/record-form/selectors.unit.test.js b/app/javascript/components/record-form/selectors.unit.test.js
index a67433b143..41a04ed028 100644
--- a/app/javascript/components/record-form/selectors.unit.test.js
+++ b/app/javascript/components/record-form/selectors.unit.test.js
@@ -1319,4 +1319,14 @@ describe(" - Selectors", () => {
).to.deep.equals(fromJS(["name_first"]));
});
});
+
+ describe("getPreviousRecordType", () => {
+ it("returns the previousRecordType", () => {
+ const stateWithPreviousRecord = fromJS({
+ forms: { previousRecord: { id: "001", recordType: "cases" } }
+ });
+
+ expect(selectors.getPreviousRecordType(stateWithPreviousRecord)).to.equals("cases");
+ });
+ });
});
diff --git a/app/models/child.rb b/app/models/child.rb
index 41f1833528..9b93bc15b0 100644
--- a/app/models/child.rb
+++ b/app/models/child.rb
@@ -221,6 +221,7 @@ def update_properties(user, data)
self.family_id = data.delete('family_id') if data.key?('family_id')
self.registry_record_id = data.delete('registry_record_id') if data.key?('registry_record_id')
self.mark_for_reopen = @incidents_to_save.present?
+ update_family_data(data)
super_update_properties(user, data)
end
@@ -284,6 +285,12 @@ def display_id
case_id_display
end
+ def family_number
+ return super unless family.present?
+
+ family.family_number
+ end
+
def day_of_birth
return nil unless date_of_birth.is_a? Date
diff --git a/app/models/concerns/family_linkable.rb b/app/models/concerns/family_linkable.rb
index 7e1f07ae18..2c93a800f4 100644
--- a/app/models/concerns/family_linkable.rb
+++ b/app/models/concerns/family_linkable.rb
@@ -11,14 +11,13 @@ module FamilyLinkable
before_save :associate_to_family
before_save :update_family_members
after_save :associate_family_member
- after_save :update_family
+ after_save :save_family
end
def stamp_family_fields
return unless changes_to_save.key?('family_id')
self.family_id_display = family&.family_id_display
- self.family_number = family&.family_number
end
def associate_to_family
@@ -39,11 +38,15 @@ def associate_family_member
end
end
- def update_family
+ def update_family_data(child_data)
return unless family.present?
- family.family_number = family_number if saved_changes_to_record.keys.include?('family_number')
- return unless family.has_changes_to_save?
+ changed_family_fields = FamilyLinkageService::GLOBAL_FAMILY_FIELDS & child_data.keys
+ changed_family_fields.each { |field| family.data[field] = child_data.delete(field) }
+ end
+
+ def save_family
+ return unless family.present? && family.has_changes_to_save?
family.save!
end
@@ -84,4 +87,18 @@ def find_family_detail(family_detail_id)
def family_members
(family&.family_members || []).reject { |member| member['unique_id'] == family_member_id }
end
+
+ def family_changes(changes)
+ return [] unless family.present?
+
+ changes ||= saved_changes_to_record.keys
+ if changes.include?('family_id_display')
+ return FamilyLinkageService::GLOBAL_FAMILY_FIELDS + ['family_details_section']
+ end
+
+ field_names = []
+ field_names << 'family_details_section' if family.family_members_changed?
+ field_names += FamilyLinkageService::GLOBAL_FAMILY_FIELDS & family.saved_changes_to_record.keys
+ field_names
+ end
end
diff --git a/app/services/record_data_service.rb b/app/services/record_data_service.rb
index ed9b0f7740..a5fa8f3ea3 100644
--- a/app/services/record_data_service.rb
+++ b/app/services/record_data_service.rb
@@ -98,7 +98,6 @@ def embed_family_info(data, record, selected_field_names)
data['family_id'] = record.family_id if selected_field_names.include?('family_id')
data['family_member_id'] = record.family_member_id if selected_field_names.include?('family_member_id')
- data['family_number'] = record.family_number if selected_field_names.include?('family_number')
data = embed_family_details(data, record, selected_field_names)
embed_family_details_section(data, record, selected_field_names)
end
diff --git a/config/version.rb b/config/version.rb
index f8d7ce0316..d83e9e2e71 100644
--- a/config/version.rb
+++ b/config/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class Primero::Application
- VERSION = '2.7.0'
+ VERSION = '2.8.0-rc1'
end
diff --git a/db/configuration/users/roles.rb b/db/configuration/users/roles.rb
index dc183134e4..28935dd99a 100644
--- a/db/configuration/users/roles.rb
+++ b/db/configuration/users/roles.rb
@@ -156,7 +156,9 @@ def create_or_update_role(role_hash)
Permission::ENABLE_DISABLE_RECORD,
Permission::REOPEN,
Permission::CLOSE,
- Permission::VIEW_PHOTO
+ Permission::VIEW_PHOTO,
+ Permission::VIEW_FAMILY_RECORD,
+ Permission::LINK_FAMILY_RECORD
]
),
Permission.new(
@@ -367,7 +369,9 @@ def create_or_update_role(role_hash)
Permission::DISPLAY_VIEW_PAGE,
Permission::INCIDENT_DETAILS_FROM_CASE,
Permission::VIEW_PHOTO,
- Permission::CASE_FROM_FAMILY
+ Permission::CASE_FROM_FAMILY,
+ Permission::VIEW_FAMILY_RECORD,
+ Permission::LINK_FAMILY_RECORD
]
),
Permission.new(
@@ -537,7 +541,9 @@ def create_or_update_role(role_hash)
Permission::DISPLAY_VIEW_PAGE,
Permission::RECEIVE_TRANSFER,
Permission::VIEW_PHOTO,
- Permission::CASE_FROM_FAMILY
+ Permission::CASE_FROM_FAMILY,
+ Permission::VIEW_FAMILY_RECORD,
+ Permission::LINK_FAMILY_RECORD
]
),
Permission.new(
diff --git a/spec/requests/api/v2/children_controller_spec.rb b/spec/requests/api/v2/children_controller_spec.rb
index 8d7c29536e..bb2603e49d 100644
--- a/spec/requests/api/v2/children_controller_spec.rb
+++ b/spec/requests/api/v2/children_controller_spec.rb
@@ -173,7 +173,9 @@
}
)
@family3 = Family.create!(data: { family_number: '003', family_name: 'FamilyTest' })
- @family4 = Family.create!(data: { family_number: '004', family_name: 'FamilyTest2' })
+ @family4 = Family.create!(
+ data: { family_number: '004', family_size: 2, family_notes: 'NotesFamilyTest2', family_name: 'FamilyTest2' }
+ )
@family5 = Family.create!(
data: {
family_number: '005',
@@ -973,6 +975,8 @@
params = {
data: {
family_number: '002',
+ family_size: 5,
+ family_notes: 'Family002Notes',
family_details_section: [
{
unique_id: member2_unique_id,
@@ -992,6 +996,8 @@
expect(response).to have_http_status(200)
expect(json['data']['family_number']).to eq('002')
+ expect(json['data']['family_size']).to eq(5)
+ expect(json['data']['family_notes']).to eq('Family002Notes')
expect(json['data']['family_details_section']).to eq(
[
{
@@ -1020,6 +1026,8 @@
]
)
expect(family.family_number).to eq('002')
+ expect(family.family_size).to eq(5)
+ expect(family.family_notes).to eq('Family002Notes')
expect(family.family_members).to eq(
[
{
@@ -1092,7 +1100,7 @@
end
end
- it 'links an existing record to a family' do
+ it 'links an existing record to a family and returns global fields' do
login_for_test(
permissions: [
Permission.new(resource: Permission::CASE, actions: [Permission::WRITE, Permission::LINK_FAMILY_RECORD])
@@ -1107,6 +1115,9 @@
expect(response).to have_http_status(200)
expect(json['data']['id']).not_to be_empty
expect(json['data']['family_id']).to eq(@family4.id)
+ expect(json['data']['family_number']).to eq(@family4.family_number)
+ expect(json['data']['family_size']).to eq(@family4.family_size)
+ expect(json['data']['family_notes']).to eq(@family4.family_notes)
expect(json['data']['family_member_id']).to eq(@family4.family_members[0]['unique_id'])
expect(@family4.family_members[0]['unique_id']).not_to be_empty
expect(@family4.family_members[0]['relation_name']).to eq(@case9.name)
diff --git a/spec/support/fake_devise_login.rb b/spec/support/fake_devise_login.rb
index 31eff2fd98..568ad7da5a 100644
--- a/spec/support/fake_devise_login.rb
+++ b/spec/support/fake_devise_login.rb
@@ -42,8 +42,10 @@ module FakeDeviseLogin
]
)
),
- Field.new(name: 'registry_type', type: 'text_field', display_name_en: 'Registry Type'),
- Field.new(name: 'family_number', type: 'text_field', display_name_en: 'Family Number')
+ Field.new(name: 'registry_type', type: Field::TEXT_FIELD, display_name_en: 'Registry Type'),
+ Field.new(name: 'family_number', type: Field::TEXT_FIELD, display_name_en: 'Family Number'),
+ Field.new(name: 'family_notes', type: Field::TEXT_FIELD, display_name_en: 'Family Notes'),
+ Field.new(name: 'family_size', type: Field::NUMERIC_FIELD, display_name_en: 'Family Size')
].freeze
def permission_case