@openmrs/esm-form-engine-lib 4.2.1-pre.2435 → 4.2.1-pre.2438

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import { init, teardown } from "./lifecycle.js";
8
8
  import { isEmpty, useFormJson } from "./index.js";
9
9
  import { formEngineAppName } from "./globals.js";
10
10
  import { reportError } from "./utils/error-utils.js";
11
+ import { getDateWithinVisitWindow } from "./utils/common-utils.js";
11
12
  import { useFormCollapse } from "./hooks/useFormCollapse.js";
12
13
  import { useFormWorkspaceSize } from "./hooks/useFormWorkspaceSize.js";
13
14
  import { usePageObserver } from "./components/sidebar/usePageObserver.js";
@@ -22,9 +23,15 @@ const FormEngine = ({ formJson, patientUUID, formUUID, encounterUUID, visit, for
22
23
  const { t } = useTranslation();
23
24
  const session = useSession();
24
25
  const ref = useRef(null);
26
+ const rawSessionDate = useRef(new Date());
27
+ // Recompute when the visit bounds arrive or change; the visit prop may not be
28
+ // fully loaded when the form mounts.
25
29
  const sessionDate = useMemo(()=>{
26
- return new Date();
27
- }, []);
30
+ return getDateWithinVisitWindow(rawSessionDate.current, visit);
31
+ }, [
32
+ visit?.startDatetime,
33
+ visit?.stopDatetime
34
+ ]);
28
35
  const workspaceSize = useFormWorkspaceSize(ref);
29
36
  const { patient, isLoadingPatient } = usePatientData(patientUUID);
30
37
  const [isLoadingDependencies, setIsLoadingDependencies] = useState(false);
@@ -134,11 +134,15 @@ export function saveAttachments(fields, encounter, abortController) {
134
134
  return Promise.all(allPromises);
135
135
  }
