openchs-models 1.33.65 → 1.33.67

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.
@@ -265,11 +265,10 @@ class AbstractEncounter extends _BaseEntity.default {
265
265
  return (0, _Media.findMediaObservations)(_ObservationsHolder.default.clone(this.observations), _ObservationsHolder.default.clone(this.cancelObservations));
266
266
  }
267
267
  replaceMediaObservation(originalValue, newValue, conceptUUID) {
268
- new _ObservationsHolder.default(this.observations).replaceMediaObservation(originalValue, newValue, conceptUUID);
269
- }
270
- replaceObservation(originalValue, newValue) {
271
- new _ObservationsHolder.default(this.observations).updateObservationBasedOnValue(originalValue, newValue);
272
- new _ObservationsHolder.default(this.cancelObservations).updateObservationBasedOnValue(originalValue, newValue);
268
+ // Both collections, no short-circuit — findMediaObservations enqueues from both.
269
+ const inObservations = new _ObservationsHolder.default(this.observations).replaceMediaObservation(originalValue, newValue, conceptUUID);
270
+ const inCancelObservations = new _ObservationsHolder.default(this.cancelObservations).replaceMediaObservation(originalValue, newValue, conceptUUID);
271
+ return inObservations || inCancelObservations;
273
272
  }
274
273
  getEntityTypeName() {
275
274
  return this.encounterType.name;
@@ -640,10 +640,7 @@ class Individual extends _BaseEntity.default {
640
640
  return (0, _Media.findMediaObservations)(this.observations);
641
641
  }
642
642
  replaceMediaObservation(originalValue, newValue, conceptUUID) {
643
- new _ObservationsHolder.default(this.observations).replaceMediaObservation(originalValue, newValue, conceptUUID);
644
- }
645
- replaceObservation(originalValue, newValue) {
646
- new _ObservationsHolder.default(this.observations).updateObservationBasedOnValue(originalValue, newValue);
643
+ return new _ObservationsHolder.default(this.observations).replaceMediaObservation(originalValue, newValue, conceptUUID);
647
644
  }
648
645
 
649
646
  //TODO use polymorphism to avoid if checks based on this
@@ -24,46 +24,6 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
24
24
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
25
25
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
26
26
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
27
- // Helper function to find media observation by value
28
- const findMediaObservationByValue = (observations, targetValue) => {
29
- if (!observations || !Array.isArray(observations)) return null;
30
- return _lodash.default.find(observations, obs => {
31
- // Check direct value
32
- if (obs.getValue && obs.getValue() === targetValue) return true;
33
-
34
- // Check coded values (for MultipleCodedValues)
35
- const valueJSON = obs.valueJSON;
36
- if (valueJSON && valueJSON.answer && Array.isArray(valueJSON.answer)) {
37
- return valueJSON.answer.includes(targetValue);
38
- }
39
- return false;
40
- });
41
- };
42
-
43
- // Helper function to update media value
44
- const updateMediaValue = (mediaObs, oldVal, newVal) => {
45
- if (!mediaObs) return false;
46
-
47
- // For coded values, we need to update the array content
48
- if (mediaObs.valueJSON && mediaObs.valueJSON.answer && Array.isArray(mediaObs.valueJSON.answer)) {
49
- const answerIndex = mediaObs.valueJSON.answer.indexOf(oldVal);
50
- if (answerIndex >= 0) {
51
- mediaObs.valueJSON.answer[answerIndex] = newVal;
52
- return true;
53
- }
54
- } else {
55
- // Regular value update
56
- mediaObs.setValue(mediaObs.concept.getValueWrapperFor(newVal));
57
- return true;
58
- }
59
- return false;
60
- };
61
-
62
- // Process each question group observation
63
- const processQuestionGroupObservations = (observations, oldVal, newVal) => {
64
- const mediaObs = findMediaObservationByValue(observations, oldVal);
65
- return mediaObs ? updateMediaValue(mediaObs, oldVal, newVal) : false;
66
- };
67
27
  class ObservationsHolder {
68
28
  constructor(observations) {
69
29
  this.observations = observations;
@@ -84,9 +44,6 @@ class ObservationsHolder {
84
44
  getObservation(concept) {
85
45
  return this.findObservation(concept);
86
46
  }
87
- findObservationByValue(value) {
88
- return _lodash.default.find(this.observations, observation => observation.getValue() === value);
89
- }
90
47
 
91
48
  /*
92
49
  Called from the wizard on changes done by the user for primitive fields (including Date)
@@ -431,49 +388,6 @@ class ObservationsHolder {
431
388
  }
432
389
  }
433
390
 
434
- //private
435
- updateObservationBasedOnValue(oldValue, newValue) {
436
- // Try to find at top level first
437
- const observation = this.findObservationByValue(oldValue);
438
- if (observation) {
439
- observation.setValue(observation.concept.getValueWrapperFor(newValue));
440
- return true;
441
- }
442
-
443
- // Search in nested structures if not found at top level
444
- let updated = false;
445
-
446
- // Iterate through all observations
447
- _lodash.default.forEach(this.observations, obs => {
448
- // Skip non-question group observations
449
- if (obs.concept.datatype !== _Concept.default.dataType.QuestionGroup) return;
450
- const valueWrapper = obs.getValueWrapper && obs.getValueWrapper();
451
- if (!valueWrapper) return;
452
-
453
- // Handle RepeatableQuestionGroup
454
- if (valueWrapper.isRepeatable && valueWrapper.isRepeatable()) {
455
- const allGroups = valueWrapper.getAllQuestionGroupObservations && valueWrapper.getAllQuestionGroupObservations();
456
- if (allGroups && allGroups.length) {
457
- // Check each group in the repeatable question group
458
- allGroups.forEach(group => {
459
- const groupObservations = group.getValue && group.getValue();
460
- if (processQuestionGroupObservations(groupObservations, oldValue, newValue)) {
461
- updated = true;
462
- }
463
- });
464
- }
465
- }
466
- // Handle regular QuestionGroup
467
- else {
468
- const groupObservations = valueWrapper.getValue && valueWrapper.getValue();
469
- if (processQuestionGroupObservations(groupObservations, oldValue, newValue)) {
470
- updated = true;
471
- }
472
- }
473
- });
474
- return updated;
475
- }
476
-
477
391
  // Helper method to update media value in an observation
478
392
  _updateMediaValueInObservation(observation, oldValue, newValue) {
479
393
  if (!observation) return false;
@@ -482,7 +396,13 @@ class ObservationsHolder {
482
396
 
483
397
  // Handle ImageV2 type
484
398
  if (observation.concept.datatype === _Concept.default.dataType.ImageV2) {
485
- const mediaObjects = JSON.parse(valueWrapper.getValue());
399
+ let mediaObjects;
400
+ try {
401
+ mediaObjects = JSON.parse(valueWrapper.getValue());
402
+ } catch (e) {
403
+ // A malformed value can't reference the file; one bad obs must not crash the whole scan.
404
+ return false;
405
+ }
486
406
  let updated = false;
487
407
  const newAnswers = _lodash.default.map(mediaObjects, mediaObject => {
488
408
  if (mediaObject.uri === oldValue) {
@@ -492,111 +412,62 @@ class ObservationsHolder {
492
412
  return mediaObject;
493
413
  });
494
414
  if (updated) {
495
- observation.valueJSON = new _PrimitiveValue.default(JSON.stringify(newAnswers), _Concept.default.dataType.ImageV2);
415
+ observation.valueJSON = new _PrimitiveValue.default(JSON.stringify(newAnswers), _Concept.default.dataType.ImageV2, valueWrapper.answerSource);
496
416
  return true;
497
417
  }
498
418
  }
499
419
  // Handle multiple coded values
500
420
  else if (valueWrapper.isMultipleCoded) {
501
421
  const answers = valueWrapper.getValue();
502
- const oldValueIndex = _lodash.default.indexOf(answers, oldValue);
503
- if (oldValueIndex >= 0) {
504
- const newAnswers = _lodash.default.reject(answers, answer => answer === oldValue);
505
- newAnswers.splice(oldValueIndex, 0, newValue);
506
- observation.valueJSON = new _MultipleCodedValues.default(newAnswers);
422
+ if (_lodash.default.includes(answers, oldValue)) {
423
+ const newAnswers = _lodash.default.map(answers, answer => answer === oldValue ? newValue : answer);
424
+ observation.valueJSON = new _MultipleCodedValues.default(newAnswers, valueWrapper.answerSource);
507
425
  return true;
508
426
  }
509
427
  }
510
428
  // Handle single coded value
511
429
  else if (valueWrapper.getValue() === oldValue) {
512
- observation.valueJSON = new _SingleCodedValue.default(newValue);
430
+ const newWrapper = observation.concept.getValueWrapperFor(newValue);
431
+ newWrapper.answerSource = valueWrapper.answerSource;
432
+ observation.valueJSON = newWrapper;
513
433
  return true;
514
434
  }
515
435
  return false;
516
436
  }
517
437
 
518
- // Helper to find and update media in question group
519
- _updateMediaInQuestionGroup(questionGroup, conceptUUID, oldValue, newValue) {
520
- if (!questionGroup || !questionGroup.getValue) return false;
521
- const groupObservations = questionGroup.getValue();
522
- if (!groupObservations || !Array.isArray(groupObservations)) return false;
523
-
524
- // Find media observation either by concept UUID or by value
525
- const mediaObs = _lodash.default.find(groupObservations, obs => obs.concept.uuid === conceptUUID || _Concept.default.dataType.Media.includes(obs.concept.datatype) && obs.getValue && obs.getValue() === oldValue);
526
-
527
- // Update if found
528
- return this._updateMediaValueInObservation(mediaObs, oldValue, newValue);
529
- }
530
-
531
- // Helper to find and update media in repeatable question group
532
- _updateMediaInRepeatableQuestionGroup(repeatableGroup, conceptUUID, oldValue, newValue) {
533
- if (!repeatableGroup || !repeatableGroup.getAllQuestionGroupObservations) return false;
534
- const allGroups = repeatableGroup.getAllQuestionGroupObservations();
535
- if (!allGroups || !allGroups.length) return false;
536
-
537
- // Check each group in the repeatable question group
438
+ // Replaces oldValue in every matching media child of a question-group wrapper (regular or
439
+ // repeatable) — every group/row, every child; does not stop at the first match.
440
+ _replaceMediaInGroupWrapper(wrapper, oldValue, newValue) {
441
+ const groups = wrapper.isRepeatable && wrapper.isRepeatable() ? wrapper.getAllQuestionGroupObservations ? wrapper.getAllQuestionGroupObservations() : [] : [wrapper];
538
442
  let updated = false;
539
- allGroups.forEach(group => {
540
- if (this._updateMediaInQuestionGroup(group, conceptUUID, oldValue, newValue)) {
541
- updated = true;
542
- }
443
+ _lodash.default.forEach(groups, group => {
444
+ const groupObservations = group && group.getValue && group.getValue();
445
+ if (!Array.isArray(groupObservations)) return;
446
+ _lodash.default.forEach(groupObservations, childObs => {
447
+ if (childObs.concept.isMediaConcept() && this._updateMediaValueInObservation(childObs, oldValue, newValue)) {
448
+ updated = true;
449
+ }
450
+ });
543
451
  });
544
452
  return updated;
545
453
  }
546
454
 
547
- // Helper to process a question group (regular or repeatable) and update media within it
548
- _processQuestionGroupWrapper(wrapper, conceptUUID, oldValue, newValue, sourceName) {
549
- if (!wrapper) return false;
550
-
551
- // Determine if it's a repeatable question group or regular question group
552
- const isRepeatable = wrapper.isRepeatable && wrapper.isRepeatable();
553
-
554
- // Process the appropriate group type
555
- return isRepeatable ? this._updateMediaInRepeatableQuestionGroup(wrapper, conceptUUID, oldValue, newValue) : this._updateMediaInQuestionGroup(wrapper, conceptUUID, oldValue, newValue);
556
- }
455
+ // Replaces oldValue with newValue in EVERY observation referencing it top-level media
456
+ // observations and those inside (repeatable) question groups. The same media value can be
457
+ // referenced by multiple concepts with different select types (e.g. a rule-copied read-only
458
+ // display element), so the scan is value-driven; conceptUUID is retained only for caller
459
+ // compatibility. Returns true if at least one observation was updated.
557
460
  replaceMediaObservation(oldValue, newValue, conceptUUID) {
558
- console.log(`[INFO] Replacing media: ${oldValue} → ${newValue}`);
559
-
560
- // Since conceptUUID is always provided in practice, optimize for that case first
561
- // Try to find direct top-level observation with matching concept UUID
562
- const directObservation = _lodash.default.find(this.observations, obs => obs.concept.uuid === conceptUUID);
563
- if (directObservation) {
564
- // Direct media observation
565
- if (_Concept.default.dataType.Media.includes(directObservation.concept.datatype)) {
566
- if (this._updateMediaValueInObservation(directObservation, oldValue, newValue)) {
567
- console.log(`[INFO] Updated media in direct observation`);
568
- return true;
569
- }
570
- }
571
- // Question Group containing the media
572
- else if (directObservation.concept.datatype === _Concept.default.dataType.QuestionGroup) {
573
- const valueWrapper = directObservation.getValueWrapper();
574
- if (this._processQuestionGroupWrapper(valueWrapper, conceptUUID, oldValue, newValue)) {
575
- console.log(`[INFO] Updated media in question group`);
576
- return true;
577
- }
578
- }
579
- }
580
-
581
- // Check nested structures (this works both with and without conceptUUID)
582
461
  let updated = false;
583
462
  _lodash.default.forEach(this.observations, obs => {
584
- // Only process question groups
585
- if (obs.concept.datatype !== _Concept.default.dataType.QuestionGroup) return;
586
- const valueWrapper = obs.getValueWrapper && obs.getValueWrapper();
587
- if (!valueWrapper) return;
588
- if (this._processQuestionGroupWrapper(valueWrapper, conceptUUID, oldValue, newValue)) {
589
- updated = true;
463
+ if (obs.concept.isMediaConcept()) {
464
+ if (this._updateMediaValueInObservation(obs, oldValue, newValue)) updated = true;
465
+ } else if (obs.concept.datatype === _Concept.default.dataType.QuestionGroup) {
466
+ const wrapper = obs.getValueWrapper && obs.getValueWrapper();
467
+ if (wrapper && this._replaceMediaInGroupWrapper(wrapper, oldValue, newValue)) updated = true;
590
468
  }
591
469
  });
592
- if (updated) {
593
- console.log(`[INFO] Updated media in nested structure`);
594
- return true;
595
- }
596
-
597
- // As a fallback, use value-based replacement if we couldn't find it by concept
598
- // This ensures backward compatibility and handles edge cases
599
- return this.updateObservationBasedOnValue(oldValue, newValue);
470
+ return updated;
600
471
  }
601
472
  toString(I18n) {
602
473
  let display = "";
@@ -455,11 +455,10 @@ class ProgramEnrolment extends _BaseEntity.default {
455
455
  return (0, _Media.findMediaObservations)(_ObservationsHolder.default.clone(this.observations), _ObservationsHolder.default.clone(this.programExitObservations));
456
456
  }
457
457
  replaceMediaObservation(originalValue, newValue, conceptUUID) {
458
- new _ObservationsHolder.default(this.observations).replaceMediaObservation(originalValue, newValue, conceptUUID);
459
- }
460
- replaceObservation(originalValue, newValue) {
461
- new _ObservationsHolder.default(this.observations).updateObservationBasedOnValue(originalValue, newValue);
462
- new _ObservationsHolder.default(this.programExitObservations).updateObservationBasedOnValue(originalValue, newValue);
458
+ // Both collections, no short-circuit — findMediaObservations enqueues from both.
459
+ const inObservations = new _ObservationsHolder.default(this.observations).replaceMediaObservation(originalValue, newValue, conceptUUID);
460
+ const inExitObservations = new _ObservationsHolder.default(this.programExitObservations).replaceMediaObservation(originalValue, newValue, conceptUUID);
461
+ return inObservations || inExitObservations;
463
462
  }
464
463
  getName() {
465
464
  return "ProgramEnrolment";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openchs-models",
3
3
  "description": "OpenCHS data model to be used by front end clients",
4
- "version": "1.33.65",
4
+ "version": "1.33.67",
5
5
  "private": false,
6
6
  "repository": {
7
7
  "type": "git",