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 a439eee13d..1f2b9b7b3d 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 @@ -26,7 +26,7 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ formId, enrollmentMetadata, formFoundation, - } = useLifecycle(selectedScopeId, id, trackedEntityInstanceAttributes, orgUnit); + } = useLifecycle(selectedScopeId, id, trackedEntityInstanceAttributes, orgUnit, teiId, selectedScopeId); const isUserInteractionInProgress: boolean = useSelector( state => diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js index 24b68e9351..4a84d7ccb7 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js @@ -4,7 +4,6 @@ import { useEffect, useRef } from 'react'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; import { startNewEnrollmentDataEntryInitialisation } from '../EnrollmentRegistrationEntry.actions'; import { scopeTypes, getProgramThrowIfNotFound } from '../../../../metaData'; -import { useLocationQuery } from '../../../../utils/routing'; import { useScopeInfo } from '../../../../hooks/useScopeInfo'; import { useFormValues } from './index'; import type { InputAttribute } from './useFormValues'; @@ -19,8 +18,8 @@ export const useLifecycle = ( trackedEntityInstanceAttributes?: Array, orgUnit: ?OrgUnit, teiId: ?string, + programId: string, ) => { - const { programId } = useLocationQuery(); const dataEntryReadyRef = useRef(false); const dispatch = useDispatch(); const program = programId && getProgramThrowIfNotFound(programId); diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js index a38c30e4e5..e36a6e10e6 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveAutoGenerateEvents.js @@ -24,7 +24,7 @@ export const deriveAutoGenerateEvents = ({ occurredAt: string, programId: string, orgUnitId: string, - firstStageMetadata: ProgramStage, + firstStageMetadata: ?ProgramStage, attributeCategoryOptions: { [categoryId: string]: string } | string, }) => { // in case we have a program that does not have an incident date (occurredAt), such as Malaria case diagnosis, diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js index bf40f45135..2e4f952f92 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/helpers/deriveFirstStageDuringRegistrationEvent.js @@ -16,7 +16,7 @@ export const deriveFirstStageDuringRegistrationEvent = ({ fieldsValue, attributeCategoryOptions, }: { - firstStageMetadata: ProgramStage, + firstStageMetadata: ?ProgramStage, programId: string, orgUnitId: string, currentEventValues?: { [id: string]: any }, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js index 4b0ad71c40..292a1d1530 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js @@ -6,11 +6,13 @@ import { DataEntryTrackedEntityInstance } from './TrackedEntityInstance'; type Props = { showDataEntry: boolean, programId: string, + onSaveWithoutEnrollment: () => void, + onSaveWithEnrollment: () => void, }; export class RegisterTeiDataEntryComponent extends React.Component { render() { - const { showDataEntry, programId, ...passOnProps } = this.props; + const { showDataEntry, programId, onSaveWithoutEnrollment, onSaveWithEnrollment, ...passOnProps } = this.props; if (!showDataEntry) { return null; @@ -20,6 +22,7 @@ export class RegisterTeiDataEntryComponent extends React.Component { return ( ); } @@ -27,6 +30,7 @@ export class RegisterTeiDataEntryComponent extends React.Component { return ( ); } diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js index 4bee8f13be..9a9a7070af 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js @@ -58,7 +58,8 @@ const DialogButtons = ({ onCancel, onSave, trackedEntityName }) => ( const RegisterTeiPlain = ({ dataEntryId, onLink, - onSave, + onSaveWithoutEnrollment, + onSaveWithEnrollment, onGetUnsavedAttributeValues, trackedEntityName, trackedEntityTypeId, @@ -101,7 +102,8 @@ const RegisterTeiPlain = ({ /> (newRelationshipRegisterTei.error)); const selectedScopeId = suggestedProgramId || trackedEntityTypeId; const { trackedEntityName } = useScopeInfo(selectedScopeId); - const { buildTeiPayload } = useDataEntryReduxConverter({ + const { + buildTeiWithEnrollment, + buildTeiWithoutEnrollment, + } = useDataEntryReduxConverter({ + selectedScopeId, dataEntryId, itemId, trackedEntityTypeId, }); - const onCreateNewTei = () => { - const teiPayload = buildTeiPayload(); + const onCreateNewTeiWithoutEnrollment = () => { + const teiPayload = buildTeiWithoutEnrollment(); + onSave(teiPayload); + }; + + const onCreateNewTeiWithEnrollment = () => { + const teiPayload = buildTeiWithEnrollment(); onSave(teiPayload); }; @@ -33,7 +42,8 @@ export const RegisterTei = ({ void, + onSaveWithEnrollment: () => void, + onSaveWithoutEnrollment: () => void, ...SharedProps, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/hooks/useDataEntryReduxConverter.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/hooks/useDataEntryReduxConverter.js index 9ec22b3a0b..1703315b0a 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/hooks/useDataEntryReduxConverter.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/hooks/useDataEntryReduxConverter.js @@ -1,45 +1,39 @@ // @flow import { useSelector } from 'react-redux'; -import moment from 'moment'; import { getDataEntryKey } from '../../../../../DataEntry/common/getDataEntryKey'; -import { getTrackedEntityTypeThrowIfNotFound, getTrackerProgramThrowIfNotFound } from '../../../../../../metaData'; +import { + getTrackerProgramThrowIfNotFound, + Section, +} from '../../../../../../metaData'; import type { RenderFoundation } from '../../../../../../metaData'; import { convertClientToServer, convertFormToClient } from '../../../../../../converters'; import { convertDataEntryValuesToClientValues, } from '../../../../../DataEntry/common/convertDataEntryValuesToClientValues'; -import { getFormattedStringFromMomentUsingEuropeanGlyphs } from '../../../../../../../capture-core-utils/date'; import { capitalizeFirstLetter } from '../../../../../../../capture-core-utils/string'; import { generateUID } from '../../../../../../utils/uid/generateUID'; +import { + useBuildFirstStageRegistration, +} from '../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useBuildFirstStageRegistration'; +import { + useMetadataForRegistrationForm, +} from '../../../../../DataEntries/common/TEIAndEnrollment/useMetadataForRegistrationForm'; +import { + useMergeFormFoundationsIfApplicable, +} from '../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useMergeFormFoundationsIfApplicable'; +import { + deriveAutoGenerateEvents, + deriveFirstStageDuringRegistrationEvent, +} from '../../../../New/RegistrationDataEntry/helpers'; +import { FEATURETYPE } from '../../../../../../constants'; type DataEntryReduxConverterProps = { + selectedScopeId: string; dataEntryId: string; itemId: string; trackedEntityTypeId: string; }; -function getMetadata(programId: ?string, tetId: string) { - return programId ? getTrackerProgramMetadata(programId) : getTETMetadata(tetId); -} - -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 getClientValuesForFormData(formValues: Object, formFoundation: RenderFoundation) { const clientValues = formFoundation.convertValues(formValues, convertFormToClient); return clientValues; @@ -86,7 +80,15 @@ function buildGeometryProp(key: string, serverValues: Object) { }; } +const geometryType = formValuesKey => Object.values(FEATURETYPE).find(geometryKey => geometryKey === formValuesKey); + +const deriveAttributesFromFormValues = (formValues = {}) => + Object.keys(formValues) + .filter(key => !geometryType(key)) + .map<{ attribute: string, value: ?any }>(key => ({ attribute: key, value: formValues[key] })); + export const useDataEntryReduxConverter = ({ + selectedScopeId, dataEntryId, itemId, trackedEntityTypeId, @@ -96,31 +98,68 @@ export const useDataEntryReduxConverter = ({ const dataEntryFieldValues = useSelector(({ dataEntriesFieldsValue }) => dataEntriesFieldsValue[dataEntryKey]); const dataEntryFieldsMeta = useSelector(({ dataEntriesFieldsMeta }) => dataEntriesFieldsMeta[dataEntryKey]); const { programId, orgUnit } = useSelector(({ newRelationshipRegisterTei }) => newRelationshipRegisterTei); + const { formFoundation: scopeFormFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); + const { firstStageMetaData } = useBuildFirstStageRegistration(programId, !programId); + const { formFoundation } = useMergeFormFoundationsIfApplicable(scopeFormFoundation, firstStageMetaData); - const buildTeiPayload = () => { - const { form: formFoundation } = getMetadata(programId, trackedEntityTypeId); + const buildTeiWithEnrollment = () => { + if (!formFoundation) return null; + const firstStage = firstStageMetaData && firstStageMetaData.stage; const clientValues = getClientValuesForFormData(formValues, formFoundation); - const serverValuesForFormValues = formFoundation.convertValues(clientValues, convertClientToServer); + const serverValuesForFormValues = formFoundation.convertAndGroupBySection(clientValues, convertClientToServer); const serverValuesForMainValues = getServerValuesForMainValues( dataEntryFieldValues, dataEntryFieldsMeta, formFoundation, ); - - // $FlowFixMe - const attributes = Object.keys(serverValuesForFormValues) - .map(key => ({ - attribute: key, - value: serverValuesForFormValues[key], - })); + const { enrolledAt, occurredAt } = serverValuesForMainValues; + + const { stages } = getTrackerProgramThrowIfNotFound(programId); + + const attributeCategoryOptionsId = 'attributeCategoryOptions'; + const attributeCategoryOptions = Object.keys(serverValuesForMainValues) + .filter(key => key.startsWith(attributeCategoryOptionsId)) + .reduce((acc, key) => { + const categoryId = key.split('-')[1]; + acc[categoryId] = serverValuesForMainValues[key]; + return acc; + }, {}); + + const formServerValues = serverValuesForFormValues[Section.groups.ENROLLMENT]; + const currentEventValues = serverValuesForFormValues[Section.groups.EVENT]; + + + const firstStageDuringRegistrationEvent = deriveFirstStageDuringRegistrationEvent({ + firstStageMetadata: firstStage, + programId, + orgUnitId: orgUnit.id, + currentEventValues, + fieldsValue: dataEntryFieldValues, + attributeCategoryOptions, + }); + + const autoGenerateEvents = deriveAutoGenerateEvents({ + firstStageMetadata: firstStage, + stages, + enrolledAt, + occurredAt, + programId, + orgUnitId: orgUnit.id, + attributeCategoryOptions, + }); + + const allEventsToBeCreated = firstStageDuringRegistrationEvent + ? [firstStageDuringRegistrationEvent, ...autoGenerateEvents] + : autoGenerateEvents; const enrollment = programId ? { program: programId, status: 'ACTIVE', orgUnit: orgUnit.id, - occurredAt: getFormattedStringFromMomentUsingEuropeanGlyphs(moment()), - attributes, - ...serverValuesForMainValues, + occurredAt, + enrolledAt, + attributes: deriveAttributesFromFormValues(formServerValues), + events: allEventsToBeCreated, } : null; const tetFeatureTypeKey = getPossibleTetFeatureTypeKey(serverValuesForFormValues); @@ -131,18 +170,45 @@ export const useDataEntryReduxConverter = ({ } return { - // $FlowFixMe - attributes: !enrollment ? attributes : undefined, trackedEntity: generateUID(), orgUnit: orgUnit.id, trackedEntityType: trackedEntityTypeId, geometry, - enrollments: enrollment ? [enrollment] : [], + enrollments: [enrollment], }; }; + const buildTeiWithoutEnrollment = () => { + if (!scopeFormFoundation) return null; + const clientValues = getClientValuesForFormData(formValues, scopeFormFoundation); + const serverValuesForFormValues = scopeFormFoundation.convertValues(clientValues, convertClientToServer); + + // $FlowFixMe + const attributes = Object.keys(serverValuesForFormValues) + .map(key => ({ + attribute: key, + value: serverValuesForFormValues[key], + })); + + const tetFeatureTypeKey = getPossibleTetFeatureTypeKey(serverValuesForFormValues); + let geometry; + if (tetFeatureTypeKey) { + geometry = buildGeometryProp(tetFeatureTypeKey, serverValuesForFormValues); + delete serverValuesForFormValues[tetFeatureTypeKey]; + } + + return { + attributes, + trackedEntity: generateUID(), + orgUnit: orgUnit.id, + trackedEntityType: trackedEntityTypeId, + geometry, + enrollments: [], + }; + }; return { - buildTeiPayload, + buildTeiWithEnrollment, + buildTeiWithoutEnrollment, }; };