From a5a10fc5bdfdcdd3ded5b5e40f424b9a8c85cbf8 Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Tue, 12 Sep 2023 16:33:56 -0600 Subject: [PATCH 1/4] R2-2609 - Users redirected to Child's Details form after creating case Only reset the navigation if the selected record changed --- .../components/record-form/component.jsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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 3343ca7af3..a3d32e2289 100644 --- a/app/javascript/components/record-form/components/record-form/component.jsx +++ b/app/javascript/components/record-form/components/record-form/component.jsx @@ -13,7 +13,7 @@ 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 } from "../../../records/selectors"; +import { getIsProcessingSomeAttachment, getLoadingRecordState, getSelectedRecord } 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"; @@ -60,6 +60,7 @@ 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(); @@ -90,6 +91,7 @@ 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) ); @@ -244,10 +246,17 @@ const Component = ({ }, [selectedForm]); useEffect(() => { - if (containerMode.isShow && firstTab && shouldFetchRecord) { + 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); } - }, [shouldFetchRecord]); + }, [selectedRecordChanged, containerMode.isShow, firstTab]); const transitionProps = { fetchable: isNotANewCase, From 421908cc73341a3f4f028301385554051ccb9949 Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Mon, 18 Sep 2023 09:33:40 -0600 Subject: [PATCH 2/4] R2-2615 - Users get Forbidden error if they create case from family details and do not have create case permission --- app/controllers/api/v2/children_controller.rb | 2 +- app/controllers/api/v2/families_controller.rb | 2 +- app/services/permitted_field_service.rb | 2 +- spec/requests/api/v2/children_controller_spec.rb | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/v2/children_controller.rb b/app/controllers/api/v2/children_controller.rb index 6dc80ec7a4..fbfbd0b5af 100644 --- a/app/controllers/api/v2/children_controller.rb +++ b/app/controllers/api/v2/children_controller.rb @@ -21,7 +21,7 @@ def select_updated_fields end def family - authorize! :create, Child + authorize! :case_from_family, Child @current_record = Child.find(family_params[:case_id]) @record = FamilyLinkageService.new_family_linked_child( current_user, @current_record, family_params[:family_detail_id] diff --git a/app/controllers/api/v2/families_controller.rb b/app/controllers/api/v2/families_controller.rb index 64025bfc44..27f1fec5c2 100644 --- a/app/controllers/api/v2/families_controller.rb +++ b/app/controllers/api/v2/families_controller.rb @@ -10,7 +10,7 @@ def query_scope end def create_case - authorize! :case_from_family, model_class + authorize! :case_from_family, Family @current_record = Family.find(create_case_params[:family_id]) @record = @current_record.new_child_from_family_member(current_user, create_case_params['family_member_id']) @record.save! diff --git a/app/services/permitted_field_service.rb b/app/services/permitted_field_service.rb index 530168f986..35c9b16b59 100644 --- a/app/services/permitted_field_service.rb +++ b/app/services/permitted_field_service.rb @@ -153,7 +153,7 @@ def permitted_registry_record_id def permitted_family_id return [] unless model_class == Child - if user.can?(:view_family_record, model_class) + if user.can?(:view_family_record, model_class) || user.can?(:case_from_family, model_class) return %w[family_id family_id_display family_member_id family_name family_number] end diff --git a/spec/requests/api/v2/children_controller_spec.rb b/spec/requests/api/v2/children_controller_spec.rb index c970046b6b..2f7e8dd00b 100644 --- a/spec/requests/api/v2/children_controller_spec.rb +++ b/spec/requests/api/v2/children_controller_spec.rb @@ -1109,7 +1109,7 @@ describe 'POST /api/v2/cases/:id/family' do it 'creates a new child linked to a family when there is no family record' do - login_for_test + login_for_test(permissions: [Permission.new(resource: Permission::CASE, actions: [Permission::CASE_FROM_FAMILY])]) params = { data: { family_detail_id: @member_unique_id3 } } @@ -1118,15 +1118,15 @@ expect(response).to have_http_status(200) expect(json['data']['id']).to eq(@case7.id) expect(json['data']['record']['id']).not_to be_nil - expect(json['data']).not_to have_key('family_id') - expect(json['data']['family_number']).to be_nil - expect(json['data']).not_to have_key('family_member_id') + expect(json['data']).to have_key('family_id') + expect(json['data']).to have_key('family_number') + expect(json['data']).to have_key('family_member_id') expect(json['data']['record']['sex']).to eq('male') expect(json['data']['record']['age']).to eq(5) end it 'creates a new child linked to a family when there is a family record' do - login_for_test(permissions: [Permission.new(resource: Permission::CASE, actions: [Permission::CREATE])]) + login_for_test(permissions: [Permission.new(resource: Permission::CASE, actions: [Permission::CASE_FROM_FAMILY])]) params = { data: { family_detail_id: @member_unique_id5 } } @@ -1134,9 +1134,9 @@ expect(response).to have_http_status(200) expect(json['data']['id']).to eq(@case8.id) - expect(json['data']).not_to have_key('family_id') + expect(json['data']['family_id']).to eq(@family2.id) expect(json['data']['family_number']).to eq(@family2.family_number) - expect(json['data']).not_to have_key('family_member_id') + expect(json['data']['family_member_id']).to eq(@member_unique_id2) expect(json['data']['record']['id']).not_to be_nil expect(json['data']['record']['sex']).to eq('male') expect(json['data']['record']['age']).to eq(4) @@ -1147,7 +1147,7 @@ permissions: [ Permission.new( resource: Permission::CASE, - actions: [Permission::CREATE, Permission::VIEW_FAMILY_RECORD] + actions: [Permission::CASE_FROM_FAMILY, Permission::VIEW_FAMILY_RECORD] ) ] ) From 8d67b85b5286f68bf822fa023b65619abf728f4c Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Tue, 19 Sep 2023 10:30:22 -0600 Subject: [PATCH 3/4] R2-2607 - Users cannot delete family details subform entries linked to family record from case * Users cannot delete a family member if the member is linked to a case. --- .../subform-field-array/component.jsx | 2 + .../subforms/subform-fields/component.jsx | 20 ++- .../subforms/subform-fields/component.spec.js | 132 ++++++++++++++++++ .../subform-fields/component.unit.test.js | 101 -------------- 4 files changed, 152 insertions(+), 103 deletions(-) create mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/component.spec.js delete mode 100644 app/javascript/components/record-form/form/subforms/subform-fields/component.unit.test.js diff --git a/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx b/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx index 76ab6caca4..b63eaeec9b 100644 --- a/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-field-array/component.jsx @@ -95,6 +95,8 @@ const Component = ({ parentForm={form} entryFilter={entryFilter} parentTitle={parentTitle} + isFamilyMember={isFamilyMember} + isFamilyDetail={isFamilyDetail} /> ); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx b/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx index 46fe10e42e..48a1fa5ab9 100644 --- a/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx +++ b/app/javascript/components/record-form/form/subforms/subform-fields/component.jsx @@ -38,7 +38,9 @@ const Component = ({ parentForm, isViolationAssociation, entryFilter = false, - parentTitle + parentTitle, + isFamilyMember, + isFamilyDetail }) => { const i18n = useI18n(); @@ -63,6 +65,18 @@ const Component = ({ const { isEdit, isNew } = mode; + const canDeleteFamilySubform = index => { + if (isFamilyDetail) { + return !formik?.values?.family_id; + } + + if (isFamilyMember) { + return !values[index]?.case_id; + } + + return true; + }; + const handleDelete = () => { const index = selectedIndex; @@ -173,7 +187,7 @@ const Component = ({ rest={{ onClick: () => handleOpenModal(index), // TODO: disable only when there is no violation or association - disabled: isViolationSubform || isViolationAssociation + disabled: isViolationSubform || isViolationAssociation || !canDeleteFamilySubform(index) }} /> ) : null} @@ -215,6 +229,8 @@ Component.propTypes = { entryFilter: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), field: PropTypes.object.isRequired, formik: PropTypes.object.isRequired, + isFamilyDetail: PropTypes.bool, + isFamilyMember: PropTypes.bool, isTracesSubform: PropTypes.bool, isViolationAssociation: PropTypes.bool, isViolationSubform: PropTypes.bool, diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/component.spec.js b/app/javascript/components/record-form/form/subforms/subform-fields/component.spec.js new file mode 100644 index 0000000000..a699338122 --- /dev/null +++ b/app/javascript/components/record-form/form/subforms/subform-fields/component.spec.js @@ -0,0 +1,132 @@ +import { mountedComponent, screen } from "test-utils"; + +import { FieldRecord, FormSectionRecord } from "../../../records"; + +import SubformFields from "./component"; + +describe("", () => { + const props = { + arrayHelpers: {}, + parentForm: { + id: 33, + unique_id: "family_details", + description: { + en: "Family Details" + }, + name: { + en: "Family Details" + }, + visible: true, + is_first_tab: false, + order: 10, + order_form_group: 30, + parent_form: "case", + editable: true, + module_ids: ["primeromodule-cp"], + form_group_id: "identification_registration", + form_group_name: { + en: "Identification / Registration" + } + }, + field: FieldRecord({ + name: "family_details_section", + displayName: { en: "Family Details" }, + subform_section_id: FormSectionRecord({ + unique_id: "family_section", + collapsed_field_names: ["relation_name"], + fields: [ + FieldRecord({ + name: "relation_name", + visible: true, + type: "text_field" + }), + FieldRecord({ + name: "relation_child_is_in_contact", + visible: true, + type: "text_field" + }) + ] + }) + }), + locale: "en", + setDialogIsNew: () => {}, + setOpen: () => {}, + i18n: { t: value => value, locale: "en" }, + values: [ + { + relation_name: "Family1", + relation_child_is_in_contact: "" + } + ], + mode: { + isShow: true + }, + recordType: "cases" + }; + + it("renders the subform fields", () => { + mountedComponent(); + + expect(screen.queryByText("Family1")).toBeTruthy(); + }); + + describe("when is violation or violation association", () => { + it("renders the Delete button disabled", () => { + mountedComponent( + + ); + + expect(screen.queryAllByRole("button")[0]).toHaveAttribute("disabled"); + }); + }); + + describe("Family Detail subform", () => { + describe("when is associated to a family", () => { + it("renders the Delete button disabled", () => { + mountedComponent( + + ); + + expect(screen.queryAllByRole("button")).toHaveLength(2); + expect(screen.queryAllByRole("button")[0]).toHaveAttribute("disabled"); + }); + }); + + describe("when is not associated to a family", () => { + it("renders the Delete button enabled", () => { + mountedComponent(); + + expect(screen.queryAllByRole("button")).toHaveLength(2); + expect(screen.queryAllByRole("button")[0]).not.toHaveAttribute("disabled"); + }); + }); + }); + + describe("Family Member subform", () => { + describe("when is associated to a case", () => { + it("renders the Delete button disabled", () => { + mountedComponent( + + ); + + expect(screen.queryAllByRole("button")).toHaveLength(2); + expect(screen.queryAllByRole("button")[0]).toHaveAttribute("disabled"); + }); + }); + + describe("when is not associated to a case", () => { + it("renders the Delete button enabled", () => { + mountedComponent(); + + expect(screen.queryAllByRole("button")).toHaveLength(2); + expect(screen.queryAllByRole("button")[0]).not.toHaveAttribute("disabled"); + }); + }); + }); +}); diff --git a/app/javascript/components/record-form/form/subforms/subform-fields/component.unit.test.js b/app/javascript/components/record-form/form/subforms/subform-fields/component.unit.test.js deleted file mode 100644 index be7d48288d..0000000000 --- a/app/javascript/components/record-form/form/subforms/subform-fields/component.unit.test.js +++ /dev/null @@ -1,101 +0,0 @@ -import { setupMountedComponent } from "../../../../../test"; -import { FieldRecord, FormSectionRecord } from "../../../records"; -import ActionButton from "../../../../action-button"; - -import SubformFields from "./component"; - -describe("", () => { - const props = { - arrayHelpers: {}, - parentForm: { - id: 33, - unique_id: "family_details", - description: { - en: "Family Details" - }, - name: { - en: "Family Details" - }, - visible: true, - is_first_tab: false, - order: 10, - order_form_group: 30, - parent_form: "case", - editable: true, - module_ids: ["primeromodule-cp"], - form_group_id: "identification_registration", - form_group_name: { - en: "Identification / Registration" - } - }, - field: FieldRecord({ - name: "family_details_section", - displayName: { en: "Family Details" }, - subform_section_id: FormSectionRecord({ - unique_id: "family_section", - fields: [ - FieldRecord({ - name: "relation_name", - visible: true, - type: "text_field" - }), - FieldRecord({ - name: "relation_child_is_in_contact", - visible: true, - type: "text_field" - }) - ] - }) - }), - locale: "en", - setDialogIsNew: () => {}, - setOpen: () => {}, - i18n: { t: value => value, locale: "en" }, - values: [], - mode: { - isShow: true - }, - recordType: "cases" - }; - - const formProps = { - initialValues: { - relation_name: "", - relation_child_is_in_contact: "" - } - }; - - let component; - - beforeEach(() => { - ({ component } = setupMountedComponent(SubformFields, props, {}, [], formProps)); - }); - - it("render the SubformFields", () => { - expect(component.find(SubformFields)).lengthOf(1); - }); - - context("When is violation or violation association", () => { - const { component: componentSubform } = setupMountedComponent( - SubformFields, - { - ...props, - isViolationSubform: true, - isViolationAssociation: true, - mode: { - isEdit: true - }, - values: ["something"] - }, - {}, - [], - formProps - ); - - it("render the Delete button disabled", () => { - const deleteButtonProps = componentSubform.find(ActionButton).first().props(); - - expect(deleteButtonProps.rest.disabled).to.be.true; - }); - }); -}); From c50cffe32879707001577504f8c712cf07e5d578 Mon Sep 17 00:00:00 2001 From: Dennis Hernandez Date: Tue, 19 Sep 2023 14:33:25 -0600 Subject: [PATCH 4/4] R2-2614 - Update ID for relation_is_caregiver field on Family Member --- db/configuration/forms/family/family_details.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/db/configuration/forms/family/family_details.rb b/db/configuration/forms/family/family_details.rb index 92309affa7..d7654372c0 100644 --- a/db/configuration/forms/family/family_details.rb +++ b/db/configuration/forms/family/family_details.rb @@ -12,6 +12,10 @@ matchable: true, help_text_en: 'This field can be copied to/from the Case but is not s shared field and '\ 'can be edited on the Family record.'), + Field.new(name: 'family_relation_is_caregiver', + type: 'tick_box', + display_name_en: 'Is this person the caregiver for one of the children in this family?', + tick_box_label_en: 'Yes'), Field.new(name: 'family_relationship_notes', type: 'textarea', display_name_en: 'Notes on their role in the family.',