From 5e41c46bf174a27060ece97094f7bbdfe8eb4169 Mon Sep 17 00:00:00 2001 From: Karoline Tufte Lien Date: Thu, 18 Jun 2026 12:19:16 +0200 Subject: [PATCH 1/7] fix: [DHIS2-17544] enforce org unit opening/closing dates in Capture Org unit opening/closing dates were never enforced in the Capture app, so users could enroll people and add/edit events with dates outside the selected org unit's open/close range. Enforce the range (both bounds inclusive, day granularity, calendar-safe) on the date fields that represent activity at an org unit, each validated against its own org unit: - Enrollment date (new + edit) - Event occurred date (new + edit) - Event scheduled/due date - Enrollment transfer: hard-block when the enrollment date falls outside the destination org unit's range Each constrained field disables out-of-range dates in the calendar and adds a blocking validator. Org units without opening/closing dates leave that side unconstrained. The incident date is intentionally not constrained, as it does not represent activity at the org unit (it may legitimately be a future or historical onset date). AI Assisted Co-Authored-By: Claude Opus 4.8 (1M context) --- i18n/en.pot | 629 +++++++++--------- .../EnrollmentDataEntry.component.tsx | 11 +- ...nrollmentDate.validatorContainersGetter.ts | 14 +- .../TransferModal/TransferModal.component.tsx | 22 +- .../TransferModal/TransferModal.types.ts | 2 +- .../DataEntry/DataEntry.component.tsx | 6 + .../DataEntry/DataEntry.container.tsx | 10 +- .../eventDate.validatorContainersGetter.ts | 10 +- .../eventDate.validatorContainersGetter.ts | 10 +- .../EditEventDataEntry.component.tsx | 6 + .../WidgetEventEdit.container.tsx | 10 +- .../ScheduleDate/ScheduleDate.component.tsx | 20 +- .../ScheduleDate/ScheduleDate.types.ts | 2 + .../WidgetEventSchedule.container.tsx | 10 +- .../coreOrgUnit/coreOrgUnit.types.ts | 3 + .../coreOrgUnit/fetchCoreOrgUnit.ts | 4 +- .../coreOrgUnit/useCoreOrgUnit.tsx | 2 +- .../orgUnits/getOrgUnitCalendarBounds.ts | 29 + ...getWithinOrgUnitDateRangeValidator.test.js | 75 +++ .../getWithinOrgUnitDateRangeValidator.ts | 108 +++ .../utils/validation/validators/form/index.ts | 1 + .../DateField/Date.component.tsx | 2 + .../DateAndTimeFields/DateField/Date.types.ts | 1 + 23 files changed, 668 insertions(+), 319 deletions(-) create mode 100644 src/core_modules/capture-core/utils/orgUnits/getOrgUnitCalendarBounds.ts create mode 100644 src/core_modules/capture-core/utils/validation/validators/form/getWithinOrgUnitDateRangeValidator.test.js create mode 100644 src/core_modules/capture-core/utils/validation/validators/form/getWithinOrgUnitDateRangeValidator.ts diff --git a/i18n/en.pot b/i18n/en.pot index b89585a062..cc60f3a127 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-10T13:04:43.537Z\n" -"PO-Revision-Date: 2026-06-10T13:04:43.537Z\n" +"POT-Creation-Date: 2026-06-18T10:19:23.891Z\n" +"PO-Revision-Date: 2026-06-18T10:19:23.892Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." @@ -123,17 +123,43 @@ msgstr "Show less" msgid "Show more" msgstr "Show more" +msgid "This value is validating" +msgstr "This value is validating" + +msgid "Async field update failed" +msgstr "Async field update failed" + msgid "error encountered during field validation" msgstr "error encountered during field validation" msgid "error" msgstr "error" -msgid "This value is validating" -msgstr "This value is validating" +msgid "{{programStageName}} completed" +msgstr "{{programStageName}} completed" -msgid "Async field update failed" -msgstr "Async field update failed" +msgid "Would you like to complete the enrollment and all active events as well?" +msgstr "Would you like to complete the enrollment and all active events as well?" + +msgid "{{count}} event in {{programStageName}}" +msgid_plural "{{count}} event in {{programStageName}}" +msgstr[0] "{{count}} event in {{programStageName}}" +msgstr[1] "{{count}} events in {{programStageName}}" + +msgid "Yes, complete enrollment and events" +msgstr "Yes, complete enrollment and events" + +msgid "Complete enrollment only" +msgstr "Complete enrollment only" + +msgid "No, cancel" +msgstr "No, cancel" + +msgid "Would you like to complete the enrollment?" +msgstr "Would you like to complete the enrollment?" + +msgid "Complete enrollment" +msgstr "Complete enrollment" msgid "Area" msgstr "Area" @@ -306,9 +332,6 @@ msgstr "" msgid "Yes, discard changes" msgstr "Yes, discard changes" -msgid "No, cancel" -msgstr "No, cancel" - msgid "You don't have access to create an event in the current selections" msgstr "You don't have access to create an event in the current selections" @@ -330,29 +353,6 @@ msgstr "Enroll in a program by selecting a program from the top bar." msgid "An error has occurred. See log for details" msgstr "An error has occurred. See log for details" -msgid "{{programStageName}} completed" -msgstr "{{programStageName}} completed" - -msgid "Would you like to complete the enrollment and all active events as well?" -msgstr "Would you like to complete the enrollment and all active events as well?" - -msgid "{{count}} event in {{programStageName}}" -msgid_plural "{{count}} event in {{programStageName}}" -msgstr[0] "{{count}} event in {{programStageName}}" -msgstr[1] "{{count}} events in {{programStageName}}" - -msgid "Yes, complete enrollment and events" -msgstr "Yes, complete enrollment and events" - -msgid "Complete enrollment only" -msgstr "Complete enrollment only" - -msgid "Would you like to complete the enrollment?" -msgstr "Would you like to complete the enrollment?" - -msgid "Complete enrollment" -msgstr "Complete enrollment" - msgid "A duplicate exists (but there were some errors, see log for details" msgstr "A duplicate exists (but there were some errors, see log for details" @@ -479,18 +479,6 @@ msgstr "To" msgid "Period" msgstr "Period" -msgid "Please provide zero or a positive integer" -msgstr "Please provide zero or a positive integer" - -msgid "Please specify the number of days" -msgstr "Please specify the number of days" - -msgid "Days in the past" -msgstr "Days in the past" - -msgid "Days in the future" -msgstr "Days in the future" - msgid "This week" msgstr "This week" @@ -515,6 +503,18 @@ msgstr "Absolute range" msgid "Relative range" msgstr "Relative range" +msgid "Please provide zero or a positive integer" +msgstr "Please provide zero or a positive integer" + +msgid "Please specify the number of days" +msgstr "Please specify the number of days" + +msgid "Days in the past" +msgstr "Days in the past" + +msgid "Days in the future" +msgstr "Days in the future" + msgid "Please specify a date" msgstr "Please specify a date" @@ -649,9 +649,6 @@ msgid_plural "Please enter at least {{minCharactersToSearch}} character to filte msgstr[0] "Please enter at least {{minCharactersToSearch}} character to filter" msgstr[1] "Please enter at least {{minCharactersToSearch}} characters to filter" -msgid "Locked to{{escape}} {{buttonText}}" -msgstr "Locked to{{escape}} {{buttonText}}" - msgid "{{fromDate}} to {{toDate}}" msgstr "{{fromDate}} to {{toDate}}" @@ -679,6 +676,9 @@ msgstr "after or equal to" msgid "before or equal to" msgstr "before or equal to" +msgid "Locked to{{escape}} {{buttonText}}" +msgstr "Locked to{{escape}} {{buttonText}}" + msgid "More filters" msgstr "More filters" @@ -712,6 +712,92 @@ msgstr "Notice" msgid "Close the notice" msgstr "Close the notice" +msgid "tracked entity instance" +msgstr "tracked entity instance" + +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" + +msgid "Link" +msgstr "Link" + +msgid "Save as new {{trackedEntityName}}" +msgstr "Save as new {{trackedEntityName}}" + +msgid "Some programs are being filtered." +msgstr "Some programs are being filtered." + +msgid "Show all" +msgstr "Show all" + +msgid "Program" +msgstr "Program" + +msgid "Registration" +msgstr "Registration" + +msgid "Organisation Unit" +msgstr "Organisation Unit" + +msgid "All accessible" +msgstr "All accessible" + +msgid "Selected" +msgstr "Selected" + +msgid "Please select an organisation unit." +msgstr "Please select an organisation unit." + +msgid "Organisation unit scope" +msgstr "Organisation unit scope" + +msgid "Selected program" +msgstr "Selected program" + +msgid "Search {{uniqueAttrName}}" +msgstr "Search {{uniqueAttrName}}" + +msgid "Search by attributes" +msgstr "Search by attributes" + +msgid "Search {{attributeName}}" +msgstr "Search {{attributeName}}" + +msgid "Fill in at least {{count}} attribute to search" +msgid_plural "Fill in at least {{count}} attribute to search" +msgstr[0] "Fill in at least {{count}} attribute to search" +msgstr[1] "Fill in at least {{count}} attributes to search" + +msgid "Missing search criteria" +msgstr "Missing search criteria" + +msgid "Please fill in {{uniqueTEAName}} to search" +msgstr "Please fill in {{uniqueTEAName}} to search" + +msgid "Back to search" +msgstr "Back to search" + +msgid "Search form is missing. See log for details" +msgstr "Search form is missing. See log for details" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "Could not retrieve metadata. Please try again later." + +msgid "Choose an organisation unit to start reporting" +msgstr "Choose an organisation unit to start reporting" + +msgid "The enrollment event data could not be found" +msgstr "The enrollment event data could not be found" + +msgid "Loading" +msgstr "Loading" + +msgid "An error occurred while loading the form" +msgstr "An error occurred while loading the form" + msgid "Quick actions" msgstr "Quick actions" @@ -724,6 +810,18 @@ msgstr "Make referral" msgid "No available program stages" msgstr "No available program stages" +msgid "Enrollment with id \"{{enrollmentId}}\" does not exist" +msgstr "Enrollment with id \"{{enrollmentId}}\" does not exist" + +msgid "Tracked entity instance with id \"{{teiId}}\" does not exist" +msgstr "Tracked entity instance with id \"{{teiId}}\" does not exist" + +msgid "Program with id \"{{programId}}\" does not exist" +msgstr "Program with id \"{{programId}}\" does not exist" + +msgid "An error occurred while fetching enrollments. Please enter a valid url." +msgstr "An error occurred while fetching enrollments. Please enter a valid url." + msgid "Invalid enrollment id {{enrollmentId}}." msgstr "Invalid enrollment id {{enrollmentId}}." @@ -778,18 +876,6 @@ msgstr "Create a new event in this program." msgid "View working list in this program." msgstr "View working list in this program." -msgid "Enrollment with id \"{{enrollmentId}}\" does not exist" -msgstr "Enrollment with id \"{{enrollmentId}}\" does not exist" - -msgid "Tracked entity instance with id \"{{teiId}}\" does not exist" -msgstr "Tracked entity instance with id \"{{teiId}}\" does not exist" - -msgid "Program with id \"{{programId}}\" does not exist" -msgstr "Program with id \"{{programId}}\" does not exist" - -msgid "An error occurred while fetching enrollments. Please enter a valid url." -msgstr "An error occurred while fetching enrollments. Please enter a valid url." - msgid "An error has occurred" msgstr "An error has occurred" @@ -885,9 +971,6 @@ msgstr "" "You don't have access to create a {{trackedEntityName}} in the current " "selections" -msgid "Choose an organisation unit to start reporting" -msgstr "Choose an organisation unit to start reporting" - msgid "Choose the {{missingCategories}} to start reporting" msgstr "Choose the {{missingCategories}} to start reporting" @@ -909,32 +992,8 @@ 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" - -msgid "Link" -msgstr "Link" - -msgid "Save as new {{trackedEntityName}}" -msgstr "Save as new {{trackedEntityName}}" - -msgid "Some programs are being filtered." -msgstr "Some programs are being filtered." - -msgid "Show all" -msgstr "Show all" - -msgid "Program" -msgstr "Program" - -msgid "Organisation Unit" -msgstr "Organisation Unit" - -msgid "Registration" -msgstr "Registration" +msgid "Register" +msgstr "Register" msgid "Search results" msgstr "Search results" @@ -954,9 +1013,6 @@ msgstr "Link to an existing {{trackedEntityType}}" msgid "Create new {{trackedEntityType}}" msgstr "Create new {{trackedEntityType}}" -msgid "Register" -msgstr "Register" - msgid "Back" msgstr "Back" @@ -966,6 +1022,15 @@ msgstr "events" msgid "event" msgstr "event" +msgid "Event could not be loaded. Are you sure it exists?" +msgstr "Event could not be loaded. Are you sure it exists?" + +msgid "Event could not be loaded" +msgstr "Event could not be loaded" + +msgid "Organisation unit could not be loaded" +msgstr "Organisation unit could not be loaded" + msgid "View changelog" msgstr "View changelog" @@ -991,79 +1056,14 @@ msgstr "This event doesn't have any relationships" msgid "Warnings" msgstr "Warnings" -msgid "Event could not be loaded. Are you sure it exists?" -msgstr "Event could not be loaded. Are you sure it exists?" +msgid "First Page" +msgstr "First Page" -msgid "Event could not be loaded" -msgstr "Event could not be loaded" +msgid "Previous Page" +msgstr "Previous Page" -msgid "Organisation unit could not be loaded" -msgstr "Organisation unit could not be loaded" - -msgid "tracked entity instance" -msgstr "tracked entity instance" - -msgid "All accessible" -msgstr "All accessible" - -msgid "Selected" -msgstr "Selected" - -msgid "Please select an organisation unit." -msgstr "Please select an organisation unit." - -msgid "Organisation unit scope" -msgstr "Organisation unit scope" - -msgid "Selected program" -msgstr "Selected program" - -msgid "Search {{uniqueAttrName}}" -msgstr "Search {{uniqueAttrName}}" - -msgid "Search by attributes" -msgstr "Search by attributes" - -msgid "Search {{attributeName}}" -msgstr "Search {{attributeName}}" - -msgid "Fill in at least {{count}} attribute to search" -msgid_plural "Fill in at least {{count}} attribute to search" -msgstr[0] "Fill in at least {{count}} attribute to search" -msgstr[1] "Fill in at least {{count}} attributes to search" - -msgid "Missing search criteria" -msgstr "Missing search criteria" - -msgid "Please fill in {{uniqueTEAName}} to search" -msgstr "Please fill in {{uniqueTEAName}} to search" - -msgid "Back to search" -msgstr "Back to search" - -msgid "Search form is missing. See log for details" -msgstr "Search form is missing. See log for details" - -msgid "Could not retrieve metadata. Please try again later." -msgstr "Could not retrieve metadata. Please try again later." - -msgid "The enrollment event data could not be found" -msgstr "The enrollment event data could not be found" - -msgid "Loading" -msgstr "Loading" - -msgid "An error occurred while loading the form" -msgstr "An error occurred while loading the form" - -msgid "First Page" -msgstr "First Page" - -msgid "Previous Page" -msgstr "Previous Page" - -msgid "Next Page" -msgstr "Next Page" +msgid "Next Page" +msgstr "Next Page" msgid "Possible duplicates found" msgstr "Possible duplicates found" @@ -1371,15 +1371,24 @@ msgstr "Remove mark for follow-up" msgid "Mark for follow-up" msgstr "Mark for follow-up" +msgid "An error occurred while transferring ownership" +msgstr "An error occurred while transferring ownership" + msgid "Transfer" msgstr "Transfer" -msgid "An error occurred while transferring ownership" -msgstr "An error occurred while transferring ownership" +msgid "Cancelled" +msgstr "Cancelled" msgid "Existing dates for auto-generated events will not be updated." msgstr "Existing dates for auto-generated events will not be updated." +msgid "Add coordinates" +msgstr "Add coordinates" + +msgid "Add area" +msgstr "Add area" + msgid "Latitude" msgstr "Latitude" @@ -1431,6 +1440,16 @@ msgstr "" "Choose the organisation unit to which enrollment ownership should be " "transferred." +msgid "Cannot transfer to this organisation unit" +msgstr "Cannot transfer to this organisation unit" + +msgid "" +"The enrollment date is outside the selected organisation unit's opening and " +"closing dates." +msgstr "" +"The enrollment date is outside the selected organisation unit's opening and " +"closing dates." + msgid "Enrollment date" msgstr "Enrollment date" @@ -1449,15 +1468,6 @@ msgstr "Started at{{escape}}" msgid "Owned by{{escape}}" msgstr "Owned by{{escape}}" -msgid "Cancelled" -msgstr "Cancelled" - -msgid "Add coordinates" -msgstr "Add coordinates" - -msgid "Add area" -msgstr "Add area" - msgid "Program stage not found" msgstr "Program stage not found" @@ -1617,6 +1627,9 @@ msgstr "{{trackedEntityName}} profile" msgid "Profile" msgstr "Profile" +msgid "Fix errors in the form to continue." +msgstr "Fix errors in the form to continue." + msgid "There is a problem with this form" msgstr "There is a problem with this form" @@ -1629,9 +1642,6 @@ msgstr "There was a problem saving changes" msgid "Try again or contact your system administrator for support" msgstr "Try again or contact your system administrator for support" -msgid "Fix errors in the form to continue." -msgstr "Fix errors in the form to continue." - msgid "You do not have access to delete this {{trackedEntityTypeName}}" msgstr "You do not have access to delete this {{trackedEntityTypeName}}" @@ -1668,12 +1678,36 @@ msgstr "No attributes configured" msgid "{{trackedEntityTypeName}} profile" msgstr "{{trackedEntityTypeName}} profile" +msgid "Enter details now" +msgstr "Enter details now" + +msgid "Link to an existing event" +msgstr "Link to an existing event" + +msgid "The event was successfully linked" +msgstr "The event was successfully linked" + +msgid "An error occurred while linking the event" +msgstr "An error occurred while linking the event" + +msgid "Scheduled date" +msgstr "Scheduled date" + +msgid "Report date" +msgstr "Report date" + msgid "Choose a {{linkableStageLabel}} event" msgstr "Choose a {{linkableStageLabel}} event" msgid "Select an event" msgstr "Select an event" +msgid "Please enter a date" +msgstr "Please enter a date" + +msgid "Please select a valid event" +msgstr "Please select a valid event" + msgid "{{ linkableStageLabel }} can only have one event" msgstr "{{ linkableStageLabel }} can only have one event" @@ -1692,29 +1726,104 @@ msgstr "Enter details" msgid "Linked event" msgstr "Linked event" -msgid "Enter details now" -msgstr "Enter details now" +msgid "Changelog" +msgstr "Changelog" -msgid "Link to an existing event" -msgstr "Link to an existing event" +msgid "No changes to display" +msgstr "No changes to display" -msgid "The event was successfully linked" -msgstr "The event was successfully linked" +msgid "Data item" +msgstr "Data item" -msgid "An error occurred while linking the event" -msgstr "An error occurred while linking the event" +msgid "Updated" +msgstr "Updated" -msgid "Scheduled date" -msgstr "Scheduled date" +msgid "Created" +msgstr "Created" -msgid "Report date" -msgstr "Report date" +msgid "Deleted" +msgstr "Deleted" -msgid "Please enter a date" -msgstr "Please enter a date" +msgid "Sort by date" +msgstr "Sort by date" -msgid "Please select a valid event" -msgstr "Please select a valid event" +msgid "User" +msgstr "User" + +msgid "Sort by username" +msgstr "Sort by username" + +msgid "Sort by data item" +msgstr "Sort by data item" + +msgid "Change" +msgstr "Change" + +msgid "Value" +msgstr "Value" + +msgid "File" +msgstr "File" + +msgid "Image" +msgstr "Image" + +msgid "Delete relationship" +msgstr "Delete relationship" + +msgid "Deleting the relationship is permanent and cannot be undone." +msgstr "Deleting the relationship is permanent and cannot be undone." + +msgid "Are you sure you want to delete this relationship?" +msgstr "Are you sure you want to delete this relationship?" + +msgid "Yes, delete relationship" +msgstr "Yes, delete relationship" + +msgid "An error occurred while deleting the relationship." +msgstr "An error occurred while deleting the relationship." + +msgid "Show {{ rest }} more" +msgstr "Show {{ rest }} more" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "To open this relationship, please wait until saving is complete" + +msgid "This enrollment doesn't have any relationships" +msgstr "This enrollment doesn't have any relationships" + +msgid "Type" +msgstr "Type" + +msgid "Created date" +msgstr "Created date" + +msgid "Program stage name" +msgstr "Program stage name" + +msgid "New {{trackedEntityTypeName}} relationship" +msgstr "New {{trackedEntityTypeName}} relationship" + +msgid "An error occurred while adding the relationship" +msgstr "An error occurred while adding the relationship" + +msgid "Missing implementation step" +msgstr "Missing implementation step" + +msgid "Go back without saving relationship" +msgstr "Go back without saving relationship" + +msgid "New Relationship" +msgstr "New Relationship" + +msgid "Link to an existing {{tetName}}" +msgstr "Link to an existing {{tetName}}" + +msgid "Something went wrong while loading relationships. Please try again later." +msgstr "Something went wrong while loading relationships. Please try again later." + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "{{trackedEntityTypeName}} relationships" msgid "This stage can only have one event" msgstr "This stage can only have one event" @@ -1743,9 +1852,6 @@ msgstr "Skip" msgid "To open this event, please wait until saving is complete" msgstr "To open this event, please wait until saving is complete" -msgid "Show {{ rest }} more" -msgstr "Show {{ rest }} more" - msgid "Go to full {{ eventName }}" msgstr "Go to full {{ eventName }}" @@ -1809,6 +1915,9 @@ msgstr "You do not have access to remove the link between these events" msgid "You do not have access to remove the link and delete the linked event" msgstr "You do not have access to remove the link and delete the linked event" +msgid "Scheduled" +msgstr "Scheduled" + msgid "An error occurred while loading the widget." msgstr "An error occurred while loading the widget." @@ -1819,114 +1928,15 @@ msgstr "" "This {{stageName}} event is linked to a {{linkedStageName}} event. Review " "the linked event details before entering data below" -msgid "Scheduled" -msgstr "Scheduled" - -msgid "Changelog" -msgstr "Changelog" - -msgid "No changes to display" -msgstr "No changes to display" - -msgid "Data item" -msgstr "Data item" - -msgid "Updated" -msgstr "Updated" - -msgid "Created" -msgstr "Created" - -msgid "Deleted" -msgstr "Deleted" - -msgid "Sort by date" -msgstr "Sort by date" - -msgid "User" -msgstr "User" - -msgid "Sort by username" -msgstr "Sort by username" - -msgid "Sort by data item" -msgstr "Sort by data item" - -msgid "Change" -msgstr "Change" - -msgid "Value" -msgstr "Value" - -msgid "File" -msgstr "File" - -msgid "Image" -msgstr "Image" - -msgid "New {{trackedEntityTypeName}} relationship" -msgstr "New {{trackedEntityTypeName}} relationship" - -msgid "Missing implementation step" -msgstr "Missing implementation step" - -msgid "Go back without saving relationship" -msgstr "Go back without saving relationship" - -msgid "New Relationship" -msgstr "New Relationship" - -msgid "Link to an existing {{tetName}}" -msgstr "Link to an existing {{tetName}}" - -msgid "An error occurred while adding the relationship" -msgstr "An error occurred while adding the relationship" - -msgid "Something went wrong while loading relationships. Please try again later." -msgstr "Something went wrong while loading relationships. Please try again later." - -msgid "{{trackedEntityTypeName}} relationships" -msgstr "{{trackedEntityTypeName}} relationships" - -msgid "Delete relationship" -msgstr "Delete relationship" - -msgid "Deleting the relationship is permanent and cannot be undone." -msgstr "Deleting the relationship is permanent and cannot be undone." - -msgid "Are you sure you want to delete this relationship?" -msgstr "Are you sure you want to delete this relationship?" - -msgid "Yes, delete relationship" -msgstr "Yes, delete relationship" - -msgid "An error occurred while deleting the relationship." -msgstr "An error occurred while deleting the relationship." - -msgid "To open this relationship, please wait until saving is complete" -msgstr "To open this relationship, please wait until saving is complete" - -msgid "This enrollment doesn't have any relationships" -msgstr "This enrollment doesn't have any relationships" - -msgid "Type" -msgstr "Type" - -msgid "Created date" -msgstr "Created date" - -msgid "Program stage name" -msgstr "Program stage name" - msgid "Working list could not be loaded" msgstr "Working list could not be loaded" -msgid "Download data..." -msgstr "Download data..." - msgid "an error occurred loading working lists" msgstr "an error occurred loading working lists" +msgid "Download data..." +msgstr "Download data..." + msgid "You do not have access to complete events" msgstr "You do not have access to complete events" @@ -1972,6 +1982,12 @@ msgstr "This cannot be undone." msgid "Are you sure you want to delete the selected events?" msgstr "Are you sure you want to delete the selected events?" +msgid "Working list could not be updated" +msgstr "Working list could not be updated" + +msgid "an error occurred loading the working lists" +msgstr "an error occurred loading the working lists" + msgid "Owner organisation unit" msgstr "Owner organisation unit" @@ -2112,12 +2128,6 @@ msgstr "Are you sure you want to delete?" msgid "An error occurred while deleting the records" msgstr "An error occurred while deleting the records" -msgid "Working list could not be updated" -msgstr "Working list could not be updated" - -msgid "an error occurred loading the working lists" -msgstr "an error occurred loading the working lists" - msgid "Update view" msgstr "Update view" @@ -2298,6 +2308,19 @@ msgstr "Please enter a valid time" msgid "Please enter a time" msgstr "Please enter a time" +msgid "" +"Date must be within the organisation unit's opening and closing dates " +"({{opening}} – {{closed}})" +msgstr "" +"Date must be within the organisation unit's opening and closing dates " +"({{opening}} – {{closed}})" + +msgid "Date cannot be before the organisation unit's opening date ({{opening}})" +msgstr "Date cannot be before the organisation unit's opening date ({{opening}})" + +msgid "Date cannot be after the organisation unit's closing date ({{closed}})" +msgstr "Date cannot be after the organisation unit's closing date ({{closed}})" + msgid "" "The following attribute type is not supported for searching and has been " "hidden" diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx index 81802aa393..dedcd1ac30 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentDataEntry.component.tsx @@ -4,6 +4,10 @@ import i18n from '@dhis2/d2-i18n'; import moment from 'moment'; import { type OrgUnit } from '@dhis2/rules-engine-javascript'; import { convertDateObjectToDateFormatString } from 'capture-core/utils/converters/date'; +import { + getOrgUnitOpeningCalendarMin, + getOrgUnitClosingCalendarMax, +} from 'capture-core/utils/orgUnits/getOrgUnitCalendarBounds'; import { isLangRtl } from '../../../utils/rtl'; import { DataEntry, @@ -118,14 +122,13 @@ const getEnrollmentDateSettings = () => { required: true, calendarWidth: props.formHorizontal ? 250 : 350, popupAnchorPosition: getCalendarAnchorPosition(props.formHorizontal), - calendarMax: !props.enrollmentMetadata.allowFutureEnrollmentDate ? - convertDateObjectToDateFormatString(moment()) : - undefined, + calendarMax: getOrgUnitClosingCalendarMax(props.orgUnit, !props.enrollmentMetadata.allowFutureEnrollmentDate), + calendarMin: getOrgUnitOpeningCalendarMin(props.orgUnit), calendarType: systemSettingsStore.get().calendar, dateFormat: systemSettingsStore.get().dateFormat, }), getPropName: () => 'enrolledAt', - getValidatorContainers: getEnrollmentDateValidatorContainer, + getValidatorContainers: (props: any) => getEnrollmentDateValidatorContainer(props?.orgUnit), getPassOnFieldData: () => true, getMeta: () => ({ placement: placements.TOP, diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/fieldValidators/enrollmentDate.validatorContainersGetter.ts b/src/core_modules/capture-core/components/DataEntries/Enrollment/fieldValidators/enrollmentDate.validatorContainersGetter.ts index 90cd7edda6..d15e63165a 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/fieldValidators/enrollmentDate.validatorContainersGetter.ts +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/fieldValidators/enrollmentDate.validatorContainersGetter.ts @@ -1,6 +1,12 @@ import i18n from '@dhis2/d2-i18n'; import { hasValue } from 'capture-core-utils/validators/form'; -import { isValidDate, isValidNonFutureDate } from '../../../../utils/validation/validators/form'; +import { + isValidDate, + isValidNonFutureDate, + getWithinOrgUnitDateRangeValidator, +} from '../../../../utils/validation/validators/form'; + +type OrgUnitDateRange = { openingDate?: string | null, closedDate?: string | null }; const isValidEnrollmentDate = (value: string, internalComponentError?: {error?: string, errorCode?: string}) => { if (!value) { @@ -10,7 +16,7 @@ const isValidEnrollmentDate = (value: string, internalComponentError?: {error?: return isValidDate(value, internalComponentError); }; -export const getEnrollmentDateValidatorContainer = () => { +export const getEnrollmentDateValidatorContainer = (orgUnit?: OrgUnitDateRange | null) => { const validatorContainers = [ { validator: hasValue, @@ -24,6 +30,10 @@ export const getEnrollmentDateValidatorContainer = () => { { validator: isValidNonFutureDate, errorMessage: i18n.t('A date in the future is not allowed'), }, + { + validator: getWithinOrgUnitDateRangeValidator(orgUnit), + errorMessage: '', + }, ]; return validatorContainers; }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx index 0ed8c66bc4..ed50a26c0f 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx +++ b/src/core_modules/capture-core/components/WidgetEnrollment/TransferModal/TransferModal.component.tsx @@ -6,12 +6,15 @@ import { ModalActions, ButtonStrip, Button, + NoticeBox, } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; +import { isIsoDateWithinOrgUnitRange } from 'capture-core/utils/validation/validators/form'; import type { TransferModalProps } from './TransferModal.types'; import { OrgUnitField } from './OrgUnitField'; import { useTransferValidation } from './hooks/useTransferValidation'; import { InfoBoxes } from './InfoBoxes'; +import { useCoreOrgUnit } from '../../../metadataRetrieval/coreOrgUnit'; export const TransferModal = ({ enrollment, @@ -31,6 +34,11 @@ export const TransferModal = ({ ownerOrgUnitId, }); + // The enrollment date must fall within the destination org unit's opening/closing range + const { orgUnit: destinationOrgUnit } = useCoreOrgUnit(selectedOrgUnit?.id ?? ''); + const enrollmentDateOutsideRange = !!selectedOrgUnit + && !isIsoDateWithinOrgUnitRange(enrollment.enrolledAt, destinationOrgUnit); + const handleOnUpdateOwnership = async () => { if (!selectedOrgUnit) return; await onUpdateOwnership({ @@ -65,6 +73,18 @@ export const TransferModal = ({ programAccessLevel={programAccessLevel} orgUnitScopes={orgUnitScopes} /> + + {enrollmentDateOutsideRange && ( + + {i18n.t( + "The enrollment date is outside the selected organisation unit's opening and closing dates.", + )} + + )} @@ -76,7 +96,7 @@ export const TransferModal = ({