diff --git a/i18n/en.pot b/i18n/en.pot index 2f414dc91a..d4d4fb3813 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -774,6 +774,9 @@ msgstr "Save {{trackedEntityTypeName}}" msgid "Save {{trackedEntityName}}" msgstr "Save {{trackedEntityName}}" +msgid "Enter details now is not available when creating a relationship" +msgstr "Enter details now is not available when creating a relationship" + msgid "Save new {{trackedEntityTypeName}} and link" msgstr "Save new {{trackedEntityTypeName}} and link" @@ -1376,8 +1379,8 @@ msgstr "{{trackedEntityTypeName}} profile" msgid "tracked entity instance" msgstr "tracked entity instance" -msgid "Link to an existing {{linkableStageLabel}}" -msgstr "Link to an existing {{linkableStageLabel}}" +msgid "Choose a {{linkableStageLabel}} event" +msgstr "Choose a {{linkableStageLabel}} event" msgid "Choose a {{linkableStageLabel}}" msgstr "Choose a {{linkableStageLabel}}" @@ -1388,14 +1391,17 @@ msgstr "{{ linkableStageLabel }} is not repeatable" msgid "{{ linkableStageLabel }} has no linkable events" msgstr "{{ linkableStageLabel }} has no linkable events" +msgid "Actions - {{relationshipName}}" +msgstr "Actions - {{relationshipName}}" + msgid "Ambiguous relationships, contact system administrator" msgstr "Ambiguous relationships, contact system administrator" msgid "Enter details now" msgstr "Enter details now" -msgid "Link to an existing" -msgstr "Link to an existing" +msgid "Link to an existing event" +msgstr "Link to an existing event" msgid "Scheduled date" msgstr "Scheduled date" diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js index 1444a98aea..18a20c37c4 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.js @@ -42,6 +42,7 @@ import { withAOCFieldBuilder, withDataEntryFields, } from '../../DataEntryDhis2Helpers'; +import type { RelatedStageRefPayload } from '../../WidgetRelatedStages'; const overrideMessagePropNames = { errorMessage: 'validationError', @@ -333,6 +334,7 @@ type FinalTeiDataEntryProps = { onUpdateFormFieldAsync: Function, onUpdateFormField: Function, firstStageMetaData?: ?{ stage: ProgramStage }, + relatedStageRef?: { current: ?RelatedStageRefPayload }, formFoundation: RenderFoundation, }; // final step before the generic dataEntry is inserted diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js index b831b9f197..420c663669 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js @@ -4,12 +4,13 @@ import type { Props } from './EnrollmentWithFirstStageDataEntry.types'; import { FirstStageDataEntry } from './EnrollmentWithFirstStageDataEntry.component'; import { useDataEntrySections } from './hooks'; import { Section } from '../../../../metaData'; +import { WidgetRelatedStages } from '../../../WidgetRelatedStages'; const getSectionId = sectionId => (sectionId === Section.MAIN_SECTION_ID ? `${Section.MAIN_SECTION_ID}-stage` : sectionId); export const EnrollmentWithFirstStageDataEntry = (props: Props) => { - const { firstStageMetaData, ...passOnProps } = props; + const { firstStageMetaData, relatedStageRef, relatedStageActionsOptions, ...passOnProps } = props; const { stage: { stageForm: firstStageFormFoundation, name: stageName }, } = firstStageMetaData; @@ -19,10 +20,18 @@ export const EnrollmentWithFirstStageDataEntry = (props: Props) => { const dataEntrySections = useDataEntrySections(stageName, beforeSectionId); return ( - + <> + + + ); }; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.types.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.types.js index 6c2a054867..135a01646b 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.types.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.types.js @@ -1,9 +1,20 @@ // @flow import type { ProgramStage, RenderFoundation } from '../../../../metaData'; +import type { RelatedStageRefPayload } from '../../../WidgetRelatedStages'; +import { relatedStageActions } from '../../../WidgetRelatedStages'; export type Props = { firstStageMetaData: { stage: ProgramStage, }, formFoundation: RenderFoundation, + programId: string, + relatedStageRef?: { current: ?RelatedStageRefPayload }, + relatedStageActionsOptions?: { + [key: $Keys]: { + hidden?: boolean, + disabled?: boolean, + disabledMessage?: string + }, + }, }; diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js index 92ff9612b7..e4f014b325 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js @@ -1,5 +1,5 @@ // @flow -import React from 'react'; +import React, { useRef } from 'react'; import type { ComponentType } from 'react'; import { useSelector } from 'react-redux'; import { EnrollmentRegistrationEntryComponent } from './EnrollmentRegistrationEntry.component'; @@ -10,6 +10,7 @@ import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges' import { useBuildEnrollmentPayload, } from './hooks/useBuildEnrollmentPayload'; +import type { RelatedStageRefPayload } from '../../WidgetRelatedStages'; export const EnrollmentRegistrationEntry: ComponentType = ({ selectedScopeId, @@ -22,6 +23,7 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ onCancel, ...passOnProps }) => { + const relatedStageRef = useRef(null); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); const { ready, @@ -57,13 +59,15 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ } const onSaveWithEnrollment = () => { - const teiWithEnrollment = buildTeiWithEnrollment(); - onSave(teiWithEnrollment); + const { teiWithEnrollment, formHasError, relatedStageLinkedEvent } = + buildTeiWithEnrollment(relatedStageRef); + !formHasError && onSave(teiWithEnrollment, relatedStageLinkedEvent); }; return ( , saveButtonText: (trackedEntityName: string) => string, firstStageMetaData?: ?{ stage: ?ProgramStage }, + relatedStageRef?: { current: ?RelatedStageRefPayload }, + relatedStageActionsOptions?: { + [key: $Keys]: { + hidden?: boolean, + disabled?: boolean, + disabledMessage?: string + }, + }, |}>; type ContainerProps = {| diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveAutoGenerateEvents.js similarity index 77% rename from src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js rename to src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveAutoGenerateEvents.js index 215b32f57b..6af15e19be 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveAutoGenerateEvents.js @@ -1,11 +1,13 @@ // @flow import moment from 'moment'; -import { dataElementTypes, ProgramStage } from '../../../../../metaData'; -import { convertClientToServer } from '../../../../../converters'; -import { convertCategoryOptionsToServer } from '../../../../../converters/clientToServer'; +import { dataElementTypes, ProgramStage } from '../../../../metaData'; +import { convertClientToServer } from '../../../../converters'; +import { convertCategoryOptionsToServer } from '../../../../converters/clientToServer'; +import type { RequestEvent, LinkedRequestEvent } from '../../../DataEntries'; +import { generateUID } from '../../../../utils/uid/generateUID'; -const ignoreAutoGenerateIfApplicable = (stage, firstStageDuringRegistrationEvent) => - !firstStageDuringRegistrationEvent || firstStageDuringRegistrationEvent.id !== stage.id; +const ignoreAutoGenerateIfApplicable = (stage, stageToSkip) => + !stageToSkip || stageToSkip.programStage !== stage.id; export const deriveAutoGenerateEvents = ({ stages, @@ -13,7 +15,8 @@ export const deriveAutoGenerateEvents = ({ occurredAt, programId, orgUnitId, - firstStageMetadata, + firstStageDuringRegistrationEvent, + relatedStageLinkedEvent, attributeCategoryOptions, serverMinorVersion, }: { @@ -22,7 +25,8 @@ export const deriveAutoGenerateEvents = ({ occurredAt: string, programId: string, orgUnitId: string, - firstStageMetadata: ?ProgramStage, + firstStageDuringRegistrationEvent: ?RequestEvent, + relatedStageLinkedEvent: ?LinkedRequestEvent, attributeCategoryOptions: { [categoryId: string]: string } | string, serverMinorVersion: number, }) => { @@ -33,7 +37,8 @@ export const deriveAutoGenerateEvents = ({ // $FlowFixMe[missing-annot] return [...stages.values()] .filter(({ autoGenerateEvent }) => autoGenerateEvent) - .filter(stage => ignoreAutoGenerateIfApplicable(stage, firstStageMetadata)) + .filter(stage => ignoreAutoGenerateIfApplicable(stage, firstStageDuringRegistrationEvent)) + .filter(stage => ignoreAutoGenerateIfApplicable(stage, relatedStageLinkedEvent)) .map( ({ id: programStage, @@ -71,6 +76,7 @@ export const deriveAutoGenerateEvents = ({ return { ...eventInfo, ...eventAttributeCategoryOptions, + event: generateUID(), programStage, program: programId, orgUnit: orgUnitId, diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveFirstStageDuringRegistrationEvent.js similarity index 85% rename from src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js rename to src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveFirstStageDuringRegistrationEvent.js index c0eac9af7b..38a5697e61 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveFirstStageDuringRegistrationEvent.js @@ -1,9 +1,10 @@ // @flow import { pipe } from 'capture-core-utils'; -import { dataElementTypes, ProgramStage } from '../../../../../metaData'; -import { convertFormToClient, convertClientToServer } from '../../../../../converters'; -import { convertCategoryOptionsToServer } from '../../../../../converters/clientToServer'; -import { convertStatusOut } from '../../../../DataEntries'; +import { generateUID } from '../../../../utils/uid/generateUID'; +import { dataElementTypes, ProgramStage } from '../../../../metaData'; +import { convertFormToClient, convertClientToServer } from '../../../../converters'; +import { convertCategoryOptionsToServer } from '../../../../converters/clientToServer'; +import { convertStatusOut } from '../../../DataEntries'; import { standardGeoJson } from './standardGeoJson'; const convertFn = pipe(convertFormToClient, convertClientToServer); @@ -37,6 +38,7 @@ export const deriveFirstStageDuringRegistrationEvent = ({ : {}; const event: any = { + event: generateUID(), status: convertStatusOut(stageComplete), geometry: standardGeoJson(stageGeometry), occurredAt: convertFn(stageOccurredAt, dataElementTypes.DATE), diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveRelatedStageEvent.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveRelatedStageEvent.js new file mode 100644 index 0000000000..08d779bd3a --- /dev/null +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/deriveRelatedStageEvent.js @@ -0,0 +1,72 @@ +// @flow +import { ProgramStage } from '../../../../metaData'; +import { getConvertedRelatedStageEvent } from '../../../DataEntries'; +import type { RequestEvent } from '../../../DataEntries'; +import type { RelatedStageRefPayload } from '../../../WidgetRelatedStages'; + +export const deriveRelatedStageEvent = ({ + serverRequestEvent, + relatedStageRef, + firstStageMetaData, + programId, + teiId, +}: { + serverRequestEvent: ?RequestEvent, + relatedStageRef?: { current: ?RelatedStageRefPayload}, + firstStageMetaData: ?{ stage: ?ProgramStage}, + programId: string, + teiId?: ?string, +}) => { + if (relatedStageRef?.current && relatedStageRef.current.eventHasLinkableStageRelationship()) { + const isValid = relatedStageRef.current.formIsValidOnSave(); + const currentProgramStageId = firstStageMetaData?.stage?.id; + if ( + !isValid || + !relatedStageRef.current?.getLinkedStageValues || + !currentProgramStageId || + !serverRequestEvent + ) { + return { + formHasError: true, + linkedEvent: null, + relationship: null, + linkMode: null, + }; + } + + const { selectedRelationshipType, relatedStageDataValues, linkMode } = + relatedStageRef.current.getLinkedStageValues(); + + if (!linkMode) { + return { + formHasError: false, + linkedEvent: null, + relationship: null, + linkMode: null, + }; + } + + const { linkedEvent, relationship } = getConvertedRelatedStageEvent({ + linkMode, + relatedStageDataValues, + serverRequestEvent, + relatedStageType: selectedRelationshipType, + programId, + currentProgramStageId, + teiId, + }); + + return { + formHasError: false, + linkedEvent, + relationship, + linkMode, + }; + } + return { + formHasError: false, + linkedEvent: null, + relationship: null, + linkMode: null, + }; +}; diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/index.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/index.js new file mode 100644 index 0000000000..b0c3744c1f --- /dev/null +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/index.js @@ -0,0 +1,5 @@ +// @flow +export { deriveFirstStageDuringRegistrationEvent } from './deriveFirstStageDuringRegistrationEvent'; +export { deriveRelatedStageEvent } from './deriveRelatedStageEvent'; +export { deriveAutoGenerateEvents } from './deriveAutoGenerateEvents'; +export { standardGeoJson } from './standardGeoJson'; diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/standardGeoJson.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/standardGeoJson.js similarity index 100% rename from src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/standardGeoJson.js rename to src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/helpers/standardGeoJson.js diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js index 65be698174..8d0a1c4b01 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js @@ -24,9 +24,12 @@ import { import { deriveAutoGenerateEvents, deriveFirstStageDuringRegistrationEvent, -} from '../../../Pages/New/RegistrationDataEntry/helpers'; + deriveRelatedStageEvent, +} from '../helpers'; import type { EnrollmentPayload } from '../EnrollmentRegistrationEntry.types'; import { geometryType, getPossibleTetFeatureTypeKey, buildGeometryProp } from '../../common/TEIAndEnrollment/geometry'; +import { relatedStageActions } from '../../../WidgetRelatedStages'; +import type { RelatedStageRefPayload } from '../../../WidgetRelatedStages'; type DataEntryReduxConverterProps = { programId: string; @@ -88,7 +91,14 @@ export const useBuildEnrollmentPayload = ({ const { firstStageMetaData } = useBuildFirstStageRegistration(programId); const { formFoundation } = useMergeFormFoundationsIfApplicable(scopeFormFoundation, firstStageMetaData); - const buildTeiWithEnrollment = (): EnrollmentPayload => { + const buildTeiWithEnrollment = (relatedStageRef?: {current: ?RelatedStageRefPayload}): { + teiWithEnrollment: EnrollmentPayload, + formHasError: boolean, + relatedStageLinkedEvent?: { + programStageId: string, + eventId: string, + }, + } => { if (!formFoundation) throw Error('form foundation object not found'); const firstStage = firstStageMetaData && firstStageMetaData.stage; const clientValues = getClientValuesForFormData(formValues, formFoundation); @@ -126,8 +136,17 @@ export const useBuildEnrollmentPayload = ({ serverMinorVersion: minor, }); + const { formHasError, linkedEvent: relatedStageLinkedEvent, relationship, linkMode } = deriveRelatedStageEvent({ + serverRequestEvent: firstStageDuringRegistrationEvent, + relatedStageRef, + firstStageMetaData, + programId, + teiId, + }); + const autoGenerateEvents = deriveAutoGenerateEvents({ - firstStageMetadata: firstStage, + firstStageDuringRegistrationEvent, + relatedStageLinkedEvent, stages, enrolledAt, occurredAt, @@ -137,9 +156,8 @@ export const useBuildEnrollmentPayload = ({ serverMinorVersion: minor, }); - const allEventsToBeCreated = firstStageDuringRegistrationEvent - ? [firstStageDuringRegistrationEvent, ...autoGenerateEvents] - : autoGenerateEvents; + const allEventsToBeCreated = [firstStageDuringRegistrationEvent, relatedStageLinkedEvent, ...autoGenerateEvents] + .filter(Boolean); const attributes = deriveAttributesFromFormValues(formServerValues); @@ -155,17 +173,26 @@ export const useBuildEnrollmentPayload = ({ }; const tetFeatureTypeKey = getPossibleTetFeatureTypeKey(formServerValues); - const tetGeometry = tetFeatureTypeKey ? - buildGeometryProp(tetFeatureTypeKey, formValues) - : undefined; + const tetGeometry = tetFeatureTypeKey ? buildGeometryProp(tetFeatureTypeKey, formValues) : undefined; return { - trackedEntity: teiId || generateUID(), - orgUnit: orgUnitId, - trackedEntityType: trackedEntityTypeId, - attributes, - geometry: tetGeometry, - enrollments: [enrollment], + teiWithEnrollment: { + trackedEntity: teiId || generateUID(), + orgUnit: orgUnitId, + trackedEntityType: trackedEntityTypeId, + attributes, + geometry: tetGeometry, + enrollments: [enrollment], + relationships: relationship ? [relationship] : undefined, + }, + formHasError, + relatedStageLinkedEvent: + relatedStageLinkedEvent && linkMode === relatedStageActions.ENTER_DATA + ? { + programStageId: relatedStageLinkedEvent.programStage, + eventId: relatedStageLinkedEvent.event, + } + : undefined, }; }; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addRelationshipForNewSingleEvent.epics.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addRelationshipForNewSingleEvent.epics.js index 73107c5710..65fd3ae6ac 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addRelationshipForNewSingleEvent.epics.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/addRelationshipForNewSingleEvent.epics.js @@ -29,7 +29,7 @@ import { } from '../../../../../DataEntry/actions/dataEntry.actions'; import { getDataEntryKey } from '../../../../../DataEntry/common/getDataEntryKey'; import { convertClientRelationshipToServer } from '../../../../../../relationships/convertClientToServer'; -import { getRelationshipNewTei } from '../../../../../Pages/NewRelationship/RegisterTei'; +import { getRelationshipNewTeiName } from '../../../../../Pages/NewRelationship/RegisterTei'; const dataEntryId = 'singleEvent'; const itemId = 'newEvent'; @@ -47,13 +47,8 @@ export const addRelationshipForNewSingleEventEpic = (action$: InputObservable, s const state = store.value; const existingRelationships = state.dataEntriesRelationships[dataEntryKey] || []; const payload = action.payload; - const entity = payload.entity; + const toEntity = payload.entity; - const toEntity = entity.id ? entity : getRelationshipNewTei(entity.dataEntryId, entity.itemId, state); - const toEntityIsNew = !entity.id; - const newToEntity = toEntityIsNew ? { - dataEntryId: entity.dataEntryId, - } : null; const newRelationship = { clientId: uuid(), @@ -64,6 +59,7 @@ export const addRelationshipForNewSingleEventEpic = (action$: InputObservable, s }, to: { ...toEntity, + name: toEntity.name || getRelationshipNewTeiName(toEntity.dataEntryId, toEntity.itemId, state), type: payload.entityType, }, relationshipType: { ...payload.relationshipType }, @@ -87,7 +83,7 @@ export const addRelationshipForNewSingleEventEpic = (action$: InputObservable, s return batchActions([ recentlyAddedRelationship(newRelationship.clientId), - addRelationship(dataEntryId, itemId, newRelationship, newToEntity), + addRelationship(dataEntryId, itemId, newRelationship, toEntity), ], newEventNewRelationshipBatchActionTypes.ADD_RELATIONSHIP_BATCH); })); diff --git a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/DuplicateCheckOnSave/types/duplicateCheckOnSave.types.js b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/DuplicateCheckOnSave/types/duplicateCheckOnSave.types.js index f55dc39c0e..da1e6bcc39 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/DuplicateCheckOnSave/types/duplicateCheckOnSave.types.js +++ b/src/core_modules/capture-core/components/DataEntries/common/TEIAndEnrollment/DuplicateCheckOnSave/types/duplicateCheckOnSave.types.js @@ -4,4 +4,8 @@ import type { TeiPayload } from '../../../../../Pages/common/TEIRelationshipsWid export type SaveForDuplicateCheck = ( teiWithEnrollment: EnrollmentPayload | TeiPayload, + relatedStageLinkedEvent?: { + programStageId: string, + eventId: string, + }, ) => void; diff --git a/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/withAskToCompleteEnrollment.js b/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/withAskToCompleteEnrollment.js index b1d2cee393..c19649a0d7 100644 --- a/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/withAskToCompleteEnrollment.js +++ b/src/core_modules/capture-core/components/DataEntries/common/trackerEvent/withAskToCompleteEnrollment/withAskToCompleteEnrollment.js @@ -6,8 +6,8 @@ import { CompleteModal } from './CompleteModal'; import { statusTypes as eventStatuses } from '../../../../../events/statusTypes'; import { type RenderFoundation } from '../../../../../metaData'; import { addEventSaveTypes } from '../../../../WidgetEnrollmentEventNew/DataEntry/addEventSaveTypes'; -import { actions as LinkModes } from '../../../../WidgetRelatedStages/constants'; -import type { RelatedStageRefPayload } from '../../../../WidgetEnrollmentEventNew/Validated/validated.types'; +import { relatedStageActions } from '../../../../WidgetRelatedStages'; +import type { RelatedStageRefPayload } from '../../../../WidgetRelatedStages'; type Props = { onSave: (eventId: string, dataEntryId: string, formFoundation: RenderFoundation, saveType?: ?string) => void, @@ -15,7 +15,7 @@ type Props = { isCompleted?: boolean, eventId?: ?string, formFoundation: RenderFoundation, - relatedStageRef: { current?: ?RelatedStageRefPayload }, + relatedStageRef?: { current: ?RelatedStageRefPayload }, onSaveAndCompleteEnrollment: ( eventId: string, dataEntryId: string, @@ -60,7 +60,11 @@ const getAskToCompleteEnrollment = (InnerComponent: ComponentType) => (prop ) => { const { linkMode } = relatedStageRef?.current?.getLinkedStageValues() ?? {}; eventDataToSave.current = { itemId, dataEntryId, formFoundation, saveType }; - if (askCompleteEnrollmentOnEventComplete && (isCompleted || saveType === addEventSaveTypes.COMPLETE) && linkMode !== LinkModes.ENTER_DATA) { + if ( + askCompleteEnrollmentOnEventComplete && + (isCompleted || saveType === addEventSaveTypes.COMPLETE) && + linkMode !== relatedStageActions.ENTER_DATA + ) { setOpenCompleteModal(true); } else { onSave(itemId, dataEntryId, formFoundation, saveType); diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.js b/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.js similarity index 84% rename from src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.js rename to src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.js index 07832fe83f..ad19d0ec9e 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.js +++ b/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.js @@ -1,10 +1,9 @@ // @flow import log from 'loglevel'; import { generateUID } from '../../../../utils/uid/generateUID'; -import { actions as RelatedStageModes } from '../../../WidgetRelatedStages/constants'; -import type { ConvertedRelatedStageEventProps } from './getConvertedRelatedStageEvent.types'; +import { relatedStageActions } from '../../../WidgetRelatedStages'; +import type { LinkedRequestEvent, ConvertedRelatedStageEventProps } from './getConvertedRelatedStageEvent.types'; import { errorCreator, pipe } from '../../../../../capture-core-utils'; -import { type LinkedRequestEvent } from '../validated.types'; import { convertClientToServer, convertFormToClient } from '../../../../converters'; import { dataElementTypes } from '../../../../metaData'; @@ -18,7 +17,7 @@ const getEventDetailsByLinkMode = ({ programId, teiId, enrollmentId, - clientRequestEvent, + serverRequestEvent, }): { linkedEvent: ?LinkedRequestEvent, linkedEventId: ?string, @@ -36,7 +35,7 @@ const getEventDetailsByLinkMode = ({ status: 'SCHEDULE', }; - if (linkMode === RelatedStageModes.SCHEDULE_IN_ORG) { + if (linkMode === relatedStageActions.SCHEDULE_IN_ORG) { const { scheduledAt: linkedEventScheduledAt, orgUnit: linkedEventOrgUnit } = relatedStageDataValues; if (!linkedEventScheduledAt || !linkedEventOrgUnit) { // Business logic dictates that these values will not be null here @@ -57,7 +56,7 @@ const getEventDetailsByLinkMode = ({ }); } - if (linkMode === RelatedStageModes.ENTER_DATA) { + if (linkMode === relatedStageActions.ENTER_DATA) { const { orgUnit: linkedEventOrgUnit } = relatedStageDataValues; if (!linkedEventOrgUnit) { throw new Error( @@ -69,14 +68,14 @@ const getEventDetailsByLinkMode = ({ return ({ linkedEvent: { ...baseEventDetails, - scheduledAt: convertFn(clientRequestEvent.scheduledAt, dataElementTypes.DATE), + scheduledAt: serverRequestEvent.scheduledAt, orgUnit: convertFn(linkedEventOrgUnit, dataElementTypes.ORGANISATION_UNIT), }, linkedEventId: baseEventDetails.event, }); } - if (linkMode === RelatedStageModes.LINK_EXISTING_RESPONSE) { + if (linkMode === relatedStageActions.LINK_EXISTING_RESPONSE) { const { linkedEventId } = relatedStageDataValues; return { linkedEvent: null, @@ -96,7 +95,7 @@ export const getConvertedRelatedStageEvent = ({ programId, teiId, currentProgramStageId, - clientRequestEvent, + serverRequestEvent, enrollmentId, relatedStageType, }: ConvertedRelatedStageEventProps) => { @@ -110,19 +109,19 @@ export const getConvertedRelatedStageEvent = ({ programId, teiId, enrollmentId, - clientRequestEvent, + serverRequestEvent, }); const relationship = linkedEventId && { relationshipType: relatedStageType.id, from: { event: { - event: requestEventIsFromConstraint ? clientRequestEvent.event : linkedEventId, + event: requestEventIsFromConstraint ? serverRequestEvent.event : linkedEventId, }, }, to: { event: { - event: requestEventIsFromConstraint ? linkedEventId : clientRequestEvent.event, + event: requestEventIsFromConstraint ? linkedEventId : serverRequestEvent.event, }, }, }; diff --git a/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.types.js b/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.types.js new file mode 100644 index 0000000000..360a0ab676 --- /dev/null +++ b/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/getConvertedRelatedStageEvent.types.js @@ -0,0 +1,40 @@ +// @flow +import type { RelatedStageDataValueStates, RelatedStageRelationshipType } from '../../../WidgetRelatedStages'; +import { relatedStageActions } from '../../../WidgetRelatedStages'; + +type CommonEventDetails = { + event: string, + program: string, + programStage: string, + orgUnit: string, + trackedEntity?: ?string, + enrollment?: string, + scheduledAt: string, + dataValues: Array<{ dataElement: string, value: any }>, + status?: string, +} + +export type RequestEvent = { + ...CommonEventDetails, + occurredAt: string, + notes?: Array<{ value: string }>, + completedAt?: string, +} + +export type LinkedRequestEvent = { + ...CommonEventDetails, + occurredAt?: string, + completedAt?: string, +} + +export type ConvertedRelatedStageEventProps = {| + linkMode: $Keys, + relatedStageDataValues: RelatedStageDataValueStates, + programId: string, + teiId?: ?string, + currentProgramStageId: string, + enrollmentId?: string, + relatedStageType: RelatedStageRelationshipType, + serverRequestEvent: RequestEvent, +|} + diff --git a/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/index.js b/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/index.js new file mode 100644 index 0000000000..b6ecc885ea --- /dev/null +++ b/src/core_modules/capture-core/components/DataEntries/converters/getConvertedRelatedStageEvent/index.js @@ -0,0 +1,4 @@ +// @flow + +export { getConvertedRelatedStageEvent } from './getConvertedRelatedStageEvent'; +export type { RequestEvent, LinkedRequestEvent } from './getConvertedRelatedStageEvent.types'; diff --git a/src/core_modules/capture-core/components/DataEntries/converters/index.js b/src/core_modules/capture-core/components/DataEntries/converters/index.js index 29835b7f31..5bef2944fb 100644 --- a/src/core_modules/capture-core/components/DataEntries/converters/index.js +++ b/src/core_modules/capture-core/components/DataEntries/converters/index.js @@ -7,3 +7,6 @@ export { convertStatusOut, getConvertGeometryIn, } from './converters'; + +export { getConvertedRelatedStageEvent } from './getConvertedRelatedStageEvent'; +export type { RequestEvent, LinkedRequestEvent } from './getConvertedRelatedStageEvent'; diff --git a/src/core_modules/capture-core/components/DataEntries/index.js b/src/core_modules/capture-core/components/DataEntries/index.js index 6d73c67fbd..ff85f696cc 100644 --- a/src/core_modules/capture-core/components/DataEntries/index.js +++ b/src/core_modules/capture-core/components/DataEntries/index.js @@ -19,10 +19,12 @@ export { convertStatusIn, convertStatusOut, getConvertGeometryIn, -} from './converters/converters'; + getConvertedRelatedStageEvent, +} from './converters'; export { EnrollmentRegistrationEntry } from './EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container'; export { TeiRegistrationEntry } from './TeiRegistrationEntry/TeiRegistrationEntry.container'; export { SingleEventRegistrationEntry } from './SingleEventRegistrationEntry/SingleEventRegistrationEntry.container'; export type { SaveForDuplicateCheck as SaveForEnrollmentAndTeiRegistration } from './common/TEIAndEnrollment/DuplicateCheckOnSave'; export type { ExistingUniqueValueDialogActionsComponent } from './withErrorMessagePostProcessor'; export { withAskToCompleteEnrollment } from './common/trackerEvent'; +export type { RequestEvent, LinkedRequestEvent } from './converters'; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.epics.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.epics.js index ea90f8b0fc..7fd2050e30 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.epics.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPage.epics.js @@ -11,7 +11,7 @@ import { rollbackEnrollmentEvents, saveFailed, } from '../common/EnrollmentOverviewDomain/enrollment.actions'; -import { actions as RelatedStageActions } from '../../WidgetRelatedStages/constants'; +import { relatedStageActions } from '../../WidgetRelatedStages'; import { buildUrlQueryString } from '../../../utils/routing'; const shouldNavigateWithRelatedStage = ({ @@ -21,7 +21,7 @@ const shouldNavigateWithRelatedStage = ({ history, }) => { if (linkMode && linkedEventId) { - if (linkMode === RelatedStageActions.ENTER_DATA) { + if (linkMode === relatedStageActions.ENTER_DATA) { const navigate = () => history.push(`/enrollmentEventEdit?${buildUrlQueryString({ eventId: linkedEventId, orgUnitId: linkedOrgUnitId, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js index 01ee6a0347..a3500cd1f3 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/EnrollmentAddEventPageDefault/EnrollmentAddEventPageDefault.container.js @@ -11,7 +11,7 @@ import { buildUrlQueryString, useLocationQuery } from '../../../../utils/routing import { useProgramInfo } from '../../../../hooks/useProgramInfo'; import { EnrollmentAddEventTopBar, useEnrollmentAddEventTopBar } from '../TopBar'; import { deleteEnrollment, fetchEnrollments } from '../../Enrollment/EnrollmentPage.actions'; -import { actions as RelatedStageModes } from '../../../WidgetRelatedStages/constants'; +import { relatedStageActions } from '../../../WidgetRelatedStages'; import { useWidgetDataFromStore } from '../hooks'; import { useHideWidgetByRuleLocations } from '../../Enrollment/EnrollmentPageDefault/hooks'; @@ -70,7 +70,7 @@ export const EnrollmentAddEventPageDefault = ({ const handleSave = useCallback( ({ enrollments, events, linkMode }) => { - if (linkMode && linkMode === RelatedStageModes.ENTER_DATA) return; + if (linkMode && linkMode === relatedStageActions.ENTER_DATA) return; const nowClient = fromClientDate(new Date()); const nowServer = new Date(nowClient.getServerZonedISOString()); diff --git a/src/core_modules/capture-core/components/Pages/New/EnrollmentRegistrationEntryWrapper.component.js b/src/core_modules/capture-core/components/Pages/New/EnrollmentRegistrationEntryWrapper.component.js index e701d15e88..8c65e761b5 100644 --- a/src/core_modules/capture-core/components/Pages/New/EnrollmentRegistrationEntryWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/New/EnrollmentRegistrationEntryWrapper.component.js @@ -4,6 +4,7 @@ import { useDispatch } from 'react-redux'; import { cleanUpUid } from './NewPage.actions'; import { EnrollmentRegistrationEntry } from '../../DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container'; import type { OwnProps } from '../../DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types'; +import { relatedStageActions } from '../../WidgetRelatedStages'; export const EnrollmentRegistrationEntryWrapper: ComponentType = (props) => { const dispatch = useDispatch(); @@ -11,5 +12,9 @@ export const EnrollmentRegistrationEntryWrapper: ComponentType = (prop dispatch(cleanUpUid()); }, [dispatch]); - return ; + const relatedStageActionsOptions = { + [relatedStageActions.LINK_EXISTING_RESPONSE]: { hidden: true }, + }; + + return ; }; diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.actions.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.actions.js index 6beb555bc6..55731792cd 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.actions.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.actions.js @@ -46,24 +46,32 @@ export const saveNewTrackedEntityInstance = (candidateForRegistration: any) => ); // with enrollment -export const startSavingNewTrackedEntityInstanceWithEnrollment = (enrollmentPayload: EnrollmentPayload, uid: string) => +export const startSavingNewTrackedEntityInstanceWithEnrollment = ( + enrollmentPayload: EnrollmentPayload, + uid: string, + relatedStageLinkedEvent?: { + programStageId: string, + eventId: string, + }, +) => actionCreator(registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE_START)({ enrollmentPayload, uid, + relatedStageLinkedEvent, }); export const saveNewTrackedEntityInstanceWithEnrollment = ({ candidateForRegistration, - redirectTo, + pageToRedirectTo, uid, stageId, - eventIndex, + eventId, }: { candidateForRegistration: any, - redirectTo: string, + pageToRedirectTo: string, uid: string, stageId?: string, - eventIndex: number, + eventId?: string, }) => actionCreator(registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE)( { ...candidateForRegistration }, @@ -76,7 +84,7 @@ export const saveNewTrackedEntityInstanceWithEnrollment = ({ }, commit: { type: registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE_COMPLETED, - meta: { redirectTo, stageId, uid, eventIndex }, + meta: { pageToRedirectTo, stageId, uid, eventId }, }, rollback: { type: registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE_FAILED, diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js index 8aeda04f1f..2527f67c65 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js @@ -30,9 +30,15 @@ export const RegistrationDataEntry: ComponentType = ({ [dispatch]); const dispatchOnSaveWithEnrollment = useCallback( - (enrollmentPayload) => { + (enrollmentPayload, relatedStageLinkedEvent) => { const uid = uuid(); - dispatch(startSavingNewTrackedEntityInstanceWithEnrollment(enrollmentPayload, uid)); + dispatch( + startSavingNewTrackedEntityInstanceWithEnrollment( + enrollmentPayload, + uid, + relatedStageLinkedEvent, + ), + ); }, [dispatch]); diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.epics.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.epics.js index 4ee21bd46c..390e95aabc 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.epics.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.epics.js @@ -14,7 +14,7 @@ import { } from '../../../../actions/navigateToEnrollmentOverview/navigateToEnrollmentOverview.actions'; import { buildUrlQueryString } from '../../../../utils/routing'; import { - getStageWithOpenAfterEnrollment, + getPageToRedirectTo, PAGES, } from './helpers'; import { cleanUpUid } from '../NewPage.actions'; @@ -54,16 +54,14 @@ export const startSavingNewTrackedEntityInstanceWithEnrollmentEpic: Epic = ( ofType(registrationFormActionTypes.NEW_TRACKED_ENTITY_INSTANCE_WITH_ENROLLMENT_SAVE_START), map((action) => { const { currentSelections: { programId } } = store.value; - const { enrollmentPayload, uid } = action.payload; + const { enrollmentPayload, uid, relatedStageLinkedEvent } = action.payload; const { stages, useFirstStageDuringRegistration } = getTrackerProgramThrowIfNotFound(programId); - const { stageWithOpenAfterEnrollment, redirectTo } = getStageWithOpenAfterEnrollment( + const { stageId, eventId, pageToRedirectTo } = getPageToRedirectTo({ stages, + events: enrollmentPayload.enrollments[0]?.events, useFirstStageDuringRegistration, - ); - - const eventIndex = enrollmentPayload.enrollments[0]?.events.findIndex( - eventsToBeCreated => eventsToBeCreated.programStage === stageWithOpenAfterEnrollment?.id, - ); + relatedStageLinkedEvent, + }); return saveNewTrackedEntityInstanceWithEnrollment({ candidateForRegistration: { @@ -71,9 +69,9 @@ export const startSavingNewTrackedEntityInstanceWithEnrollmentEpic: Epic = ( enrollmentPayload, ], }, - redirectTo, - eventIndex, - stageId: stageWithOpenAfterEnrollment?.id, + pageToRedirectTo, + eventId, + stageId, uid, }); }), @@ -91,7 +89,7 @@ export const completeSavingNewTrackedEntityInstanceWithEnrollmentEpic = ( payload: { bundleReport: { typeReportMap }, }, - meta: { uid, redirectTo, stageId, eventIndex }, + meta: { uid, pageToRedirectTo, stageId, eventId }, } = action; const { currentSelections: { orgUnitId, programId }, @@ -100,15 +98,14 @@ export const completeSavingNewTrackedEntityInstanceWithEnrollmentEpic = ( const { uid: stateUid } = newPage || {}; const teiId = typeReportMap.TRACKED_ENTITY.objectReports[0].uid; const enrollmentId = typeReportMap.ENROLLMENT.objectReports[0].uid; - const eventId = typeReportMap.EVENT.objectReports?.[eventIndex]?.uid; if (stateUid !== uid) { return EMPTY; } - if (redirectTo === PAGES.enrollmentEventNew) { + if (pageToRedirectTo === PAGES.enrollmentEventNew) { history.push( - `/${redirectTo}?${buildUrlQueryString({ + `/${pageToRedirectTo}?${buildUrlQueryString({ programId, orgUnitId, teiId, @@ -119,9 +116,9 @@ export const completeSavingNewTrackedEntityInstanceWithEnrollmentEpic = ( return EMPTY; } - if (redirectTo === PAGES.enrollmentEventEdit) { + if (pageToRedirectTo === PAGES.enrollmentEventEdit) { history.push( - `/${redirectTo}?${buildUrlQueryString({ + `/${pageToRedirectTo}?${buildUrlQueryString({ eventId, orgUnitId, initMode: dataEntryKeys.EDIT, diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/getPageToRedirectTo.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/getPageToRedirectTo.js new file mode 100644 index 0000000000..1b99b1c8fa --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/getPageToRedirectTo.js @@ -0,0 +1,74 @@ +// @flow +import { ProgramStage } from '../../../../../metaData'; + +export const PAGES = { + enrollmentEventNew: 'enrollmentEventNew', + enrollmentEventEdit: 'enrollmentEventEdit', + enrollmentDashboard: 'enrollmentDashboard', +}; +/** + * An event can be + * - created during first stage registration + * - created and linked during first stage registration + * - autogenerated + * When the event will be created redirect to enrollmentEventEdit + * When the event will not be created redirect to enrollmentEventNew + */ +export const getPageToRedirectTo = ({ + stages, + events, + useFirstStageDuringRegistration, + relatedStageLinkedEvent, +}: { + stages: Map, + events: any, + useFirstStageDuringRegistration: boolean, + relatedStageLinkedEvent?: { + programStageId: string, + eventId: string, + }, +}): { stageId?: string, eventId?: string, pageToRedirectTo: string } => { + const stagesArray = [...stages.values()]; + const [firstStageWithOpenAfterEnrollment] = stagesArray.filter(({ openAfterEnrollment }) => openAfterEnrollment); + + // event will be created and linked during first stage registration + if (relatedStageLinkedEvent?.eventId) { + const { programStageId, eventId } = relatedStageLinkedEvent; + return { + stageId: programStageId, + eventId, + pageToRedirectTo: PAGES.enrollmentEventEdit, + }; + } + + if (firstStageWithOpenAfterEnrollment) { + // event will be created during first stage registration + if (useFirstStageDuringRegistration && stagesArray[0].id === firstStageWithOpenAfterEnrollment.id) { + const eventId = events.find(e => e.programStage === firstStageWithOpenAfterEnrollment.id).event; + + return { + programStageIdToRedirectTo: firstStageWithOpenAfterEnrollment.id, + eventId, + pageToRedirectTo: PAGES.enrollmentEventEdit, + }; + } + + // event will be autogenerated + if (stagesArray.find(stage => stage.autoGenerateEvent && stage.id === firstStageWithOpenAfterEnrollment.id)) { + const eventId = events.find(e => e.programStage === firstStageWithOpenAfterEnrollment.id).event; + + return { + programStageIdToRedirectTo: firstStageWithOpenAfterEnrollment.id, + eventId, + pageToRedirectTo: PAGES.enrollmentEventEdit, + }; + } + + return { + programStageIdToRedirectTo: firstStageWithOpenAfterEnrollment.id, + pageToRedirectTo: PAGES.enrollmentEventNew, + }; + } + + return { pageToRedirectTo: PAGES.enrollmentDashboard }; +}; diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/getStageWithOpenAfterEnrollment.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/getStageWithOpenAfterEnrollment.js deleted file mode 100644 index 3688d29b4c..0000000000 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/getStageWithOpenAfterEnrollment.js +++ /dev/null @@ -1,44 +0,0 @@ -// @flow -import { ProgramStage } from '../../../../../metaData'; - -export const PAGES = { - enrollmentEventNew: 'enrollmentEventNew', - enrollmentEventEdit: 'enrollmentEventEdit', - enrollmentDashboard: 'enrollmentDashboard', -}; - -// an event can be created either during first stage registration or autogenerated -// when the event will be created redirect to enrollmentEventEdit -// when the event will not be created redirect to enrollmentEventNew -export const getStageWithOpenAfterEnrollment = ( - stages: Map, - useFirstStageDuringRegistration: boolean, -) => { - const stagesArray = [...stages.values()]; - const [firstStageWithOpenAfterEnrollment] = stagesArray.filter(({ openAfterEnrollment }) => openAfterEnrollment); - - const redirectTo = (() => { - if (firstStageWithOpenAfterEnrollment) { - // event will be created during first stage registration - if ( - useFirstStageDuringRegistration - && stagesArray[0].id === firstStageWithOpenAfterEnrollment.id - ) { - return PAGES.enrollmentEventEdit; - } - // event will be autogenerated - if ( - stagesArray.find(stage => stage.autoGenerateEvent && stage.id === firstStageWithOpenAfterEnrollment.id) - ) { - return PAGES.enrollmentEventEdit; - } - return PAGES.enrollmentEventNew; - } - return PAGES.enrollmentDashboard; - })(); - - return { - stageWithOpenAfterEnrollment: firstStageWithOpenAfterEnrollment, - redirectTo, - }; -}; diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/index.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/index.js index fe7c884a9e..a82f1974d9 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/index.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/index.js @@ -1,5 +1,2 @@ // @flow -export { deriveFirstStageDuringRegistrationEvent } from './deriveFirstStageDuringRegistrationEvent'; -export { deriveAutoGenerateEvents } from './deriveAutoGenerateEvents'; -export { getStageWithOpenAfterEnrollment, PAGES } from './getStageWithOpenAfterEnrollment'; -export { standardGeoJson } from './standardGeoJson'; +export { getPageToRedirectTo, PAGES } from './getPageToRedirectTo'; diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index 252a34e61a..710ff035c1 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -6,6 +6,7 @@ import { DATA_ENTRY_ID } from '../../registerTei.const'; import enrollmentClasses from './enrollment.module.css'; import { EnrollmentRegistrationEntry } from '../../../../../DataEntries'; import type { Props } from './dataEntryEnrollment.types'; +import { relatedStageActions } from '../../../../../WidgetRelatedStages'; const NewEnrollmentRelationshipPlain = ({ @@ -20,6 +21,13 @@ const NewEnrollmentRelationshipPlain = ExistingUniqueValueDialogActions, }: Props) => { const fieldOptions = { theme, fieldLabelMediaBasedClass: enrollmentClasses.fieldLabelMediaBased }; + const relatedStageActionsOptions = { + [relatedStageActions.ENTER_DATA]: { + disabled: true, + disabledMessage: i18n.t('Enter details now is not available when creating a relationship'), + }, + [relatedStageActions.LINK_EXISTING_RESPONSE]: { hidden: true }, + }; return ( ); }; diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js index 2c159e7aba..fcbdf474a4 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js @@ -10,6 +10,9 @@ import { DataEntryWidgetOutput } from '../../../DataEntryWidgetOutput/DataEntryW import { ResultsPageSizeContext } from '../../shared-contexts'; import type { Props } from './RegisterTei.types'; import { withErrorMessageHandler } from '../../../../HOC'; +import type { EnrollmentPayload } from + '../../../DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types'; +import type { TeiPayload } from '../../common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance'; const getStyles = () => ({ container: { @@ -95,8 +98,8 @@ const RegisterTeiPlain = ({ ), [onLink]); - const handleSave = useCallback(() => { - onSave(itemId, dataEntryId); + const handleSave = useCallback((payload: EnrollmentPayload | TeiPayload) => { + onSave(itemId, dataEntryId, payload); }, [onSave, itemId, dataEntryId]); return ( diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js index 759abe2e6f..2cce109427 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js @@ -1,4 +1,9 @@ // @flow +import type { EnrollmentPayload } from + '../../../DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types'; +import type { TeiPayload } from + '../../common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types'; + type PropsFromRedux = {| dataEntryId: string, itemId: string, @@ -12,7 +17,7 @@ export type OwnProps = {| onLink: (teiId: string, values: Object) => void, onCancel: () => void, onGetUnsavedAttributeValues?: ?Function, - onSave: (itemId: string, dataEntryId: string) => void, + onSave: (itemId: string, dataEntryId: string, payload: EnrollmentPayload | TeiPayload) => void, |}; export type Props = {|...PropsFromRedux, ...OwnProps, ...CssClasses |} diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/exposedHelpers/getRelationshipNewTei.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/exposedHelpers/getRelationshipNewTei.js index 7eeef73978..e3e29b02f4 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/exposedHelpers/getRelationshipNewTei.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/exposedHelpers/getRelationshipNewTei.js @@ -1,16 +1,11 @@ // @flow -import uuid from 'd2-utilizr/src/uuid'; -import moment from 'moment'; -import { getFormattedStringFromMomentUsingEuropeanGlyphs } from 'capture-core-utils/date'; -import { capitalizeFirstLetter } from 'capture-core-utils/string/capitalizeFirstLetter'; import { getTrackerProgramThrowIfNotFound, getTrackedEntityTypeThrowIfNotFound, type RenderFoundation, } from '../../../../../metaData'; -import { convertFormToClient, convertClientToServer } from '../../../../../converters'; +import { convertFormToClient } from '../../../../../converters'; import { getDisplayName } from '../../../../../trackedEntityInstances/getDisplayName'; -import { convertDataEntryValuesToClientValues } from '../../../../DataEntry/common/convertDataEntryValuesToClientValues'; import { getDataEntryKey } from '../../../../DataEntry/common/getDataEntryKey'; function getTrackerProgramMetadata(programId: string) { @@ -41,99 +36,15 @@ function getClientValuesForFormData(formValues: Object, formFoundation: RenderFo return clientValues; } -function getServerValuesForMainValues( - values: Object, - meta: Object, - formFoundation: RenderFoundation, -) { - const clientValues = convertDataEntryValuesToClientValues( - values, - meta, - formFoundation, - ) || {}; - - // potientally run this through a server to client converter for enrollment, the same way as for event - const serverValues = Object - .keys(clientValues) - .reduce((acc, key) => { - const value = clientValues[key]; - const type = meta[key].type; - acc[key] = convertClientToServer(value, type); - return acc; - }, {}); - - return serverValues; -} - -function getPossibleTetFeatureTypeKey(serverValues: Object) { - return Object - .keys(serverValues) - .find(key => key.startsWith('FEATURETYPE_')); -} - -function buildGeometryProp(key: string, serverValues: Object) { - if (!serverValues[key]) { - return undefined; - } - const type = capitalizeFirstLetter(key.replace('FEATURETYPE_', '').toLocaleLowerCase()); - return { - type, - coordinates: serverValues[key], - }; -} - -export function getRelationshipNewTei(dataEntryId: string, itemId: string, state: ReduxState) { +export function getRelationshipNewTeiName(dataEntryId: string, itemId: string, state: ReduxState) { const dataEntryKey = getDataEntryKey(dataEntryId, itemId); const formValues = state.formsValues[dataEntryKey]; - const { programId, orgUnit } = state.newRelationshipRegisterTei; + const { programId } = state.newRelationshipRegisterTei; const tetId = state.newRelationship.selectedRelationshipType.to.trackedEntityTypeId; const { attributes: metaDataAttributes, form: formFoundation, tetName } = getMetadata(programId, tetId); const clientValuesForFormData = getClientValuesForFormData(formValues, formFoundation); const displayName = getDisplayName(clientValuesForFormData, metaDataAttributes, tetName); - const serverValuesForFormValues = formFoundation.convertValues(clientValuesForFormData, convertClientToServer); - const serverValuesForMainValues = getServerValuesForMainValues( - state.dataEntriesFieldsValue[dataEntryKey], - state.dataEntriesFieldsMeta[dataEntryKey], - formFoundation, - ); - - // $FlowFixMe - const attributes = Object.keys(serverValuesForFormValues) - .map(key => ({ - attribute: key, - value: serverValuesForFormValues[key], - })); - - const enrollment = programId ? { - program: programId, - status: 'ACTIVE', - orgUnit: orgUnit.id, - occurredAt: getFormattedStringFromMomentUsingEuropeanGlyphs(moment()), - attributes, - ...serverValuesForMainValues, - } : null; - - const tetFeatureTypeKey = getPossibleTetFeatureTypeKey(serverValuesForFormValues); - let geometry; - if (tetFeatureTypeKey) { - geometry = buildGeometryProp(tetFeatureTypeKey, serverValuesForFormValues); - delete serverValuesForFormValues[tetFeatureTypeKey]; - } - - const teiPayload = { - // $FlowFixMe - attributes: !enrollment ? attributes : undefined, - orgUnit: orgUnit.id, - trackedEntityType: tetId, - geometry, - enrollments: enrollment ? [enrollment] : [], - }; - - return { - data: teiPayload, - name: displayName, - id: uuid(), - }; + return displayName; } diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/index.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/index.js index 7a77e9ec0d..b615fc1939 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/index.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/index.js @@ -10,4 +10,4 @@ export { export { loadSearchGroupDuplicatesForReviewEpic, } from '../../../PossibleDuplicatesDialog/possibleDuplicatesDialog.epics'; -export { getRelationshipNewTei } from './exposedHelpers/getRelationshipNewTei'; +export { getRelationshipNewTeiName } from './exposedHelpers/getRelationshipNewTei'; diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js index 863327b37f..87869ce23a 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js @@ -13,7 +13,10 @@ import type { TrackedEntityType } from '../../../../metaData'; import { findModes } from '../findModes'; import { getDisplayName } from '../../../../trackedEntityInstances/getDisplayName'; import { ResultsPageSizeContext } from '../../shared-contexts'; - +import type { EnrollmentPayload } from + '../../../DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types'; +import type { TeiPayload } from + '../../common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types'; type Props = { findMode?: ?$Values, @@ -74,10 +77,16 @@ class TeiRelationshipPlain extends React.Component { }); } - handleAddRelationshipWithNewTei = (itemId: string, dataEntryId: string) => { + handleAddRelationshipWithNewTei = ( + itemId: string, + dataEntryId: string, + payload: EnrollmentPayload | TeiPayload, + ) => { this.props.onAddRelationship({ itemId, dataEntryId, + data: payload, + id: payload?.trackedEntity, }); } diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventRelationships.epics.js b/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventRelationships.epics.js index d1d27a8cc1..c662eae579 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventRelationships.epics.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventRelationships.epics.js @@ -27,7 +27,7 @@ import { convertClientRelationshipToServer, getRelationshipsForEvent, } from '../../../../relationships'; -import { getRelationshipNewTei } from '../../NewRelationship/RegisterTei'; +import { getRelationshipNewTeiName } from '../../NewRelationship/RegisterTei'; const relationshipKey = 'viewEvent'; @@ -78,9 +78,7 @@ export const addRelationshipForViewEventEpic = (action$: InputObservable, store: const eventId = state.viewEventPage.eventId; const existingRelationships = state.dataEntriesRelationships[relationshipKey] || []; const payload = action.payload; - const entity = payload.entity; - - const toEntity = entity.id ? entity : getRelationshipNewTei(entity.dataEntryId, entity.itemId, state); + const toEntity = payload.entity; const relationshipClientId = uuid(); const clientRelationship = { @@ -92,6 +90,7 @@ export const addRelationshipForViewEventEpic = (action$: InputObservable, store: }, to: { ...toEntity, + name: toEntity.name || getRelationshipNewTeiName(toEntity.dataEntryId, toEntity.itemId, state), type: payload.entityType, }, relationshipType: { ...payload.relationshipType }, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index cea2f79f8f..7d20b9f6ca 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -6,6 +6,7 @@ import { DATA_ENTRY_ID } from '../../registerTei.const'; import enrollmentClasses from './enrollment.module.css'; import { EnrollmentRegistrationEntry } from '../../../../../../DataEntries'; import type { Props } from './dataEntryEnrollment.types'; +import { relatedStageActions } from '../../../../../../WidgetRelatedStages'; const NewEnrollmentRelationshipPlain = ({ @@ -21,6 +22,13 @@ const NewEnrollmentRelationshipPlain = ExistingUniqueValueDialogActions, }: Props) => { const fieldOptions = { theme, fieldLabelMediaBasedClass: enrollmentClasses.fieldLabelMediaBased }; + const relatedStageActionsOptions = { + [relatedStageActions.ENTER_DATA]: { + disabled: true, + disabledMessage: i18n.t('Enter details now is not available when creating a relationship'), + }, + [relatedStageActions.LINK_EXISTING_RESPONSE]: { hidden: true }, + }; return ( ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/index.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/index.js index 5bb8975389..d60b166b08 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/index.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/index.js @@ -1,2 +1,3 @@ // @flow export { DataEntryTrackedEntityInstance } from './DataEntryTrackedEntityInstance'; +export type { TeiPayload } from './dataEntryTrackedEntityInstance.types'; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/exposedHelpers/getRelationshipNewTei.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/exposedHelpers/getRelationshipNewTei.js deleted file mode 100644 index d5c862fcd6..0000000000 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/exposedHelpers/getRelationshipNewTei.js +++ /dev/null @@ -1,141 +0,0 @@ -// @flow -import uuid from 'd2-utilizr/src/uuid'; -import moment from 'moment'; -import { getFormattedStringFromMomentUsingEuropeanGlyphs } from 'capture-core-utils/date'; -import { capitalizeFirstLetter } from 'capture-core-utils/string/capitalizeFirstLetter'; -import { - getTrackerProgramThrowIfNotFound, - getTrackedEntityTypeThrowIfNotFound, - type RenderFoundation, -} from '../../../../../../metaData'; -import { convertFormToClient, convertClientToServer } from '../../../../../../converters'; -import { getDisplayName } from '../../../../../../trackedEntityInstances/getDisplayName'; -import { - convertDataEntryValuesToClientValues, -} from '../../../../../DataEntry/common/convertDataEntryValuesToClientValues'; -import { getDataEntryKey } from '../../../../../DataEntry/common/getDataEntryKey'; - -function getTrackerProgramMetadata(programId: string) { - const program = getTrackerProgramThrowIfNotFound(programId); - return { - form: program.enrollment.enrollmentForm, - attributes: program.trackedEntityType.attributes, - tetName: program.trackedEntityType.name, - }; -} - -function getTETMetadata(tetId: string) { - const tet = getTrackedEntityTypeThrowIfNotFound(tetId); - return { - form: tet.teiRegistration.form, - attributes: tet.attributes, - tetName: tet.name, - }; -} - -function getMetadata(programId: ?string, tetId: string) { - return programId ? getTrackerProgramMetadata(programId) : getTETMetadata(tetId); -} - - -function getClientValuesForFormData(formValues: Object, formFoundation: RenderFoundation) { - const clientValues = formFoundation.convertValues(formValues, convertFormToClient); - return clientValues; -} - -function getServerValuesForMainValues( - values: Object, - meta: Object, - formFoundation: RenderFoundation, -) { - const clientValues = convertDataEntryValuesToClientValues( - values, - meta, - formFoundation, - ) || {}; - - // potientally run this through a server to client converter for enrollment, the same way as for event - const serverValues = Object - .keys(clientValues) - .reduce((acc, key) => { - const value = clientValues[key]; - const type = meta[key].type; - acc[key] = convertClientToServer(value, type); - return acc; - }, {}); - - return serverValues; -} - -function getPossibleTetFeatureTypeKey(serverValues: Object) { - return Object - .keys(serverValues) - .find(key => key.startsWith('FEATURETYPE_')); -} - -function buildGeometryProp(key: string, serverValues: Object) { - if (!serverValues[key]) { - return undefined; - } - const type = capitalizeFirstLetter(key.replace('FEATURETYPE_', '').toLocaleLowerCase()); - return { - type, - coordinates: serverValues[key], - }; -} - -export function getRelationshipNewTei(dataEntryId: string, itemId: string, state: ReduxState) { - const dataEntryKey = getDataEntryKey(dataEntryId, itemId); - const formValues = state.formsValues[dataEntryKey]; - const { programId, orgUnit } = state.newRelationshipRegisterTei; - const tetId = state.newRelationship.selectedRelationshipType.to.trackedEntityTypeId; - - const { attributes: metaDataAttributes, form: formFoundation, tetName } = getMetadata(programId, tetId); - const clientValuesForFormData = getClientValuesForFormData(formValues, formFoundation); - const displayName = getDisplayName(clientValuesForFormData, metaDataAttributes, tetName); - - const serverValuesForFormValues = formFoundation.convertValues(clientValuesForFormData, convertClientToServer); - const serverValuesForMainValues = getServerValuesForMainValues( - state.dataEntriesFieldsValue[dataEntryKey], - state.dataEntriesFieldsMeta[dataEntryKey], - formFoundation, - ); - - // $FlowFixMe - const attributes = Object.keys(serverValuesForFormValues) - .map(key => ({ - attribute: key, - value: serverValuesForFormValues[key], - })); - - const enrollment = programId ? { - program: programId, - status: 'ACTIVE', - orgUnit: orgUnit.id, - occurredAt: getFormattedStringFromMomentUsingEuropeanGlyphs(moment()), - attributes, - ...serverValuesForMainValues, - } : null; - - const tetFeatureTypeKey = getPossibleTetFeatureTypeKey(serverValuesForFormValues); - let geometry; - if (tetFeatureTypeKey) { - geometry = buildGeometryProp(tetFeatureTypeKey, serverValuesForFormValues); - delete serverValuesForFormValues[tetFeatureTypeKey]; - } - - const teiPayload = { - // $FlowFixMe - attributes: !enrollment ? attributes : undefined, - orgUnit: orgUnit.id, - trackedEntityType: tetId, - geometry, - enrollments: enrollment ? [enrollment] : [], - }; - - return { - data: teiPayload, - name: displayName, - id: uuid(), - }; -} diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/exposedHelpers/getRelationshipNewTeiName.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/exposedHelpers/getRelationshipNewTeiName.js new file mode 100644 index 0000000000..9a264ea2a5 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/exposedHelpers/getRelationshipNewTeiName.js @@ -0,0 +1,51 @@ +// @flow +import { + getTrackerProgramThrowIfNotFound, + getTrackedEntityTypeThrowIfNotFound, + type RenderFoundation, +} from '../../../../../../metaData'; +import { convertFormToClient } from '../../../../../../converters'; +import { getDisplayName } from '../../../../../../trackedEntityInstances/getDisplayName'; +import { getDataEntryKey } from '../../../../../DataEntry/common/getDataEntryKey'; + +function getTrackerProgramMetadata(programId: string) { + const program = getTrackerProgramThrowIfNotFound(programId); + return { + form: program.enrollment.enrollmentForm, + attributes: program.trackedEntityType.attributes, + tetName: program.trackedEntityType.name, + }; +} + +function getTETMetadata(tetId: string) { + const tet = getTrackedEntityTypeThrowIfNotFound(tetId); + return { + form: tet.teiRegistration.form, + attributes: tet.attributes, + tetName: tet.name, + }; +} + +function getMetadata(programId: ?string, tetId: string) { + return programId ? getTrackerProgramMetadata(programId) : getTETMetadata(tetId); +} + + +function getClientValuesForFormData(formValues: Object, formFoundation: RenderFoundation) { + const clientValues = formFoundation.convertValues(formValues, convertFormToClient); + return clientValues; +} + +export function getRelationshipNewTeiName(dataEntryId: string, itemId: string, state: ReduxState) { + const dataEntryKey = getDataEntryKey(dataEntryId, itemId); + const formValues = state.formsValues[dataEntryKey]; + const { programId } = state.newRelationshipRegisterTei; + const tetId = state.newRelationship.selectedRelationshipType.to.trackedEntityTypeId; + + const { attributes: metaDataAttributes, form: formFoundation, tetName } = getMetadata(programId, tetId); + const clientValuesForFormData = getClientValuesForFormData(formValues, formFoundation); + const displayName = getDisplayName(clientValuesForFormData, metaDataAttributes, tetName); + + // $FlowFixM + return displayName; +} diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/index.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/index.js index dea1f66c95..bdb73c3680 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/index.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/index.js @@ -9,4 +9,4 @@ export { export { loadSearchGroupDuplicatesForReviewEpic, } from '../../../../PossibleDuplicatesDialog/possibleDuplicatesDialog.epics'; -export { getRelationshipNewTei } from './exposedHelpers/getRelationshipNewTei'; +export { getRelationshipNewTeiName } from './exposedHelpers/getRelationshipNewTeiName'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js index b3ba7c2d14..663348da1b 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js @@ -52,7 +52,6 @@ const ValidatedPlain = ({ enrollmentId={enrollmentId} programId={programId} programStageId={stage?.id} - currentStageLabel={stage.name} /> , - relatedStageDataValues: RelatedStageDataValueStates, - programId: string, - teiId: string, - currentProgramStageId: string, - enrollmentId: string, - relatedStageType: RelatedStageType, - clientRequestEvent: RequestEvent, -|} diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedRelatedStageEvent/index.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedRelatedStageEvent/index.js deleted file mode 100644 index 1771ccf5b3..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedRelatedStageEvent/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow -export { getConvertedRelatedStageEvent } from './getConvertedRelatedStageEvent'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useBuildNewEventPayload.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useBuildNewEventPayload.js index e4269a87e1..ab8c0f3683 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useBuildNewEventPayload.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/useBuildNewEventPayload.js @@ -6,8 +6,9 @@ import { getAddEventEnrollmentServerData } from './getConvertedAddEvent'; import { convertDataEntryToClientValues } from '../../DataEntry/common/convertDataEntryToClientValues'; import { generateUID } from '../../../utils/uid/generateUID'; import { addEventSaveTypes } from '../DataEntry/addEventSaveTypes'; -import { getConvertedRelatedStageEvent } from './getConvertedRelatedStageEvent'; -import type { LinkedRequestEvent, RelatedStageRefPayload, RequestEvent } from './validated.types'; +import { getConvertedRelatedStageEvent } from '../../DataEntries'; +import type { LinkedRequestEvent, RequestEvent } from '../../DataEntries'; +import type { RelatedStageRefPayload } from '../../WidgetRelatedStages'; type Props = { dataEntryId: string, @@ -21,18 +22,18 @@ type Props = { }; export const createServerData = ({ - clientRequestEvent, + serverRequestEvent, linkedEvent, relationship, enrollment, }: { - clientRequestEvent: RequestEvent, + serverRequestEvent: RequestEvent, linkedEvent: ?LinkedRequestEvent, relationship: ?Object, enrollment: ?Object, }) => { const relationships = relationship ? [relationship] : undefined; - const newEvents = linkedEvent ? [clientRequestEvent, linkedEvent] : [clientRequestEvent]; + const newEvents = linkedEvent ? [serverRequestEvent, linkedEvent] : [serverRequestEvent]; if (enrollment) { const updatedEnrollment = { ...enrollment, events: [...(enrollment.events || []), ...newEvents] }; @@ -66,9 +67,9 @@ export const useBuildNewEventPayload = ({ const notes = useSelector(({ dataEntriesNotes }) => dataEntriesNotes[dataEntryKey]); const { fromClientDate } = useTimeZoneConversion(); - const buildRelatedStageEventPayload = (clientRequestEvent, saveType: ?$Values, relatedStageRef) => { + const buildRelatedStageEventPayload = (serverRequestEvent, saveType: ?$Values, relatedStageRef) => { if ( - relatedStageRef.current + relatedStageRef?.current && relatedStageRef.current.eventHasLinkableStageRelationship() ) { const isValid = relatedStageRef.current.formIsValidOnSave(); @@ -96,7 +97,7 @@ export const useBuildNewEventPayload = ({ const { linkedEvent, relationship } = getConvertedRelatedStageEvent({ linkMode, relatedStageDataValues, - clientRequestEvent, + serverRequestEvent, relatedStageType: selectedRelationshipType, programId, currentProgramStageId: formFoundation.id, @@ -121,7 +122,7 @@ export const useBuildNewEventPayload = ({ const buildNewEventPayload = ( saveType: ?$Values, - relatedStageRef: {| current: (?RelatedStageRefPayload) |}, + relatedStageRef?: {| current: (?RelatedStageRefPayload) |}, ) => { const requestEventId = generateUID(); @@ -133,7 +134,7 @@ export const useBuildNewEventPayload = ({ ); const notesValues = notes ? notes.map(note => ({ value: note.value })) : []; - const clientRequestEvent = getAddEventEnrollmentServerData({ + const serverRequestEvent = getAddEventEnrollmentServerData({ formFoundation, formClientValues, eventId: requestEventId, @@ -153,11 +154,11 @@ export const useBuildNewEventPayload = ({ linkedEvent, relationship, linkMode, - } = buildRelatedStageEventPayload(clientRequestEvent, saveType, relatedStageRef); + } = buildRelatedStageEventPayload(serverRequestEvent, saveType, relatedStageRef); return { formHasError, - clientRequestEvent, + serverRequestEvent, linkedEvent, relationship, linkMode, diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.actions.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.actions.js index 0aa4c1292b..f14942ced5 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.actions.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.actions.js @@ -1,8 +1,8 @@ // @flow import { actionCreator } from '../../../actions/actions.utils'; import { effectMethods } from '../../../trackerOffline'; -import { actions as RelatedStageModes } from '../../WidgetRelatedStages/constants'; -import type { RequestEvent, LinkedRequestEvent } from './validated.types'; +import { relatedStageActions } from '../../WidgetRelatedStages'; +import type { RequestEvent, LinkedRequestEvent } from '../../DataEntries'; import type { ExternalSaveHandler } from '../common.types'; export const newEventBatchActionTypes = { @@ -37,7 +37,7 @@ export const requestSaveEvent = ({ linkedEvent: ?LinkedRequestEvent, relationship: ?Object, serverData: Object, - linkMode: ?$Keys, + linkMode: ?$Keys, onSaveExternal: ?ExternalSaveHandler, onSaveSuccessActionType?: string, onSaveErrorActionType?: string, @@ -62,7 +62,7 @@ export const setSaveEnrollmentEventInProgress = ({ requestEventId: string, linkedEventId: ?string, linkedOrgUnitId: ?string, - linkMode: ?$Keys, + linkMode: ?$Keys, }) => actionCreator(newEventWidgetActionTypes.SET_SAVE_ENROLLMENT_EVENT_IN_PROGRESS)({ requestEventId, linkedEventId, diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.types.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.types.js index e0af3da0ff..8d8e58fa74 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.types.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/validated.types.js @@ -7,37 +7,6 @@ import type { RulesExecutionDependenciesClientFormatted, } from '../common.types'; -type CommonEventDetails = { - event: string, - program: string, - programStage: string, - orgUnit: string, - trackedEntity: string, - enrollment: string, - scheduledAt: string, - dataValues: Array<{ dataElement: string, value: any }>, - status?: string, -} - -export type RequestEvent = { - ...CommonEventDetails, - occurredAt: string, - notes?: Array<{ value: string }>, - completedAt?: string, -} - -export type LinkedRequestEvent = { - ...CommonEventDetails, - occurredAt?: string, - completedAt?: string, -} - -export type RelatedStageRefPayload = {| - getLinkedStageValues: () => any, - eventHasLinkableStageRelationship: () => boolean, - formIsValidOnSave: () => boolean, -|} - export type ContainerProps = {| ...CommonValidatedProps, orgUnit: OrgUnit, diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/EnterDataInOrgUnit/EnterData.component.js b/src/core_modules/capture-core/components/WidgetRelatedStages/EnterDataInOrgUnit/EnterData.component.js index 47ff354bed..f61bde9c73 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/EnterDataInOrgUnit/EnterData.component.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/EnterDataInOrgUnit/EnterData.component.js @@ -48,7 +48,6 @@ type Props = { linkableStageLabel: string, relatedStagesDataValues: RelatedStageDataValueStates, setRelatedStagesDataValues: (() => Object) => void, - currentStageLabel: string, saveAttempted: boolean, errorMessages: ErrorMessagesForRelatedStages, ...CssClasses @@ -60,7 +59,6 @@ export const EnterDataInOrgUnitPlain = ({ setRelatedStagesDataValues, saveAttempted, errorMessages, - currentStageLabel, classes, }: Props) => { const onSelectOrgUnit = (e: { id: string, displayName: string, path: string }) => { @@ -98,11 +96,10 @@ export const EnterDataInOrgUnitPlain = ({ {i18n.t( relatedStagesDataValues?.orgUnit?.name - ? 'Enter {{linkableStageLabel}} details for {{orgUnitLabel}} in the next step after completing this {{currentStageLabel}}.' - : 'Select organisation unit and enter {{linkableStageLabel}} details in the next step after completing this {{currentStageLabel}}.', + ? 'Enter {{linkableStageLabel}} details for {{orgUnitLabel}} in the next step' + : 'Select organisation unit and enter {{linkableStageLabel}} details in the next step', { linkableStageLabel, - currentStageLabel, orgUnitLabel: relatedStagesDataValues?.orgUnit?.name, }, )} diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/LinkToExisting/LinkToExisting.component.js b/src/core_modules/capture-core/components/WidgetRelatedStages/LinkToExisting/LinkToExisting.component.js index a53c05eb3f..dbc8d442d5 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/LinkToExisting/LinkToExisting.component.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/LinkToExisting/LinkToExisting.component.js @@ -40,7 +40,7 @@ export const LinkToExistingPlain = ({ return (

- {i18n.t('Link to an existing {{linkableStageLabel}}', { + {i18n.t('Choose a {{linkableStageLabel}} event', { linkableStageLabel, })}

diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.component.js b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.component.js index 38bbb6e24b..acf6c6e0a5 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.component.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.component.js @@ -4,7 +4,7 @@ import i18n from '@dhis2/d2-i18n'; import { Button, colors, Radio, spacers, spacersNum } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; -import { actions as RelatedStagesActionTypes, mainOptionTranslatedTexts, relatedStageStatus } from '../constants'; +import { relatedStageActions, mainOptionTranslatedTexts, relatedStageStatus } from '../constants'; import { useCanAddNewEventToStage } from '../hooks/useCanAddNewEventToStage'; import { DataSection } from '../../DataSection'; import { ScheduleInOrgUnit } from '../ScheduleInOrgUnit'; @@ -40,7 +40,138 @@ const styles = () => ({ }, }); -export const RelatedStagesActionsPlain = ({ +const Schedule = ({ + actionsOptions, + linkableEvents, + selectedAction, + updateSelectedAction, + programStage, + canAddNewEventToStage, +}) => { + const { hidden, disabled, disabledMessage } = + (actionsOptions && actionsOptions[relatedStageActions.SCHEDULE_IN_ORG]) || {}; + if (hidden) { + return null; + } + + const tooltipEnabled = disabled || !canAddNewEventToStage; + let tooltipContent = ''; + if (disabled) { + tooltipContent = disabledMessage; + } else if (!linkableEvents.length) { + tooltipContent = i18n.t('{{ linkableStageLabel }} is not repeatable', { + linkableStageLabel: programStage.stageForm.name, + interpolation: { escapeValue: false }, + }); + } + + return ( + + updateSelectedAction(e.value)} + value={relatedStageActions.SCHEDULE_IN_ORG} + /> + + ); +}; + +const EnterData = ({ + actionsOptions, + linkableEvents, + selectedAction, + updateSelectedAction, + programStage, + canAddNewEventToStage, +}) => { + const { hidden, disabled, disabledMessage } = + (actionsOptions && actionsOptions[relatedStageActions.ENTER_DATA]) || {}; + if (hidden) { + return null; + } + + const tooltipEnabled = disabled || !canAddNewEventToStage; + let tooltipContent = ''; + if (disabled) { + tooltipContent = disabledMessage; + } else if (!linkableEvents.length) { + tooltipContent = i18n.t('{{ linkableStageLabel }} is not repeatable', { + linkableStageLabel: programStage.stageForm.name, + interpolation: { escapeValue: false }, + }); + } + + return ( + + updateSelectedAction(e.value)} + value={relatedStageActions.ENTER_DATA} + /> + + ); +}; + +const LinkExistingResponse = ({ + actionsOptions, + linkableEvents, + selectedAction, + updateSelectedAction, + programStage, +}) => { + const { hidden, disabled, disabledMessage } = + (actionsOptions && actionsOptions[relatedStageActions.LINK_EXISTING_RESPONSE]) || {}; + if (hidden) { + return null; + } + + const tooltipEnabled = disabled || !linkableEvents.length; + let tooltipContent = ''; + if (disabled) { + tooltipContent = disabledMessage; + } else if (!linkableEvents.length) { + tooltipContent = i18n.t('{{ linkableStageLabel }} has no linkable events', { + linkableStageLabel: programStage.stageForm.name, + interpolation: { escapeValue: false }, + }); + } + + return ( + + updateSelectedAction(e.value)} + value={relatedStageActions.LINK_EXISTING_RESPONSE} + /> + + ); +}; + +const RelatedStagesActionsPlain = ({ classes, type, relationshipName, @@ -50,15 +181,15 @@ export const RelatedStagesActionsPlain = ({ relatedStagesDataValues, setRelatedStagesDataValues, constraint, - currentStageLabel, errorMessages, saveAttempted, + actionsOptions, }: Props) => { const { programStage } = useProgramStageInfo(constraint?.programStage?.id); const selectedAction = useMemo(() => relatedStagesDataValues.linkMode, [relatedStagesDataValues.linkMode]); - const updateSelectedAction = (action: ?$Values) => { + const updateSelectedAction = (action: ?$Values) => { setRelatedStagesDataValues(prevState => ({ ...prevState, linkMode: action, @@ -73,65 +204,34 @@ export const RelatedStagesActionsPlain = ({ return (
{type === relatedStageStatus.LINKABLE && ( <> - - updateSelectedAction(e.value)} - value={RelatedStagesActionTypes.SCHEDULE_IN_ORG} - /> - - - updateSelectedAction(e.value)} - value={RelatedStagesActionTypes.ENTER_DATA} - /> - - - updateSelectedAction(e.value)} - value={RelatedStagesActionTypes.LINK_EXISTING_RESPONSE} - /> - + + + )} @@ -152,7 +252,7 @@ export const RelatedStagesActionsPlain = ({
)} - {selectedAction === RelatedStagesActionTypes.SCHEDULE_IN_ORG && ( + {selectedAction === relatedStageActions.SCHEDULE_IN_ORG && ( )} - {selectedAction === RelatedStagesActionTypes.ENTER_DATA && ( + {selectedAction === relatedStageActions.ENTER_DATA && ( )} - {selectedAction === RelatedStagesActionTypes.LINK_EXISTING_RESPONSE && ( + {selectedAction === relatedStageActions.LINK_EXISTING_RESPONSE && ( void, setRelatedStagesDataValues: (() => Object) => void, - currentStageLabel: string, + actionsOptions?: { + [key: $Keys]: { + hidden?: boolean, + disabled?: boolean, + disabledMessage?: string + }, + }, ...CssClasses |} diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js index 4c3013ca3c..9ce0628481 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js @@ -14,7 +14,6 @@ const WidgetRelatedStagesPlain = ({ programId, enrollmentId, programStageId, - currentStageLabel, ...passOnProps }: Props, ref) => { const { currentRelatedStagesStatus, selectedRelationshipType, constraint } = useRelatedStages({ @@ -110,7 +109,6 @@ const WidgetRelatedStagesPlain = ({ saveAttempted={saveAttempted} errorMessages={errorMessages} constraint={constraint} - currentStageLabel={currentStageLabel} {...passOnProps} /> ); diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js index 0274e06456..7876e528c1 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js @@ -1,5 +1,5 @@ // @flow -import { actions as LinkModes } from './constants'; +import { relatedStageActions } from './index'; import type { Constraint } from './RelatedStagesActions/RelatedStagesActions.types'; export type RelationshipType = {| @@ -17,12 +17,18 @@ export type RelationshipType = {| export type Props = {| programId: string, - enrollmentId: string, + enrollmentId?: string, programStageId: string, - currentStageLabel: string, + actionsOptions?: { + [key: $Keys]: { + hidden?: boolean, + disabled?: boolean, + disabledMessage?: string + }, + }, |} export type RelatedStageDataValueStates = {| - linkMode: ?$Keys, + linkMode: ?$Keys, scheduledAt: string, scheduledAtFormatError: ?{error: ?string, errorCode: ?string}, orgUnit: ?{ @@ -32,3 +38,27 @@ export type RelatedStageDataValueStates = {| }, linkedEventId: ?string, |} + +export type RelatedStageRelationshipType = {| + id: string, + fromConstraint: {| + programStage: { + id: string, + }, + |}, + toConstraint: { + programStage: { + id: string, + }, + } +|} + +export type RelatedStageRefPayload = { + getLinkedStageValues: () => { + selectedRelationshipType: RelatedStageRelationshipType, + relatedStageDataValues: RelatedStageDataValueStates, + linkMode: ?$Keys, + }, + eventHasLinkableStageRelationship: () => boolean, + formIsValidOnSave: () => boolean, +}; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/constants.js b/src/core_modules/capture-core/components/WidgetRelatedStages/constants.js index bb46ba298d..7b021f5fc5 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/constants.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/constants.js @@ -6,14 +6,14 @@ export const relatedStageStatus = Object.freeze({ AMBIGUOUS_RELATIONSHIPS: 'AMBIGUOUS_RELATIONSHIPS', }); -export const actions = Object.freeze({ +export const relatedStageActions = Object.freeze({ SCHEDULE_IN_ORG: 'SCHEDULE_IN_ORG', LINK_EXISTING_RESPONSE: 'LINK_EXISTING_RESPONSE', ENTER_DATA: 'ENTER_DATA', }); export const mainOptionTranslatedTexts = { - [actions.SCHEDULE_IN_ORG]: i18n.t('Schedule'), - [actions.ENTER_DATA]: i18n.t('Enter details now'), - [actions.LINK_EXISTING_RESPONSE]: i18n.t('Link to an existing'), + [relatedStageActions.SCHEDULE_IN_ORG]: i18n.t('Schedule'), + [relatedStageActions.ENTER_DATA]: i18n.t('Enter details now'), + [relatedStageActions.LINK_EXISTING_RESPONSE]: i18n.t('Link to an existing event'), }; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStageEvents.js b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStageEvents.js index 8a5e8a7c17..058ab02f41 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStageEvents.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStageEvents.js @@ -7,7 +7,7 @@ import { handleAPIResponse, REQUESTED_ENTITIES } from '../../../utils/api'; type Props = { stageId: ?string, - enrollmentId: string, + enrollmentId: ?string, scheduledLabel: string, occurredLabel: string, relationshipTypeId: ?string, @@ -41,7 +41,7 @@ export const useRelatedStageEvents = ({ ['availableRelatedStageEvents', stageId, enrollmentId, relationshipTypeId], query, { - enabled: !!stageId && enabled, + enabled: !!stageId && !!enrollmentId && enabled, cacheTime: 0, staleTime: 0, select: (response: any) => { diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/index.js b/src/core_modules/capture-core/components/WidgetRelatedStages/index.js index 1cba4d9943..e75b93c8cc 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/index.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/index.js @@ -1,4 +1,9 @@ // @flow export { WidgetRelatedStages } from './WidgetRelatedStages.component'; -export type { RelatedStageDataValueStates } from './WidgetRelatedStages.types'; +export type { + RelatedStageDataValueStates, + RelatedStageRefPayload, + RelatedStageRelationshipType, +} from './WidgetRelatedStages.types'; +export { relatedStageActions } from './constants'; export { relatedStageWidgetIsValid } from './relatedStageEventIsValid/relatedStageEventIsValid'; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/ValidationFunctions.js b/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/ValidationFunctions.js index 219e354ded..187f20b625 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/ValidationFunctions.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/ValidationFunctions.js @@ -2,7 +2,7 @@ import i18n from '@dhis2/d2-i18n'; import { isValidOrgUnit } from '../../../../capture-core-utils/validators/form'; import { isValidDate } from '../../../utils/validation/validators/form'; -import { actions as RelatedStageModes } from '../constants'; +import { relatedStageActions } from '../constants'; type Props = { scheduledAt: ?string, @@ -87,8 +87,8 @@ const linkToExistingResponse = (props) => { export const ValidationFunctionsByLinkMode: { [key: string]: (props: ?Props) => boolean } = { - [RelatedStageModes.SCHEDULE_IN_ORG]: props => scheduleInOrgUnit(props), - [RelatedStageModes.ENTER_DATA]: props => enterData(props), - [RelatedStageModes.LINK_EXISTING_RESPONSE]: props => linkToExistingResponse(props), + [relatedStageActions.SCHEDULE_IN_ORG]: props => scheduleInOrgUnit(props), + [relatedStageActions.ENTER_DATA]: props => enterData(props), + [relatedStageActions.LINK_EXISTING_RESPONSE]: props => linkToExistingResponse(props), }; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/relatedStageEventIsValid.types.js b/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/relatedStageEventIsValid.types.js index 1c5e56a676..1f4fa5cb35 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/relatedStageEventIsValid.types.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/relatedStageEventIsValid/relatedStageEventIsValid.types.js @@ -1,9 +1,9 @@ // @flow import type { ErrorMessagesForRelatedStages } from '../RelatedStagesActions'; -import { actions as LinkModes } from '../constants'; +import { relatedStageActions } from '../index'; export type RelatedStageIsValidProps = {| - linkMode: ?$Keys, + linkMode: ?$Keys, scheduledAt: ?string, scheduledAtFormatError: ?{error: ?string, errorCode: ?string}, orgUnit: ?{ diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js index b57a46e4f0..e0e2055d29 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js @@ -69,13 +69,20 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => { }); }, onSuccess: async (apiResponse, requestData) => { - const apiRelationshipId = apiResponse.bundleReport.typeReportMap.RELATIONSHIP.objectReports[0].uid; + const apiRelationshipIds = apiResponse.bundleReport.typeReportMap.RELATIONSHIP.objectReports.reduce( + (acc, report) => [...acc, report.uid], + [], + ); const currentRelationships = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]); const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, currentRelationships); if (apiRelationships.length === 0) return; const newRelationships = apiRelationships.map((relationship) => { - if (relationship.relationship === apiRelationshipId) { + if ( + apiRelationshipIds.find( + apiRelationshipId => apiRelationshipId === relationship.relationship, + ) + ) { return { ...relationship, pendingApiResponse: false,