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