136
136
  export function getMutableSessionProps(context) {
137
- const { formFields, location, currentProvider, customDependencies, domainObjectValue: encounter } = context;
137
+ const { formFields, location, currentProvider, customDependencies, sessionDate, domainObjectValue: encounter } = context;
138
138
  const { defaultEncounterRole } = customDependencies;
139
139
  const encounterRole = formFields.find((field)=>field.type === 'encounterRole')?.meta.submission?.newValue || defaultEncounterRole?.uuid;
140
140
  const encounterProvider = formFields.find((field)=>field.type === 'encounterProvider')?.meta.submission?.newValue || currentProvider.uuid;
141
- const encounterDate = formFields.find((field)=>field.type === 'encounterDatetime')?.meta.submission?.newValue;
141
+ const explicitEncounterDate = formFields.find((field)=>field.type === 'encounterDatetime')?.meta.submission?.newValue;
142
+ // Always submit an explicit datetime for new encounters; if it is omitted, the backend
143
+ // defaults it to "now", which fails validation when saving into a past (stopped) visit.
144
+ // `sessionDate` is already constrained to the visit window.
145
+ const encounterDate = explicitEncounterDate ?? (encounter?.encounterDatetime ? new Date(encounter.encounterDatetime) : sessionDate);
142
146
  const encounterLocation = formFields.find((field)=>field.type === 'encounterLocation')?.meta.submission?.newValue || encounter?.location?.uuid || location.uuid;
143
147
  return {
144
148
  encounterRole: encounterRole,
@@ -1 +1 @@
1
- {"version":3,"file":"form-engine.component.d.ts","sourceRoot":"","sources":["../../src/form-engine.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,EAAc,KAAK,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAUhE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAQtF,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,6BAA6B,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AA+KD,iBAAS,aAAa,CAAC,KAAK,EAAE,eAAe,qBAM5C;AAED,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"form-engine.component.d.ts","sourceRoot":"","sources":["../../src/form-engine.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,EAAc,KAAK,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAWhE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAQtF,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,6BAA6B,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAkLD,iBAAS,aAAa,CAAC,KAAK,EAAE,eAAe,qBAM5C;AAED,eAAe,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"encounter-processor-helper.d.ts","sourceRoot":"","sources":["../../../../src/processors/encounter/encounter-processor-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAQrE,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,gBAAgB,EACzB,aAAa,EAAE,IAAI,EACnB,aAAa,EAAE,MAAM,EACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,oBA6DjB;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,iBAAiB,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAI7G;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,4DAI7F;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAI9E;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,4DAIxF;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EAAE,EACnB,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,sBAAsB,EAAE,KAAK,CAAC,cAAc,CAAC,GAC5C,KAAK,CAAC,qBAAqB,CAAC,CA+B9B;AAED,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,qBAAqB,EAAE,4DAG3E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,oEAcjH;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB;mBAa5B,MAAM;uBACE,MAAM;mBACd,IAAI;uBACI,MAAM;EAEjD;AA4FD,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,SAAS,OAetE;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,gBAAgB,EAC3B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,SAAS,EAAE,CAAC,CAwEtB"}
1
+ {"version":3,"file":"encounter-processor-helper.d.ts","sourceRoot":"","sources":["../../../../src/processors/encounter/encounter-processor-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAQrE,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,gBAAgB,EACzB,aAAa,EAAE,IAAI,EACnB,aAAa,EAAE,MAAM,EACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,oBA6DjB;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,iBAAiB,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAI7G;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,4DAI7F;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,EAAE,CAI9E;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,4DAIxF;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EAAE,EACnB,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,sBAAsB,EAAE,KAAK,CAAC,cAAc,CAAC,GAC5C,KAAK,CAAC,qBAAqB,CAAC,CA+B9B;AAED,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,qBAAqB,EAAE,4DAG3E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,oEAcjH;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB;mBA0B5B,MAAM;uBACE,MAAM;mBACd,IAAI;uBACI,MAAM;EAEjD;AA4FD,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,SAAS,OAetE;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,gBAAgB,EAC3B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,SAAS,EAAE,CAAC,CAwEtB"}
@@ -1,4 +1,5 @@
1
1
  import { type FormSchema, type FormField, type OpenmrsObs, type RenderType } from '../types';
2
+ import { type Visit } from '@openmrs/esm-framework';
2
3
  export declare function flattenObsList(obsList: OpenmrsObs[]): OpenmrsObs[];
3
4
  export declare function hasRendering(field: FormField, rendering: RenderType): boolean;
4
5
  export declare function clearSubmission(field: FormField): void;
@@ -6,6 +7,13 @@ export declare function gracefullySetSubmission(field: FormField, newValue: any,
6
7
  export declare function hasSubmission(field: FormField): boolean;
7
8
  export declare function isViewMode(sessionMode: string): sessionMode is "view" | "embedded-view";
8
9
  export declare function parseToLocalDateTime(dateString: string): Date;
10
+ /**
11
+ * Returns `date` if it falls within the visit's start/stop window, otherwise the
12
+ * visit's start datetime. This keeps default encounter datetimes valid when filling
13
+ * forms against a past (stopped) visit via retrospective data entry; the backend
14
+ * rejects encounters dated outside the visit window.
15
+ */
16
+ export declare function getDateWithinVisitWindow(date: Date, visit?: Visit): Date;
9
17
  export declare function formatDateAsDisplayString(field: FormField, date: Date): string;
10
18
  /**
11
19
  * Creates a new copy of `formJson` with updated references at the page and section levels.
@@ -1 +1 @@
1
- {"version":3,"file":"common-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/common-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAI7F,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAgBlE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,WAEnE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,QAS/C;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,OAWxF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,WAE7C;AAED,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,2CAE7C;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAgB7D;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,UAQrE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,UAAU;;;;;;;;;;;;;;;eA9E1D,CAAH;cAGD,CAAF;;;;;;;;gBAQmB,CAAC;4BAA0B,CAAC;;;;EAyE9C;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,UAExD"}
1
+ {"version":3,"file":"common-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/common-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAE7F,OAAO,EAAsC,KAAK,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAExF,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAgBlE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,WAEnE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,QAS/C;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,OAWxF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,WAE7C;AAED,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,2CAE7C;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAgB7D;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAUxE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,UAQrE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,UAAU;;;;;;;;;;;;;;;eAjG5C,CAAC;cACd,CAAC;;;;;;;;gBAWD,CAAC;4BAA0B,CAAC;;;;EA2FlC;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,UAExD"}
@@ -69,6 +69,22 @@ export function parseToLocalDateTime(dateString) {
69
69
  }
70
70
  return dateObj;
71
71
  }
72
+ /**
73
+ * Returns `date` if it falls within the visit's start/stop window, otherwise the
74
+ * visit's start datetime. This keeps default encounter datetimes valid when filling
75
+ * forms against a past (stopped) visit via retrospective data entry; the backend
76
+ * rejects encounters dated outside the visit window.
77
+ */ export function getDateWithinVisitWindow(date, visit) {
78
+ if (!visit) {
79
+ return date;
80
+ }
81
+ const visitStart = visit.startDatetime ? new Date(visit.startDatetime) : null;
82
+ const visitStop = visit.stopDatetime ? new Date(visit.stopDatetime) : null;
83
+ if (visitStart && date < visitStart || visitStop && date > visitStop) {
84
+ return visitStart ?? visitStop;
85
+ }
86
+ return date;
87
+ }
72
88
  export function formatDateAsDisplayString(field, date) {
73
89
  const options = {
74
90
  noToday: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-form-engine-lib",
3
- "version": "4.2.1-pre.2435",
3
+ "version": "4.2.1-pre.2438",
4
4
  "description": "React Form Engine for O3",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -8,6 +8,7 @@ import { init, teardown } from './lifecycle';
8
8
  import { isEmpty, useFormJson } from '.';
9
9
  import { formEngineAppName } from './globals';
10
10
  import { reportError } from './utils/error-utils';
11
+ import { getDateWithinVisitWindow } from './utils/common-utils';
11
12
  import { useFormCollapse } from './hooks/useFormCollapse';
12
13
  import { useFormWorkspaceSize } from './hooks/useFormWorkspaceSize';
13
14
  import { usePageObserver } from './components/sidebar/usePageObserver';
@@ -58,9 +59,12 @@ const FormEngine = ({
58
59
  const { t } = useTranslation();
59
60
  const session = useSession();
60
61
  const ref = useRef(null);
62
+ const rawSessionDate = useRef(new Date());
63
+ // Recompute when the visit bounds arrive or change; the visit prop may not be
64
+ // fully loaded when the form mounts.
61
65
  const sessionDate = useMemo(() => {
62
- return new Date();
63
- }, []);
66
+ return getDateWithinVisitWindow(rawSessionDate.current, visit);
67
+ }, [visit?.startDatetime, visit?.stopDatetime]);
64
68
  const workspaceSize = useFormWorkspaceSize(ref);
65
69
  const { patient, isLoadingPatient } = usePatientData(patientUUID);
66
70
  const [isLoadingDependencies, setIsLoadingDependencies] = useState(false);
@@ -172,13 +172,26 @@ export function saveAttachments(fields: FormField[], encounter: OpenmrsEncounter
172
172
  }
173
173
 
174
174
  export function getMutableSessionProps(context: FormContextProps) {
175
- const { formFields, location, currentProvider, customDependencies, domainObjectValue: encounter } = context;
175
+ const {
176
+ formFields,
177
+ location,
178
+ currentProvider,
179
+ customDependencies,
180
+ sessionDate,
181
+ domainObjectValue: encounter,
182
+ } = context;
176
183
  const { defaultEncounterRole } = customDependencies;
177
184
  const encounterRole =
178
185
  formFields.find((field) => field.type === 'encounterRole')?.meta.submission?.newValue || defaultEncounterRole?.uuid;
179
186
  const encounterProvider =
180
187
  formFields.find((field) => field.type === 'encounterProvider')?.meta.submission?.newValue || currentProvider.uuid;
181
- const encounterDate = formFields.find((field) => field.type === 'encounterDatetime')?.meta.submission?.newValue;
188
+ const explicitEncounterDate = formFields.find((field) => field.type === 'encounterDatetime')?.meta.submission
189
+ ?.newValue;
190
+ // Always submit an explicit datetime for new encounters; if it is omitted, the backend
191
+ // defaults it to "now", which fails validation when saving into a past (stopped) visit.
192
+ // `sessionDate` is already constrained to the visit window.
193
+ const encounterDate =
194
+ explicitEncounterDate ?? (encounter?.encounterDatetime ? new Date(encounter.encounterDatetime) : sessionDate);
182
195
  const encounterLocation =
183
196
  formFields.find((field) => field.type === 'encounterLocation')?.meta.submission?.newValue ||
184
197
  encounter?.location?.uuid ||
@@ -1,7 +1,7 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { type FormSchema, type FormField, type OpenmrsObs, type RenderType } from '../types';
3
3
  import { isEmpty } from '../validators/form-validator';
4
- import { formatDate, type FormatDateOptions } from '@openmrs/esm-framework';
4
+ import { formatDate, type FormatDateOptions, type Visit } from '@openmrs/esm-framework';
5
5
 
6
6
  export function flattenObsList(obsList: OpenmrsObs[]): OpenmrsObs[] {
7
7
  const flattenedList: OpenmrsObs[] = [];
@@ -75,6 +75,24 @@ export function parseToLocalDateTime(dateString: string): Date {
75
75
  return dateObj;
76
76
  }
77
77
 
78
+ /**
79
+ * Returns `date` if it falls within the visit's start/stop window, otherwise the
80
+ * visit's start datetime. This keeps default encounter datetimes valid when filling
81
+ * forms against a past (stopped) visit via retrospective data entry; the backend
82
+ * rejects encounters dated outside the visit window.
83
+ */
84
+ export function getDateWithinVisitWindow(date: Date, visit?: Visit): Date {
85
+ if (!visit) {
86
+ return date;
87
+ }
88
+ const visitStart = visit.startDatetime ? new Date(visit.startDatetime) : null;
89
+ const visitStop = visit.stopDatetime ? new Date(visit.stopDatetime) : null;
90
+ if ((visitStart && date < visitStart) || (visitStop && date > visitStop)) {
91
+ return visitStart ?? visitStop;
92
+ }
93
+ return date;
94
+ }
95
+
78
96
  export function formatDateAsDisplayString(field: FormField, date: Date) {
79
97
  const options: Partial<FormatDateOptions> = { noToday: true };
80
98
  if (field.datePickerFormat === 'calendar') {