@acorex-platform/module-workplacesafety-management-client 0.0.28 → 0.0.32
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.
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-acorex-platform-module-workplacesafety-management-client-isnVDChu.mjs +549 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-acorex-platform-module-workplacesafety-management-client-isnVDChu.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-contracts.mjs +739 -806
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-CZFCMX6R.mjs +93 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-CZFCMX6R.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-C-qrrqOV.mjs → acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.component-D7hofHR0.mjs} +8 -93
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.component-D7hofHR0.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-BBhH7KP1.mjs → acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-tMrUDnCb.mjs} +48 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-tMrUDnCb.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs → acorex-platform-module-workplacesafety-management-client-index-yxxC7i6A.mjs} +391 -92
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-index-yxxC7i6A.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer-widget-column.component-D2Mmow12.mjs +42 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer-widget-column.component-D2Mmow12.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer-widget-edit.component-CQtVhCgw.mjs +101 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer-widget-edit.component-CQtVhCgw.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer.service-YS8FHiRu.mjs +221 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer.service-YS8FHiRu.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer.util-2Kl3_3sg.mjs +61 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-ppe-item-viewer.util-2Kl3_3sg.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-C2X3VpEt.mjs → acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-Dd9xdo_y.mjs} +3 -7
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-Dd9xdo_y.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-safety-plan-preview-modal.component-C7IojUT9.mjs +557 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-safety-plan-preview-modal.component-C7IojUT9.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs → acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BlPzzCc2.mjs} +13 -25
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BlPzzCc2.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-workplacesafety-management-client.mjs +1 -460
- package/fesm2022/acorex-platform-module-workplacesafety-management-client.mjs.map +1 -1
- package/package.json +2 -2
- package/types/acorex-platform-module-workplacesafety-management-client-contracts.d.ts +89 -140
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-C-qrrqOV.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-BBhH7KP1.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-ppe-rule-status.provider-DtqzcWh0.mjs +0 -42
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-ppe-rule-status.provider-DtqzcWh0.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-C2X3VpEt.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs.map +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createRandomUuid } from '@acorex-platform/framework-shared/core';
|
|
2
|
+
|
|
1
3
|
//#region ---- GHS symbol options ----
|
|
2
4
|
/** Asset base path for GHS pictograms (served from demo app assets). */
|
|
3
5
|
const GHS_IMAGE_BASE = '/assets/workplacesafety-management/ghs';
|
|
@@ -113,13 +115,6 @@ const AXMWorkplaceSafetyManagementPermissionKeys = {
|
|
|
113
115
|
Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
114
116
|
Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
115
117
|
},
|
|
116
|
-
HazardRiskPpeRule: {
|
|
117
|
-
Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
118
|
-
View: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
119
|
-
Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
120
|
-
Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
121
|
-
Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
|
|
122
|
-
},
|
|
123
118
|
ControlHierarchyLevels: {
|
|
124
119
|
Management: AXPSafetyRiskPermissionKeys.Setup,
|
|
125
120
|
},
|
|
@@ -237,13 +232,6 @@ const RootConfig = {
|
|
|
237
232
|
titlePlural: `@${config.i18n}:hazard-risks.entities.hazard-risk.plural`,
|
|
238
233
|
icon: 'fa-light fa-shield-exclamation',
|
|
239
234
|
},
|
|
240
|
-
hazardRiskPpeRule: {
|
|
241
|
-
name: 'HazardRiskPpeRule',
|
|
242
|
-
source: `${config.module}.HazardRiskPpeRule`,
|
|
243
|
-
title: `@${config.i18n}:hazard-risk-ppe-rules.entities.hazard-risk-ppe-rule.title`,
|
|
244
|
-
titlePlural: `@${config.i18n}:hazard-risk-ppe-rules.entities.hazard-risk-ppe-rule.plural`,
|
|
245
|
-
icon: 'fa-light fa-user-helmet-safety',
|
|
246
|
-
},
|
|
247
235
|
controlHierarchyLevels: {
|
|
248
236
|
name: 'ControlHierarchyLevels',
|
|
249
237
|
source: `${config.module}.ControlHierarchyLevels`,
|
|
@@ -414,7 +402,7 @@ const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE = {
|
|
|
414
402
|
HazardRisk: `${moduleName}.${RootConfig.entities.hazardRisk.name}`,
|
|
415
403
|
Ppe: `${moduleName}.${RootConfig.entities.ppeItem.name}`,
|
|
416
404
|
PpeHazards: `${moduleName}.${RootConfig.entities.hazardPpeRule.name}`,
|
|
417
|
-
HazardRiskPpe:
|
|
405
|
+
HazardRiskPpe: undefined,
|
|
418
406
|
Control: `${moduleName}.${RootConfig.entities.controlMeasure.name}`,
|
|
419
407
|
HierarchyLevel: `${moduleName}.${RootConfig.entities.controlHierarchyLevels.name}`,
|
|
420
408
|
RiskMatrix: 'RiskManagement.RiskMatrixCell',
|
|
@@ -529,897 +517,845 @@ const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_EXPRESSION = buildSectionTypeEntityEx
|
|
|
529
517
|
const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_EXPRESSION_SIBLING = buildSectionTypeEntityExpressionWithSnapshotFallback('sectionType.id', 'sectionType.itemEntityName');
|
|
530
518
|
//#endregion
|
|
531
519
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
PpeHazards: {
|
|
541
|
-
kind: 'parentFilter',
|
|
542
|
-
parentSectionTypeKey: 'Hazard',
|
|
543
|
-
filterField: 'hazardId',
|
|
544
|
-
},
|
|
545
|
-
HazardRiskPpe: {
|
|
546
|
-
kind: 'parentFilter',
|
|
547
|
-
parentSectionTypeKey: 'HazardRisk',
|
|
548
|
-
filterField: 'hazardRiskId',
|
|
549
|
-
},
|
|
550
|
-
RiskMatrix: {
|
|
551
|
-
kind: 'derivedFromParent',
|
|
552
|
-
parentSectionTypeKey: 'HazardRisk',
|
|
553
|
-
resolveField: 'riskMatrixId',
|
|
554
|
-
filterField: 'id',
|
|
555
|
-
},
|
|
556
|
-
Control: {
|
|
557
|
-
kind: 'derivedFromParent',
|
|
558
|
-
parentSectionTypeKey: 'HazardRisk',
|
|
559
|
-
resolveField: 'controlMeasureIds',
|
|
560
|
-
filterField: 'id',
|
|
561
|
-
},
|
|
562
|
-
};
|
|
563
|
-
/** Section types cleared when upstream hazard selections change. */
|
|
564
|
-
const WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD = [
|
|
565
|
-
'HazardRisk',
|
|
566
|
-
'PpeHazards',
|
|
567
|
-
'HazardRiskPpe',
|
|
568
|
-
'RiskMatrix',
|
|
569
|
-
'Control',
|
|
570
|
-
];
|
|
571
|
-
/** Section types cleared when upstream hazard-risk selections change. */
|
|
572
|
-
const WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD_RISK = [
|
|
573
|
-
'HazardRiskPpe',
|
|
574
|
-
'RiskMatrix',
|
|
575
|
-
'Control',
|
|
576
|
-
];
|
|
577
|
-
//#endregion
|
|
578
|
-
//#region ---- Lookup helpers ----
|
|
579
|
-
const PARENT_HAZARD_IDS_CONTEXT_KEY = 'parentHazardIds';
|
|
580
|
-
const PARENT_HAZARD_RISK_IDS_CONTEXT_KEY = 'parentHazardRiskIds';
|
|
581
|
-
/** Expose paths when a section type is selected (shared by entity repeater and builder dialog). */
|
|
582
|
-
const WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_EXPOSE = [
|
|
583
|
-
{ source: 'id', target: 'sectionType.id' },
|
|
584
|
-
{ source: 'title', target: 'sectionType.title' },
|
|
585
|
-
{ source: 'code', target: 'sectionType.code' },
|
|
586
|
-
{ source: 'description', target: 'sectionType.description' },
|
|
587
|
-
{ source: 'isRepeatable', target: 'sectionType.isRepeatable' },
|
|
588
|
-
];
|
|
589
|
-
/** Auto-fill section description when section type is selected. */
|
|
590
|
-
const WORKPLACE_SAFETY_SECTION_TYPE_DESCRIPTION_SYNC_TRIGGERS = [
|
|
591
|
-
{
|
|
592
|
-
event: "{{ events.context('sectionTypeId') }}",
|
|
593
|
-
action: "{{ const items = widget.output('selectedItems'); const selectedDescription = Array.isArray(items) ? items[0]?.description : undefined; const d = selectedDescription || context.eval('sectionType.description') || ''; context.set('sectionTypeDescription', d); const descriptionWidget = widget.find('sectionTypeDescription'); if (descriptionWidget) { descriptionWidget.setValue(d); } }}",
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
event: "{{ events.context('sectionType.description') }}",
|
|
597
|
-
action: "{{ const d = context.eval('sectionType.description') || ''; context.set('sectionTypeDescription', d); const descriptionWidget = widget.find('sectionTypeDescription'); if (descriptionWidget) { descriptionWidget.setValue(d); } }}",
|
|
598
|
-
},
|
|
599
|
-
];
|
|
600
|
-
/** Auto-fill section title when section type is selected. */
|
|
601
|
-
const WORKPLACE_SAFETY_SECTION_TYPE_TITLE_SYNC_TRIGGERS = [
|
|
602
|
-
{
|
|
603
|
-
event: "{{ events.context('sectionTypeId') }}",
|
|
604
|
-
action: "{{ const items = widget.output('selectedItems'); const selectedTitle = Array.isArray(items) ? items[0]?.title : undefined; const t = selectedTitle || context.eval('sectionType.title') || ''; if (t) { context.set('title', t); const titleWidget = widget.find('title'); if (titleWidget) { titleWidget.setValue(t); } } }}",
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
event: "{{ events.context('sectionType.title') }}",
|
|
608
|
-
action: "{{ const t = context.eval('sectionType.title') || ''; if (t) { context.set('title', t); const titleWidget = widget.find('title'); if (titleWidget) { titleWidget.setValue(t); } } }}",
|
|
609
|
-
},
|
|
610
|
-
];
|
|
611
|
-
/** Shared lookup widget options for section type pickers (builder dialog and entity forms). */
|
|
612
|
-
const WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_WIDGET_OPTIONS = {
|
|
613
|
-
look: 'select',
|
|
614
|
-
allowSearch: true,
|
|
615
|
-
textField: 'code',
|
|
616
|
-
textTemplate: '{code} — {description}',
|
|
617
|
-
valueField: 'id',
|
|
618
|
-
columns: ['code', 'title', 'description'],
|
|
619
|
-
expose: WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_EXPOSE,
|
|
620
|
-
triggers: [
|
|
621
|
-
...WORKPLACE_SAFETY_SECTION_TYPE_TITLE_SYNC_TRIGGERS,
|
|
622
|
-
...WORKPLACE_SAFETY_SECTION_TYPE_DESCRIPTION_SYNC_TRIGGERS,
|
|
623
|
-
],
|
|
624
|
-
};
|
|
625
|
-
//#endregion
|
|
626
|
-
//#region ---- Resolver API ----
|
|
627
|
-
function getSectionTypeDependency(sectionTypeKey) {
|
|
628
|
-
if (!sectionTypeKey) {
|
|
629
|
-
return undefined;
|
|
520
|
+
/**
|
|
521
|
+
* Shared helpers for Safety Plan Section type identity (repeatable vs one-off).
|
|
522
|
+
* Leaf module — imports only other contract leaves (no client/server layers).
|
|
523
|
+
*/
|
|
524
|
+
//#region ---- Helpers ----
|
|
525
|
+
function resolveRefId$1(raw) {
|
|
526
|
+
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
527
|
+
return raw.trim();
|
|
630
528
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
function sectionTypeKeyFromSection(section) {
|
|
634
|
-
const sectionTypeId = section.sectionTypeId || section.sectionType?.id;
|
|
635
|
-
const key = resolveSectionTypeKey(sectionTypeId, section.sectionType);
|
|
636
|
-
return key;
|
|
637
|
-
}
|
|
638
|
-
function collectSectionItemIds(section, sectionTypeKey) {
|
|
639
|
-
if (!section?.items?.length || sectionTypeKeyFromSection(section) !== sectionTypeKey) {
|
|
640
|
-
return [];
|
|
529
|
+
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
|
530
|
+
return String(raw);
|
|
641
531
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
ids.add(id);
|
|
647
|
-
}
|
|
532
|
+
if (raw && typeof raw === 'object') {
|
|
533
|
+
const id = raw.id;
|
|
534
|
+
if (typeof id === 'string' && id.trim().length > 0) {
|
|
535
|
+
return id.trim();
|
|
648
536
|
}
|
|
649
537
|
}
|
|
650
|
-
return
|
|
651
|
-
}
|
|
652
|
-
/** Collects item IDs from sections up to `sectionIndex` whose type matches `parentSectionTypeKey`. */
|
|
653
|
-
function resolveParentItemIds(sections, sectionIndex, parentSectionTypeKey) {
|
|
654
|
-
const ids = new Set();
|
|
655
|
-
const lastIndex = Math.min(sectionIndex, sections.length - 1);
|
|
656
|
-
for (let index = 0; index <= lastIndex; index += 1) {
|
|
657
|
-
collectSectionItemIds(sections[index], parentSectionTypeKey).forEach((id) => ids.add(id));
|
|
658
|
-
}
|
|
659
|
-
return [...ids];
|
|
660
|
-
}
|
|
661
|
-
/** Collects hazard IDs from earlier sections and Hazard rows in the current section. */
|
|
662
|
-
function resolveParentHazardIds(sections, sectionIndex, parentSectionTypeKey = 'Hazard') {
|
|
663
|
-
return resolveParentItemIds(sections, sectionIndex, parentSectionTypeKey);
|
|
664
|
-
}
|
|
665
|
-
/** Collects hazard-risk IDs from prior HazardRisk sections up to `sectionIndex`. */
|
|
666
|
-
function resolveParentHazardRiskIds(sections, sectionIndex) {
|
|
667
|
-
return resolveParentItemIds(sections, sectionIndex, 'HazardRisk');
|
|
668
|
-
}
|
|
669
|
-
function parentItemSignature(sections, sectionIndex, parentSectionTypeKey) {
|
|
670
|
-
return resolveParentItemIds(sections, sectionIndex, parentSectionTypeKey).sort().join('|');
|
|
538
|
+
return '';
|
|
671
539
|
}
|
|
672
|
-
|
|
673
|
-
|
|
540
|
+
/**
|
|
541
|
+
* True when this section type may appear more than once in a Safety Plan.
|
|
542
|
+
* Checks (in order): snapshot flag, explicit allow-list, built-in section type meta.
|
|
543
|
+
*/
|
|
544
|
+
function isSafetyPlanSectionTypeRepeatable(sectionTypeId, options) {
|
|
545
|
+
const id = resolveRefId$1(sectionTypeId);
|
|
546
|
+
if (!id) {
|
|
674
547
|
return false;
|
|
675
548
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
return false;
|
|
549
|
+
if (options?.sectionType?.isRepeatable === true) {
|
|
550
|
+
return true;
|
|
679
551
|
}
|
|
680
|
-
|
|
681
|
-
|
|
552
|
+
const allowList = new Set((options?.repeatableSectionTypeIds ?? []).map((entry) => resolveRefId$1(entry)).filter(Boolean));
|
|
553
|
+
if (allowList.has(id)) {
|
|
554
|
+
return true;
|
|
682
555
|
}
|
|
683
|
-
return
|
|
556
|
+
return WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID[id]?.isRepeatable === true;
|
|
684
557
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
558
|
+
/** Built-in section type ids that are always treated as repeatable. */
|
|
559
|
+
function collectBuiltInRepeatableSectionTypeIds() {
|
|
560
|
+
return Object.entries(WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID)
|
|
561
|
+
.filter(([, meta]) => meta.isRepeatable === true)
|
|
562
|
+
.map(([id]) => id);
|
|
690
563
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
564
|
+
//#endregion
|
|
565
|
+
|
|
566
|
+
//#region ---- Imports ----
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region ---- Display helpers ----
|
|
569
|
+
/** Coerces a stored string or locale map into a single display string. */
|
|
570
|
+
function asSafetyPlanDisplayText(value) {
|
|
571
|
+
if (typeof value === 'string') {
|
|
572
|
+
return value.trim();
|
|
697
573
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
changeIndex = Math.min(changeIndex, index);
|
|
706
|
-
hazardChanged = true;
|
|
574
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
575
|
+
const rec = value;
|
|
576
|
+
for (const locale of ['en-US', 'en-AU', 'en', 'fa-IR']) {
|
|
577
|
+
const part = rec[locale];
|
|
578
|
+
if (typeof part === 'string' && part.trim()) {
|
|
579
|
+
return part.trim();
|
|
580
|
+
}
|
|
707
581
|
}
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
hazardRiskChanged = true;
|
|
582
|
+
for (const part of Object.values(rec)) {
|
|
583
|
+
if (typeof part === 'string' && part.trim()) {
|
|
584
|
+
return part.trim();
|
|
585
|
+
}
|
|
713
586
|
}
|
|
714
587
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
588
|
+
return '';
|
|
589
|
+
}
|
|
590
|
+
function buildSectionTypeRelationSnapshot(sectionTypeId, snapshot) {
|
|
591
|
+
const title = asSafetyPlanDisplayText(snapshot?.title);
|
|
592
|
+
const code = asSafetyPlanDisplayText(snapshot?.code);
|
|
593
|
+
const description = asSafetyPlanDisplayText(snapshot?.description);
|
|
718
594
|
return {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (!clearFromHazard && !clearFromHazardRisk) {
|
|
725
|
-
return section;
|
|
726
|
-
}
|
|
727
|
-
return clearSectionItems(section);
|
|
728
|
-
}),
|
|
595
|
+
id: sectionTypeId,
|
|
596
|
+
...(title ? { title } : {}),
|
|
597
|
+
...(code ? { code } : {}),
|
|
598
|
+
...(description ? { description } : {}),
|
|
599
|
+
...(snapshot?.isRepeatable === true ? { isRepeatable: true } : {}),
|
|
729
600
|
};
|
|
730
601
|
}
|
|
731
|
-
function
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (typeof id === 'string' && id.length > 0) {
|
|
736
|
-
ids.add(id);
|
|
737
|
-
}
|
|
602
|
+
function resolveSectionTypeTitle(sectionTypeId, sectionType) {
|
|
603
|
+
const title = asSafetyPlanDisplayText(sectionType?.title);
|
|
604
|
+
if (title) {
|
|
605
|
+
return title;
|
|
738
606
|
}
|
|
739
|
-
|
|
607
|
+
const code = asSafetyPlanDisplayText(sectionType?.code);
|
|
608
|
+
if (code) {
|
|
609
|
+
return code;
|
|
610
|
+
}
|
|
611
|
+
const builtIn = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID[sectionTypeId]?.sectionTypeKey;
|
|
612
|
+
if (builtIn) {
|
|
613
|
+
return builtIn;
|
|
614
|
+
}
|
|
615
|
+
return sectionTypeId;
|
|
740
616
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (!row || typeof row !== 'object') {
|
|
750
|
-
continue;
|
|
751
|
-
}
|
|
752
|
-
const entry = row;
|
|
753
|
-
const controlMeasure = entry['controlMeasure'];
|
|
754
|
-
const id = (typeof entry['controlMeasureId'] === 'string' && entry['controlMeasureId']) ||
|
|
755
|
-
(typeof controlMeasure?.['id'] === 'string' && controlMeasure['id']) ||
|
|
756
|
-
undefined;
|
|
757
|
-
if (id) {
|
|
758
|
-
ids.add(id);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
617
|
+
/**
|
|
618
|
+
* Title persisted on SafetyPlanSection. Never stores a bare UUID — the UI translate
|
|
619
|
+
* pipe treats unknown keys as empty, which made seeded section headers blank.
|
|
620
|
+
*/
|
|
621
|
+
function resolveSafetyPlanSectionPersistedTitle(sectionTitle, sectionTypeId, snapshot) {
|
|
622
|
+
const explicit = asSafetyPlanDisplayText(sectionTitle);
|
|
623
|
+
if (explicit && explicit !== sectionTypeId) {
|
|
624
|
+
return explicit;
|
|
761
625
|
}
|
|
762
|
-
|
|
626
|
+
const fromType = resolveSectionTypeTitle(sectionTypeId, snapshot);
|
|
627
|
+
if (fromType && fromType !== sectionTypeId) {
|
|
628
|
+
return fromType;
|
|
629
|
+
}
|
|
630
|
+
return 'Section';
|
|
763
631
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region ---- Ref helpers ----
|
|
634
|
+
/** True when a type-section row is marked as the plan-type default template. */
|
|
635
|
+
function isSafetyPlanTypeSectionDefault(row) {
|
|
636
|
+
const value = row?.isDefault;
|
|
637
|
+
return value === true || value === 1 || value === 'true';
|
|
638
|
+
}
|
|
639
|
+
/** Extracts a stable entity id from a raw lookup / context value. */
|
|
640
|
+
function resolveEntityRefId(raw) {
|
|
641
|
+
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
642
|
+
return raw.trim();
|
|
768
643
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
644
|
+
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
|
645
|
+
return String(raw);
|
|
646
|
+
}
|
|
647
|
+
if (raw && typeof raw === 'object') {
|
|
648
|
+
const id = raw.id;
|
|
649
|
+
if (typeof id === 'string' && id.trim().length > 0) {
|
|
650
|
+
return id.trim();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
774
654
|
}
|
|
775
|
-
|
|
776
|
-
|
|
655
|
+
function resolveSectionTypeId(typeRow) {
|
|
656
|
+
if (!typeRow) {
|
|
657
|
+
return '';
|
|
658
|
+
}
|
|
659
|
+
return (resolveEntityRefId(typeRow.sectionTypeId) ??
|
|
660
|
+
resolveEntityRefId(typeRow.sectionType) ??
|
|
661
|
+
resolveEntityRefId(typeRow.sectionType?.id) ??
|
|
662
|
+
'');
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region ---- Build from type sections ----
|
|
666
|
+
let structureIdSeq = 0;
|
|
667
|
+
function nextStructureId(prefix) {
|
|
668
|
+
structureIdSeq += 1;
|
|
669
|
+
const rand = createRandomUuid();
|
|
670
|
+
return prefix ? `${prefix}-${rand}` : rand;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Builds an empty editable section structure from SafetyPlanTypeSection rows for one plan type.
|
|
674
|
+
*
|
|
675
|
+
* Uses `isDefault` rows when present; otherwise falls back to all active rows for that type
|
|
676
|
+
* (so a mis-flagged template still produces a structure). One plan section per `sectionTypeIds`
|
|
677
|
+
* entry, titled from the Section Type, ordered by type-section `sortOrder` then repeater order.
|
|
678
|
+
*/
|
|
679
|
+
function buildSectionStructureFromTypeSections(typeSections, options) {
|
|
680
|
+
const createId = options?.createId ?? (() => nextStructureId(''));
|
|
681
|
+
const active = typeSections
|
|
682
|
+
.filter((row) => row && row.isArchived !== true)
|
|
683
|
+
.slice()
|
|
684
|
+
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || String(a.title ?? '').localeCompare(String(b.title ?? '')));
|
|
685
|
+
const defaults = active.filter((row) => isSafetyPlanTypeSectionDefault(row));
|
|
686
|
+
const sourceRows = defaults.length > 0 ? defaults : active;
|
|
687
|
+
const sections = [];
|
|
688
|
+
const seenSourceTypePairs = new Set();
|
|
689
|
+
let sortOrder = 1;
|
|
690
|
+
for (const typeSection of sourceRows) {
|
|
691
|
+
const typeRows = Array.isArray(typeSection.sectionTypeIds) ? typeSection.sectionTypeIds : [];
|
|
692
|
+
if (!typeRows.length) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
for (const typeRow of typeRows) {
|
|
696
|
+
const sectionTypeId = resolveSectionTypeId(typeRow);
|
|
697
|
+
if (!sectionTypeId) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
// One plan section per (type-section source, section type) — prevents
|
|
701
|
+
// accidental double expansion when the same type is listed twice.
|
|
702
|
+
const sourceId = typeof typeSection.id === 'string' ? typeSection.id : '';
|
|
703
|
+
const pairKey = sourceId ? `${sourceId}|${sectionTypeId}` : sectionTypeId;
|
|
704
|
+
if (seenSourceTypePairs.has(pairKey)) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
seenSourceTypePairs.add(pairKey);
|
|
708
|
+
const sectionType = buildSectionTypeRelationSnapshot(sectionTypeId, typeRow.sectionType);
|
|
709
|
+
const sectionId = createId();
|
|
710
|
+
sections.push({
|
|
711
|
+
id: sectionId,
|
|
712
|
+
sectionTypeId,
|
|
713
|
+
sectionType,
|
|
714
|
+
title: resolveSafetyPlanSectionPersistedTitle(undefined, sectionTypeId, sectionType),
|
|
715
|
+
description: asSafetyPlanDisplayText(sectionType.description),
|
|
716
|
+
sourceSafetyPlanTypeSectionId: typeSection.id,
|
|
717
|
+
sortOrder: sortOrder++,
|
|
718
|
+
required: isSafetyPlanTypeSectionDefault(typeSection) || defaults.length === 0,
|
|
719
|
+
items: [
|
|
720
|
+
{
|
|
721
|
+
id: `${sectionId}__item_1`,
|
|
722
|
+
itemIds: [],
|
|
723
|
+
items: [],
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return { sections };
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Removes accidental ghost rows from double materialization of the same type-section
|
|
733
|
+
* source. Distinct sections of the same type are kept. Reassigns contiguous 1-based
|
|
734
|
+
* `sortOrder` to preserve display order.
|
|
735
|
+
*/
|
|
736
|
+
function dedupeSafetyPlanSectionStructure(structure, _options) {
|
|
737
|
+
const sections = Array.isArray(structure?.sections)
|
|
738
|
+
? structure.sections
|
|
739
|
+
: [];
|
|
740
|
+
const seenSourceTypePairs = new Set();
|
|
741
|
+
const deduped = [];
|
|
742
|
+
for (const section of sections) {
|
|
743
|
+
const sectionTypeId = resolveEntityRefId(section.sectionTypeId) ?? String(section.sectionTypeId ?? '').trim();
|
|
744
|
+
if (!sectionTypeId) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
const sourceId = String(section.sourceSafetyPlanTypeSectionId ?? '').trim();
|
|
748
|
+
const sourcePairKey = sourceId ? `${sourceId}|${sectionTypeId}` : '';
|
|
749
|
+
// Same type-section source + section type must never appear twice (double materialization).
|
|
750
|
+
if (sourcePairKey && seenSourceTypePairs.has(sourcePairKey)) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (sourcePairKey) {
|
|
754
|
+
seenSourceTypePairs.add(sourcePairKey);
|
|
755
|
+
}
|
|
756
|
+
deduped.push(section);
|
|
757
|
+
}
|
|
777
758
|
return {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
759
|
+
...structure,
|
|
760
|
+
sections: deduped.map((section, index) => ({
|
|
761
|
+
...section,
|
|
762
|
+
sortOrder: index + 1,
|
|
763
|
+
})),
|
|
781
764
|
};
|
|
782
765
|
}
|
|
783
|
-
function
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
766
|
+
function collectAllowedSectionTypeIds(typeSections) {
|
|
767
|
+
const ids = new Set();
|
|
768
|
+
for (const typeSection of typeSections) {
|
|
769
|
+
if (typeSection.isArchived === true) {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
for (const row of typeSection.sectionTypeIds ?? []) {
|
|
773
|
+
const id = resolveSectionTypeId(row);
|
|
774
|
+
if (id) {
|
|
775
|
+
ids.add(id);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
788
778
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
779
|
+
return [...ids];
|
|
780
|
+
}
|
|
781
|
+
function collectRequiredSourceTypeSectionIds(typeSections) {
|
|
782
|
+
return typeSections
|
|
783
|
+
.filter((row) => row.isArchived !== true && isSafetyPlanTypeSectionDefault(row))
|
|
784
|
+
.map((row) => row.id)
|
|
785
|
+
.filter((id) => typeof id === 'string' && id.length > 0);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Validates that a plan-type section template does not list the same
|
|
789
|
+
* non-repeatable section type more than once (across rows and within a single row's repeater).
|
|
790
|
+
*/
|
|
791
|
+
function validateSafetyPlanTypeSectionTemplate(typeSections, repeatableSectionTypeIds = []) {
|
|
792
|
+
const seen = new Map();
|
|
793
|
+
const issues = [];
|
|
794
|
+
for (const typeSection of typeSections) {
|
|
795
|
+
if (typeSection.isArchived === true) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
for (const row of typeSection.sectionTypeIds ?? []) {
|
|
799
|
+
const sectionTypeId = resolveSectionTypeId(row);
|
|
800
|
+
if (!sectionTypeId ||
|
|
801
|
+
isSafetyPlanSectionTypeRepeatable(sectionTypeId, {
|
|
802
|
+
repeatableSectionTypeIds,
|
|
803
|
+
sectionType: row.sectionType,
|
|
804
|
+
})) {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const sectionTypeTitle = resolveSafetyPlanSectionPersistedTitle(undefined, sectionTypeId, row.sectionType);
|
|
808
|
+
if (seen.has(sectionTypeId)) {
|
|
809
|
+
issues.push({
|
|
810
|
+
code: 'INVALID_TYPE_SECTION_TEMPLATE',
|
|
811
|
+
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.invalid-type-template-duplicate',
|
|
812
|
+
details: {
|
|
813
|
+
sectionTypeId,
|
|
814
|
+
sectionTypeTitle,
|
|
815
|
+
sourceSafetyPlanTypeSectionId: typeSection.id,
|
|
816
|
+
previousSourceSafetyPlanTypeSectionId: seen.get(sectionTypeId)?.sourceSafetyPlanTypeSectionId,
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
seen.set(sectionTypeId, {
|
|
822
|
+
title: sectionTypeTitle,
|
|
823
|
+
sourceSafetyPlanTypeSectionId: typeSection.id,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
792
826
|
}
|
|
793
|
-
return
|
|
827
|
+
return issues;
|
|
794
828
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region ---- Validation ----
|
|
831
|
+
function validateSafetyPlanSectionStructure(structure, context) {
|
|
832
|
+
const sections = Array.isArray(structure?.sections) ? structure.sections : [];
|
|
833
|
+
const issues = [];
|
|
834
|
+
const presentSourceIds = new Set(sections
|
|
835
|
+
.map((s) => s.sourceSafetyPlanTypeSectionId)
|
|
836
|
+
.filter((id) => typeof id === 'string' && id.length > 0));
|
|
837
|
+
for (const requiredSourceId of context.requiredSourceTypeSectionIds) {
|
|
838
|
+
if (!presentSourceIds.has(requiredSourceId)) {
|
|
839
|
+
issues.push({
|
|
840
|
+
code: 'REQUIRED_SECTION_MISSING',
|
|
841
|
+
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.required-section-missing',
|
|
842
|
+
details: { sourceSafetyPlanTypeSectionId: requiredSourceId },
|
|
843
|
+
});
|
|
844
|
+
}
|
|
800
845
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
846
|
+
const allowed = new Set(context.allowedSectionTypeIds);
|
|
847
|
+
const sortOrders = [];
|
|
848
|
+
for (const section of sections) {
|
|
849
|
+
const sectionTypeId = resolveEntityRefId(section.sectionTypeId) ??
|
|
850
|
+
String(section.sectionTypeId ?? '').trim();
|
|
851
|
+
if (!sectionTypeId) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
const sectionType = section
|
|
855
|
+
.sectionType;
|
|
856
|
+
const sectionTypeTitle = resolveSafetyPlanSectionPersistedTitle(undefined, sectionTypeId, sectionType);
|
|
857
|
+
if (allowed.size > 0 && !allowed.has(sectionTypeId)) {
|
|
858
|
+
issues.push({
|
|
859
|
+
code: 'SECTION_TYPE_NOT_ALLOWED',
|
|
860
|
+
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.section-type-not-allowed',
|
|
861
|
+
details: { sectionTypeId, sectionTypeTitle, sectionId: section.id },
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
const sortOrder = section.sortOrder;
|
|
865
|
+
if (typeof sortOrder === 'number' && Number.isFinite(sortOrder)) {
|
|
866
|
+
sortOrders.push(sortOrder);
|
|
805
867
|
}
|
|
806
|
-
return buildInFilter(dependency.filterField, parentIds);
|
|
807
868
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
869
|
+
if (sortOrders.length !== sections.length) {
|
|
870
|
+
issues.push({
|
|
871
|
+
code: 'INVALID_SORT_ORDER',
|
|
872
|
+
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.invalid-sort-order',
|
|
873
|
+
});
|
|
811
874
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
875
|
+
else {
|
|
876
|
+
const unique = new Set(sortOrders);
|
|
877
|
+
if (unique.size !== sortOrders.length) {
|
|
878
|
+
issues.push({
|
|
879
|
+
code: 'INVALID_SORT_ORDER',
|
|
880
|
+
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.invalid-sort-order',
|
|
881
|
+
details: { reason: 'duplicate' },
|
|
882
|
+
});
|
|
883
|
+
}
|
|
818
884
|
}
|
|
819
|
-
return
|
|
885
|
+
return issues;
|
|
820
886
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
887
|
+
function toBuilderValueFromStructure(structure) {
|
|
888
|
+
return {
|
|
889
|
+
sections: structure.sections.map((section) => ({
|
|
890
|
+
id: section.id,
|
|
891
|
+
sectionTypeId: section.sectionTypeId,
|
|
892
|
+
sectionType: section.sectionType,
|
|
893
|
+
title: section.title,
|
|
894
|
+
description: section.description ?? '',
|
|
895
|
+
safetyPlanId: section.safetyPlanId,
|
|
896
|
+
code: section.code,
|
|
897
|
+
sourceSafetyPlanTypeSectionId: section.sourceSafetyPlanTypeSectionId,
|
|
898
|
+
sortOrder: section.sortOrder,
|
|
899
|
+
required: section.required,
|
|
900
|
+
items: section.items.map((item) => ({
|
|
901
|
+
id: item.id,
|
|
902
|
+
itemIds: [...(item.itemIds ?? [])],
|
|
903
|
+
items: item.items ? [...item.items] : [],
|
|
904
|
+
})),
|
|
905
|
+
})),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
//#endregion
|
|
909
|
+
|
|
910
|
+
//#region ---- Imports ----
|
|
911
|
+
/** Declarative section-type dependencies resolved from prior sections in plan order. */
|
|
912
|
+
const WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES = {
|
|
913
|
+
HazardRisk: {
|
|
914
|
+
kind: 'parentFilter',
|
|
915
|
+
parentSectionTypeKey: 'Hazard',
|
|
916
|
+
filterField: 'hazardId',
|
|
917
|
+
},
|
|
918
|
+
PpeHazards: {
|
|
919
|
+
kind: 'parentFilter',
|
|
920
|
+
parentSectionTypeKey: 'Hazard',
|
|
921
|
+
filterField: 'hazardId',
|
|
922
|
+
},
|
|
923
|
+
HazardRiskPpe: {
|
|
924
|
+
kind: 'parentFilter',
|
|
925
|
+
parentSectionTypeKey: 'HazardRisk',
|
|
926
|
+
filterField: 'hazardRiskId',
|
|
827
927
|
},
|
|
928
|
+
RiskMatrix: {
|
|
929
|
+
kind: 'derivedFromParent',
|
|
930
|
+
parentSectionTypeKey: 'HazardRisk',
|
|
931
|
+
resolveField: 'riskMatrixId',
|
|
932
|
+
filterField: 'id',
|
|
933
|
+
},
|
|
934
|
+
};
|
|
935
|
+
/** Section types cleared when upstream hazard selections change. */
|
|
936
|
+
const WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD = [
|
|
937
|
+
'HazardRisk',
|
|
938
|
+
'PpeHazards',
|
|
939
|
+
'HazardRiskPpe',
|
|
940
|
+
'RiskMatrix',
|
|
941
|
+
];
|
|
942
|
+
/** Section types cleared when upstream hazard-risk selections change. */
|
|
943
|
+
const WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD_RISK = [
|
|
944
|
+
'HazardRiskPpe',
|
|
945
|
+
'RiskMatrix',
|
|
946
|
+
];
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region ---- Lookup helpers ----
|
|
949
|
+
const PARENT_HAZARD_IDS_CONTEXT_KEY = 'parentHazardIds';
|
|
950
|
+
const PARENT_HAZARD_RISK_IDS_CONTEXT_KEY = 'parentHazardRiskIds';
|
|
951
|
+
/** Expose paths when a section type is selected (shared by entity repeater and builder dialog). */
|
|
952
|
+
const WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_EXPOSE = [
|
|
953
|
+
{ source: 'id', target: 'sectionType.id' },
|
|
954
|
+
{ source: 'title', target: 'sectionType.title' },
|
|
955
|
+
{ source: 'code', target: 'sectionType.code' },
|
|
956
|
+
{ source: 'description', target: 'sectionType.description' },
|
|
957
|
+
{ source: 'isRepeatable', target: 'sectionType.isRepeatable' },
|
|
958
|
+
];
|
|
959
|
+
/** Auto-fill section description when section type is selected. */
|
|
960
|
+
const WORKPLACE_SAFETY_SECTION_TYPE_DESCRIPTION_SYNC_TRIGGERS = [
|
|
828
961
|
{
|
|
829
962
|
event: "{{ events.context('sectionTypeId') }}",
|
|
830
|
-
action: "{{ widget.
|
|
963
|
+
action: "{{ const items = widget.output('selectedItems'); const selectedDescription = Array.isArray(items) ? items[0]?.description : undefined; const d = selectedDescription || context.eval('sectionType.description') || ''; context.set('sectionTypeDescription', d); const descriptionWidget = widget.find('sectionTypeDescription'); if (descriptionWidget) { descriptionWidget.setValue(d); } }}",
|
|
831
964
|
},
|
|
832
|
-
];
|
|
833
|
-
/**
|
|
834
|
-
* Reloads lookup data after section type is resolved (`sectionType.id`, `itemEntityName`).
|
|
835
|
-
*/
|
|
836
|
-
const WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_REFRESH_ON_SECTION_TYPE_EXPOSE_TRIGGERS = [
|
|
837
965
|
{
|
|
838
|
-
event: "{{ events.context('sectionType.
|
|
839
|
-
action:
|
|
966
|
+
event: "{{ events.context('sectionType.description') }}",
|
|
967
|
+
action: "{{ const d = context.eval('sectionType.description') || ''; context.set('sectionTypeDescription', d); const descriptionWidget = widget.find('sectionTypeDescription'); if (descriptionWidget) { descriptionWidget.setValue(d); } }}",
|
|
840
968
|
},
|
|
969
|
+
];
|
|
970
|
+
/** Auto-fill section title when section type is selected. */
|
|
971
|
+
const WORKPLACE_SAFETY_SECTION_TYPE_TITLE_SYNC_TRIGGERS = [
|
|
841
972
|
{
|
|
842
|
-
event: "{{ events.context('
|
|
843
|
-
action:
|
|
973
|
+
event: "{{ events.context('sectionTypeId') }}",
|
|
974
|
+
action: "{{ const items = widget.output('selectedItems'); const selectedTitle = Array.isArray(items) ? items[0]?.title : undefined; const t = selectedTitle || context.eval('sectionType.title') || ''; if (t) { context.set('title', t); const titleWidget = widget.find('title'); if (titleWidget) { titleWidget.setValue(t); } } }}",
|
|
844
975
|
},
|
|
845
976
|
{
|
|
846
|
-
event: "{{ events.context('
|
|
847
|
-
action:
|
|
977
|
+
event: "{{ events.context('sectionType.title') }}",
|
|
978
|
+
action: "{{ const t = context.eval('sectionType.title') || ''; if (t) { context.set('title', t); const titleWidget = widget.find('title'); if (titleWidget) { titleWidget.setValue(t); } } }}",
|
|
848
979
|
},
|
|
849
980
|
];
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const dependentSectionTypeIds = Object.entries(WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES)
|
|
870
|
-
.filter(([, config]) => config.kind === 'parentFilter' && config.parentSectionTypeKey === parentSectionTypeKey)
|
|
871
|
-
.map(([key]) => {
|
|
872
|
-
const id = Object.entries(WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID).find(([, meta]) => meta.sectionTypeKey === key)?.[0];
|
|
873
|
-
return id ? `'${id}'` : undefined;
|
|
874
|
-
})
|
|
875
|
-
.filter((id) => Boolean(id))
|
|
876
|
-
.join(',');
|
|
877
|
-
return {
|
|
878
|
-
field: parentSectionTypeKey === 'Hazard' ? 'hazardId' : 'hazardRiskId',
|
|
879
|
-
operator: { type: 'in' },
|
|
880
|
-
value: `{{ [${dependentSectionTypeIds}].includes(context.eval('${sectionTypeIdPath}')) ? ((context.eval('${parentIdsContextKey}') || []).length ? context.eval('${parentIdsContextKey}') : undefined) : undefined }}`,
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
function buildSectionTypeDependentHazardFilter(sectionTypeIdPath, parentHazardIdsContextKey) {
|
|
884
|
-
return buildSectionTypeDependentParentFilter(sectionTypeIdPath, parentHazardIdsContextKey, 'Hazard');
|
|
885
|
-
}
|
|
886
|
-
const sectionItemLookupExpose = [
|
|
887
|
-
{ source: 'id', target: 'items.{id}' },
|
|
888
|
-
{ source: 'title', target: 'items.{title}' },
|
|
889
|
-
{ source: 'code', target: 'items.{code}' },
|
|
890
|
-
{ source: 'ppeItem.id', target: 'items.{id}' },
|
|
891
|
-
{ source: 'ppeItem.title', target: 'items.{title}' },
|
|
892
|
-
{ source: 'ppeItem.code', target: 'items.{code}' },
|
|
893
|
-
];
|
|
894
|
-
/** Shared item lookup options for builder item dialog (`sectionType.*` in dialog context). */
|
|
895
|
-
function buildSafetyPlanSectionItemLookupOptions(entityExpression, options) {
|
|
896
|
-
const sectionTypeIdPath = options?.sectionTypeIdPath ?? 'sectionType.id';
|
|
897
|
-
const lookup = {
|
|
898
|
-
entity: entityExpression,
|
|
899
|
-
look: 'lookup',
|
|
900
|
-
multiple: true,
|
|
901
|
-
allowSearch: true,
|
|
902
|
-
textField: buildSectionTypeItemTextFieldExpression(sectionTypeIdPath),
|
|
903
|
-
columns: buildSectionTypeItemColumnsExpression(sectionTypeIdPath),
|
|
904
|
-
placeholder: options?.defaultPlaceholderKey ?? defaultItemIdsPlaceholderKey,
|
|
905
|
-
expose: sectionItemLookupExpose,
|
|
906
|
-
triggers: WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_SECTION_TYPE_TRIGGERS,
|
|
907
|
-
};
|
|
908
|
-
const filters = [];
|
|
909
|
-
if (options?.parentHazardIdsContextKey) {
|
|
910
|
-
filters.push(buildSectionTypeDependentHazardFilter(sectionTypeIdPath, options.parentHazardIdsContextKey));
|
|
911
|
-
}
|
|
912
|
-
if (options?.parentHazardRiskIdsContextKey) {
|
|
913
|
-
filters.push(buildSectionTypeDependentParentFilter(sectionTypeIdPath, options.parentHazardRiskIdsContextKey, 'HazardRisk'));
|
|
914
|
-
}
|
|
915
|
-
if (filters.length === 1) {
|
|
916
|
-
lookup['filter'] = filters[0];
|
|
917
|
-
}
|
|
918
|
-
else if (filters.length > 1) {
|
|
919
|
-
lookup['filter'] = { logic: 'and', filters };
|
|
981
|
+
/** Shared lookup widget options for section type pickers (builder dialog and entity forms). */
|
|
982
|
+
const WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_WIDGET_OPTIONS = {
|
|
983
|
+
look: 'select',
|
|
984
|
+
allowSearch: true,
|
|
985
|
+
textField: 'code',
|
|
986
|
+
textTemplate: '{code} — {description}',
|
|
987
|
+
valueField: 'id',
|
|
988
|
+
columns: ['code', 'title', 'description'],
|
|
989
|
+
expose: WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_EXPOSE,
|
|
990
|
+
triggers: [
|
|
991
|
+
...WORKPLACE_SAFETY_SECTION_TYPE_TITLE_SYNC_TRIGGERS,
|
|
992
|
+
...WORKPLACE_SAFETY_SECTION_TYPE_DESCRIPTION_SYNC_TRIGGERS,
|
|
993
|
+
],
|
|
994
|
+
};
|
|
995
|
+
//#endregion
|
|
996
|
+
//#region ---- Resolver API ----
|
|
997
|
+
function getSectionTypeDependency(sectionTypeKey) {
|
|
998
|
+
if (!sectionTypeKey) {
|
|
999
|
+
return undefined;
|
|
920
1000
|
}
|
|
921
|
-
return
|
|
1001
|
+
return WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES[sectionTypeKey];
|
|
922
1002
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
look: 'lookup',
|
|
928
|
-
};
|
|
1003
|
+
function sectionTypeKeyFromSection(section) {
|
|
1004
|
+
const sectionTypeId = section.sectionTypeId || section.sectionType?.id;
|
|
1005
|
+
const key = resolveSectionTypeKey(sectionTypeId, section.sectionType);
|
|
1006
|
+
return key;
|
|
929
1007
|
}
|
|
930
|
-
function
|
|
931
|
-
if (
|
|
932
|
-
return [
|
|
933
|
-
}
|
|
934
|
-
if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe) {
|
|
935
|
-
return ['code', 'hazardRisk.title', 'ppeItem.title', 'requirementType'];
|
|
936
|
-
}
|
|
937
|
-
if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.riskMatrix) {
|
|
938
|
-
return ['code', 'title'];
|
|
1008
|
+
function collectSectionItemIds(section, sectionTypeKey) {
|
|
1009
|
+
if (!section?.items?.length || sectionTypeKeyFromSection(section) !== sectionTypeKey) {
|
|
1010
|
+
return [];
|
|
939
1011
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const isPpeHazards = sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards;
|
|
947
|
-
const isHazardRiskPpe = sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe;
|
|
948
|
-
return selectedItems
|
|
949
|
-
.map((item) => {
|
|
950
|
-
if (isPpeHazards || isHazardRiskPpe) {
|
|
951
|
-
const ppeItem = item['ppeItem'];
|
|
952
|
-
const id = String(ppeItem?.['id'] ?? item['id'] ?? '');
|
|
953
|
-
return {
|
|
954
|
-
id,
|
|
955
|
-
title: typeof ppeItem?.['title'] === 'string' ? ppeItem['title'] : undefined,
|
|
956
|
-
code: typeof ppeItem?.['code'] === 'string' ? ppeItem['code'] : undefined,
|
|
957
|
-
};
|
|
1012
|
+
const ids = new Set();
|
|
1013
|
+
for (const item of section.items) {
|
|
1014
|
+
for (const id of item.itemIds ?? []) {
|
|
1015
|
+
if (typeof id === 'string' && id.length > 0) {
|
|
1016
|
+
ids.add(id);
|
|
1017
|
+
}
|
|
958
1018
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
title: typeof item['title'] === 'string' ? item['title'] : undefined,
|
|
962
|
-
code: typeof item['code'] === 'string' ? item['code'] : undefined,
|
|
963
|
-
};
|
|
964
|
-
})
|
|
965
|
-
.filter((item) => item.id.length > 0);
|
|
1019
|
+
}
|
|
1020
|
+
return [...ids];
|
|
966
1021
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
const
|
|
970
|
-
const
|
|
971
|
-
for (let index = 0; index
|
|
972
|
-
|
|
973
|
-
if (typeof id !== 'string' || !id || seen.has(id)) {
|
|
974
|
-
continue;
|
|
975
|
-
}
|
|
976
|
-
seen.add(id);
|
|
977
|
-
itemIds.push(id);
|
|
978
|
-
const snapshot = item.items?.[index];
|
|
979
|
-
items.push(snapshot?.id === id ? snapshot : { id });
|
|
1022
|
+
/** Collects item IDs from sections up to `sectionIndex` whose type matches `parentSectionTypeKey`. */
|
|
1023
|
+
function resolveParentItemIds(sections, sectionIndex, parentSectionTypeKey) {
|
|
1024
|
+
const ids = new Set();
|
|
1025
|
+
const lastIndex = Math.min(sectionIndex, sections.length - 1);
|
|
1026
|
+
for (let index = 0; index <= lastIndex; index += 1) {
|
|
1027
|
+
collectSectionItemIds(sections[index], parentSectionTypeKey).forEach((id) => ids.add(id));
|
|
980
1028
|
}
|
|
981
|
-
return
|
|
1029
|
+
return [...ids];
|
|
982
1030
|
}
|
|
983
|
-
|
|
984
|
-
|
|
1031
|
+
/** Collects hazard IDs from earlier sections and Hazard rows in the current section. */
|
|
1032
|
+
function resolveParentHazardIds(sections, sectionIndex, parentSectionTypeKey = 'Hazard') {
|
|
1033
|
+
return resolveParentItemIds(sections, sectionIndex, parentSectionTypeKey);
|
|
985
1034
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const SAFETY_PLAN_SECTIONS_PAGE_COMPONENT_KEY = 'safety-plan-sections-page';
|
|
990
|
-
|
|
991
|
-
//#region ---- Safety plan section builder value types ----
|
|
992
|
-
//#endregion
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* Shared helpers for Safety Plan Section uniqueness (section type per plan).
|
|
996
|
-
* Leaf module — imports only other contract leaves (no client/server layers).
|
|
997
|
-
*/
|
|
998
|
-
//#region ---- Constants ----
|
|
999
|
-
const SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE_MESSAGE_KEY = '@workplacesafety-management:safety-plan-sections.messages.duplicate-section-type';
|
|
1000
|
-
/** Stable API / middleware error code for duplicate section type in a plan. */
|
|
1001
|
-
const SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE_CODE = 'SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE';
|
|
1002
|
-
//#endregion
|
|
1003
|
-
//#region ---- Helpers ----
|
|
1004
|
-
function resolveRefId$1(raw) {
|
|
1005
|
-
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
1006
|
-
return raw.trim();
|
|
1007
|
-
}
|
|
1008
|
-
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
|
1009
|
-
return String(raw);
|
|
1010
|
-
}
|
|
1011
|
-
if (raw && typeof raw === 'object') {
|
|
1012
|
-
const id = raw.id;
|
|
1013
|
-
if (typeof id === 'string' && id.trim().length > 0) {
|
|
1014
|
-
return id.trim();
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
return '';
|
|
1035
|
+
/** Collects hazard-risk IDs from prior HazardRisk sections up to `sectionIndex`. */
|
|
1036
|
+
function resolveParentHazardRiskIds(sections, sectionIndex) {
|
|
1037
|
+
return resolveParentItemIds(sections, sectionIndex, 'HazardRisk');
|
|
1018
1038
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
return resolveRefId$1(section.sectionTypeId) || resolveRefId$1(section.sectionType);
|
|
1039
|
+
function parentItemSignature(sections, sectionIndex, parentSectionTypeKey) {
|
|
1040
|
+
return resolveParentItemIds(sections, sectionIndex, parentSectionTypeKey).sort().join('|');
|
|
1022
1041
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
* Checks (in order): snapshot flag, explicit allow-list, built-in section type meta.
|
|
1026
|
-
*/
|
|
1027
|
-
function isSafetyPlanSectionTypeRepeatable(sectionTypeId, options) {
|
|
1028
|
-
const id = resolveRefId$1(sectionTypeId);
|
|
1029
|
-
if (!id) {
|
|
1042
|
+
function shouldClearSectionOnParentChange(section, sectionIndex, changeIndex, parentTypeKey, downstreamTypes) {
|
|
1043
|
+
if (sectionIndex < changeIndex) {
|
|
1030
1044
|
return false;
|
|
1031
1045
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1046
|
+
const sectionKey = sectionTypeKeyFromSection(section);
|
|
1047
|
+
if (!sectionKey || !downstreamTypes.includes(sectionKey)) {
|
|
1048
|
+
return false;
|
|
1034
1049
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
return true;
|
|
1050
|
+
if (sectionIndex === changeIndex && sectionKey === parentTypeKey) {
|
|
1051
|
+
return false;
|
|
1038
1052
|
}
|
|
1039
|
-
return
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
.
|
|
1045
|
-
|
|
1053
|
+
return section.items.some((item) => (item.itemIds?.length ?? 0) > 0);
|
|
1054
|
+
}
|
|
1055
|
+
function clearSectionItems(section) {
|
|
1056
|
+
return {
|
|
1057
|
+
...section,
|
|
1058
|
+
items: section.items.map((item) => ({ ...item, itemIds: [], items: [] })),
|
|
1059
|
+
};
|
|
1046
1060
|
}
|
|
1047
|
-
//#endregion
|
|
1048
|
-
//#region ---- Validation ----
|
|
1049
1061
|
/**
|
|
1050
|
-
*
|
|
1051
|
-
* when all non-repeatable types are unique. Empty / missing section type ids are ignored.
|
|
1052
|
-
* Repeatable section types are allowed to appear more than once.
|
|
1062
|
+
* When upstream hazard or hazard-risk selections change, clears item rows in later dependent sections.
|
|
1053
1063
|
*/
|
|
1054
|
-
function
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
})) {
|
|
1068
|
-
continue;
|
|
1064
|
+
function clearDependentItemsOnParentChange(previous, next) {
|
|
1065
|
+
if (!next.sections.length) {
|
|
1066
|
+
return next;
|
|
1067
|
+
}
|
|
1068
|
+
let changeIndex = next.sections.length;
|
|
1069
|
+
let hazardChanged = false;
|
|
1070
|
+
let hazardRiskChanged = false;
|
|
1071
|
+
for (let index = 0; index < next.sections.length; index += 1) {
|
|
1072
|
+
const prevHazardSig = parentItemSignature(previous.sections, index, 'Hazard');
|
|
1073
|
+
const nextHazardSig = parentItemSignature(next.sections, index, 'Hazard');
|
|
1074
|
+
if (prevHazardSig !== nextHazardSig) {
|
|
1075
|
+
changeIndex = Math.min(changeIndex, index);
|
|
1076
|
+
hazardChanged = true;
|
|
1069
1077
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1078
|
+
const prevRiskSig = parentItemSignature(previous.sections, index, 'HazardRisk');
|
|
1079
|
+
const nextRiskSig = parentItemSignature(next.sections, index, 'HazardRisk');
|
|
1080
|
+
if (prevRiskSig !== nextRiskSig) {
|
|
1081
|
+
changeIndex = Math.min(changeIndex, index);
|
|
1082
|
+
hazardRiskChanged = true;
|
|
1072
1083
|
}
|
|
1073
|
-
seen.add(sectionTypeId);
|
|
1074
1084
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
/**
|
|
1078
|
-
* True when `sectionTypeId` already appears on another section in the collection
|
|
1079
|
-
* and the type is not repeatable.
|
|
1080
|
-
*/
|
|
1081
|
-
function safetyPlanSectionTypeAlreadyExists(sections, sectionTypeId, options) {
|
|
1082
|
-
const target = resolveRefId$1(sectionTypeId);
|
|
1083
|
-
if (!target) {
|
|
1084
|
-
return false;
|
|
1085
|
-
}
|
|
1086
|
-
if (isSafetyPlanSectionTypeRepeatable(target, {
|
|
1087
|
-
repeatableSectionTypeIds: options?.repeatableSectionTypeIds,
|
|
1088
|
-
sectionType: options?.sectionType,
|
|
1089
|
-
})) {
|
|
1090
|
-
return false;
|
|
1085
|
+
if (changeIndex === next.sections.length) {
|
|
1086
|
+
return next;
|
|
1091
1087
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1088
|
+
return {
|
|
1089
|
+
sections: next.sections.map((section, sectionIndex) => {
|
|
1090
|
+
const clearFromHazard = hazardChanged &&
|
|
1091
|
+
shouldClearSectionOnParentChange(section, sectionIndex, changeIndex, 'Hazard', WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD);
|
|
1092
|
+
const clearFromHazardRisk = hazardRiskChanged &&
|
|
1093
|
+
shouldClearSectionOnParentChange(section, sectionIndex, changeIndex, 'HazardRisk', WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD_RISK);
|
|
1094
|
+
if (!clearFromHazard && !clearFromHazardRisk) {
|
|
1095
|
+
return section;
|
|
1096
|
+
}
|
|
1097
|
+
return clearSectionItems(section);
|
|
1098
|
+
}),
|
|
1099
|
+
};
|
|
1100
1100
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1101
|
+
function extractRiskMatrixIdsFromHazardRisks(records) {
|
|
1102
|
+
const ids = new Set();
|
|
1103
|
+
for (const record of records) {
|
|
1104
|
+
const id = record['riskMatrixId'];
|
|
1105
|
+
if (typeof id === 'string' && id.length > 0) {
|
|
1106
|
+
ids.add(id);
|
|
1107
|
+
}
|
|
1107
1108
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1109
|
+
return [...ids];
|
|
1110
|
+
}
|
|
1111
|
+
function buildInFilter(field, values) {
|
|
1112
|
+
const unique = [...new Set(values.filter((id) => id.length > 0))];
|
|
1113
|
+
if (!unique.length) {
|
|
1114
|
+
return null;
|
|
1110
1115
|
}
|
|
1111
|
-
return
|
|
1116
|
+
return {
|
|
1117
|
+
field,
|
|
1118
|
+
operator: { type: 'in' },
|
|
1119
|
+
value: unique,
|
|
1120
|
+
};
|
|
1112
1121
|
}
|
|
1113
|
-
/**
|
|
1114
|
-
function
|
|
1115
|
-
|
|
1116
|
-
|
|
1122
|
+
/** Filter that matches no records when parent context exists but derived IDs are empty. */
|
|
1123
|
+
function buildEmptyResultFilter(field) {
|
|
1124
|
+
return {
|
|
1125
|
+
field,
|
|
1126
|
+
operator: { type: 'in' },
|
|
1127
|
+
value: ['00000000-0000-0000-0000-000000000000'],
|
|
1128
|
+
};
|
|
1117
1129
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1130
|
+
function resolveSafetyPlanSectionItemFilter(sectionTypeId, parentHazardIds, parentHazardRiskIds = []) {
|
|
1131
|
+
const sectionTypeKey = resolveSectionTypeKey(sectionTypeId);
|
|
1132
|
+
const dependency = getSectionTypeDependency(sectionTypeKey);
|
|
1133
|
+
if (!dependency || dependency.kind !== 'parentFilter') {
|
|
1134
|
+
return null;
|
|
1122
1135
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1136
|
+
const parentIds = dependency.parentSectionTypeKey === 'Hazard' ? parentHazardIds : parentHazardRiskIds;
|
|
1137
|
+
if (!parentIds.length) {
|
|
1138
|
+
return null;
|
|
1125
1139
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1140
|
+
return buildInFilter(dependency.filterField, parentIds);
|
|
1141
|
+
}
|
|
1142
|
+
async function resolveSafetyPlanSectionItemFilterAsync(sectionTypeId, sections, sectionIndex, loadHazardRisks) {
|
|
1143
|
+
const sectionTypeKey = resolveSectionTypeKey(sectionTypeId);
|
|
1144
|
+
const dependency = getSectionTypeDependency(sectionTypeKey);
|
|
1145
|
+
if (!dependency) {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
if (dependency.kind === 'parentFilter') {
|
|
1149
|
+
const parentIds = resolveParentItemIds(sections, sectionIndex, dependency.parentSectionTypeKey);
|
|
1150
|
+
if (!parentIds.length) {
|
|
1151
|
+
return null;
|
|
1130
1152
|
}
|
|
1153
|
+
return buildInFilter(dependency.filterField, parentIds);
|
|
1131
1154
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if (!typeRow) {
|
|
1136
|
-
return '';
|
|
1155
|
+
const parentIds = resolveParentItemIds(sections, sectionIndex, dependency.parentSectionTypeKey);
|
|
1156
|
+
if (!parentIds.length) {
|
|
1157
|
+
return null;
|
|
1137
1158
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
//#region ---- Build from type sections ----
|
|
1145
|
-
let structureIdSeq = 0;
|
|
1146
|
-
function nextStructureId(prefix) {
|
|
1147
|
-
structureIdSeq += 1;
|
|
1148
|
-
const rand = typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
|
1149
|
-
? crypto.randomUUID()
|
|
1150
|
-
: `00000000-0000-4000-8000-${String(structureIdSeq).padStart(12, '0')}`;
|
|
1151
|
-
return prefix ? `${prefix}-${rand}` : rand;
|
|
1159
|
+
const records = await loadHazardRisks(parentIds);
|
|
1160
|
+
const derivedIds = extractRiskMatrixIdsFromHazardRisks(records);
|
|
1161
|
+
if (!derivedIds.length) {
|
|
1162
|
+
return buildEmptyResultFilter(dependency.filterField);
|
|
1163
|
+
}
|
|
1164
|
+
return buildInFilter(dependency.filterField, derivedIds);
|
|
1152
1165
|
}
|
|
1166
|
+
const defaultItemIdsPlaceholderKey = '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.item-ids.placeholder';
|
|
1167
|
+
/** Clears item selection when section type changes in item dialog context. */
|
|
1168
|
+
const WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_CLEAR_ON_SECTION_TYPE_TRIGGERS = [
|
|
1169
|
+
{
|
|
1170
|
+
event: "{{ events.context('sectionType.id') }}",
|
|
1171
|
+
action: "{{ widget.setValue([]); context.set('items', []); }}",
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
event: "{{ events.context('sectionTypeId') }}",
|
|
1175
|
+
action: "{{ widget.setValue([]); context.set('items', []); }}",
|
|
1176
|
+
},
|
|
1177
|
+
];
|
|
1153
1178
|
/**
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* Uses `isDefault` rows when present; otherwise falls back to all active rows for that type
|
|
1157
|
-
* (so a mis-flagged template still produces a structure). One plan section per `sectionTypeIds`
|
|
1158
|
-
* entry, titled from the Section Type, ordered by type-section `sortOrder` then repeater order.
|
|
1179
|
+
* Reloads lookup data after section type is resolved (`sectionType.id`, `itemEntityName`).
|
|
1159
1180
|
*/
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
.
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
return { sections };
|
|
1181
|
+
const WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_REFRESH_ON_SECTION_TYPE_EXPOSE_TRIGGERS = [
|
|
1182
|
+
{
|
|
1183
|
+
event: "{{ events.context('sectionType.id') }}",
|
|
1184
|
+
action: '{{ widget.refresh(); }}',
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
event: "{{ events.context('sectionType.itemEntityName') }}",
|
|
1188
|
+
action: '{{ widget.refresh(); }}',
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
event: "{{ events.context('sectionTypeId') }}",
|
|
1192
|
+
action: '{{ widget.refresh(); }}',
|
|
1193
|
+
},
|
|
1194
|
+
];
|
|
1195
|
+
const WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_SECTION_TYPE_TRIGGERS = [
|
|
1196
|
+
...WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_CLEAR_ON_SECTION_TYPE_TRIGGERS,
|
|
1197
|
+
...WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_REFRESH_ON_SECTION_TYPE_EXPOSE_TRIGGERS,
|
|
1198
|
+
];
|
|
1199
|
+
function isPpeRuleSectionType(sectionTypeId) {
|
|
1200
|
+
return (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards ||
|
|
1201
|
+
sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe);
|
|
1202
|
+
}
|
|
1203
|
+
function buildSectionTypeItemTextFieldExpression(sectionTypeIdPath) {
|
|
1204
|
+
const ppeHazardsId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards;
|
|
1205
|
+
const hazardRiskPpeId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe;
|
|
1206
|
+
return `{{ [ '${ppeHazardsId}', '${hazardRiskPpeId}' ].includes(context.eval('${sectionTypeIdPath}')) ? 'ppeItem.title' : 'title' }}`;
|
|
1207
|
+
}
|
|
1208
|
+
function buildSectionTypeItemColumnsExpression(sectionTypeIdPath) {
|
|
1209
|
+
const ppeHazardsId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards;
|
|
1210
|
+
const hazardRiskPpeId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe;
|
|
1211
|
+
const riskMatrixId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.riskMatrix;
|
|
1212
|
+
return `{{ context.eval('${sectionTypeIdPath}') === '${riskMatrixId}' ? ['code', 'title', 'matrix'] : [ '${ppeHazardsId}', '${hazardRiskPpeId}' ].includes(context.eval('${sectionTypeIdPath}')) ? ['code', 'ppeItem.title', 'requirementType'] : ['code', 'title'] }}`;
|
|
1213
|
+
}
|
|
1214
|
+
function buildSectionTypeDependentParentFilter(sectionTypeIdPath, parentIdsContextKey, parentSectionTypeKey) {
|
|
1215
|
+
const dependentSectionTypeIds = Object.entries(WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES)
|
|
1216
|
+
.filter(([, config]) => config.kind === 'parentFilter' && config.parentSectionTypeKey === parentSectionTypeKey)
|
|
1217
|
+
.map(([key]) => {
|
|
1218
|
+
const id = Object.entries(WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID).find(([, meta]) => meta.sectionTypeKey === key)?.[0];
|
|
1219
|
+
return id ? `'${id}'` : undefined;
|
|
1220
|
+
})
|
|
1221
|
+
.filter((id) => Boolean(id))
|
|
1222
|
+
.join(',');
|
|
1223
|
+
return {
|
|
1224
|
+
field: parentSectionTypeKey === 'Hazard' ? 'hazardId' : 'hazardRiskId',
|
|
1225
|
+
operator: { type: 'in' },
|
|
1226
|
+
value: `{{ [${dependentSectionTypeIds}].includes(context.eval('${sectionTypeIdPath}')) ? ((context.eval('${parentIdsContextKey}') || []).length ? context.eval('${parentIdsContextKey}') : undefined) : undefined }}`,
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
function buildSectionTypeDependentHazardFilter(sectionTypeIdPath, parentHazardIdsContextKey) {
|
|
1230
|
+
return buildSectionTypeDependentParentFilter(sectionTypeIdPath, parentHazardIdsContextKey, 'Hazard');
|
|
1213
1231
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
}
|
|
1248
|
-
seenSectionTypeIds.add(sectionTypeId);
|
|
1249
|
-
deduped.push(section);
|
|
1232
|
+
const sectionItemLookupExpose = [
|
|
1233
|
+
{ source: 'id', target: 'items.{id}' },
|
|
1234
|
+
{ source: 'title', target: 'items.{title}' },
|
|
1235
|
+
{ source: 'code', target: 'items.{code}' },
|
|
1236
|
+
{ source: 'ppeItem.id', target: 'items.{id}' },
|
|
1237
|
+
{ source: 'ppeItem.title', target: 'items.{title}' },
|
|
1238
|
+
{ source: 'ppeItem.code', target: 'items.{code}' },
|
|
1239
|
+
];
|
|
1240
|
+
/** Shared item lookup options for builder item dialog (`sectionType.*` in dialog context). */
|
|
1241
|
+
function buildSafetyPlanSectionItemLookupOptions(entityExpression, options) {
|
|
1242
|
+
const sectionTypeIdPath = options?.sectionTypeIdPath ?? 'sectionType.id';
|
|
1243
|
+
const lookup = {
|
|
1244
|
+
entity: entityExpression,
|
|
1245
|
+
look: 'lookup',
|
|
1246
|
+
multiple: true,
|
|
1247
|
+
allowSearch: true,
|
|
1248
|
+
textField: buildSectionTypeItemTextFieldExpression(sectionTypeIdPath),
|
|
1249
|
+
columns: buildSectionTypeItemColumnsExpression(sectionTypeIdPath),
|
|
1250
|
+
placeholder: options?.defaultPlaceholderKey ?? defaultItemIdsPlaceholderKey,
|
|
1251
|
+
expose: sectionItemLookupExpose,
|
|
1252
|
+
triggers: WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_SECTION_TYPE_TRIGGERS,
|
|
1253
|
+
};
|
|
1254
|
+
const filters = [];
|
|
1255
|
+
if (options?.parentHazardIdsContextKey) {
|
|
1256
|
+
filters.push(buildSectionTypeDependentHazardFilter(sectionTypeIdPath, options.parentHazardIdsContextKey));
|
|
1257
|
+
}
|
|
1258
|
+
if (options?.parentHazardRiskIdsContextKey) {
|
|
1259
|
+
filters.push(buildSectionTypeDependentParentFilter(sectionTypeIdPath, options.parentHazardRiskIdsContextKey, 'HazardRisk'));
|
|
1260
|
+
}
|
|
1261
|
+
if (filters.length === 1) {
|
|
1262
|
+
lookup['filter'] = filters[0];
|
|
1263
|
+
}
|
|
1264
|
+
else if (filters.length > 1) {
|
|
1265
|
+
lookup['filter'] = { logic: 'and', filters };
|
|
1250
1266
|
}
|
|
1267
|
+
return lookup;
|
|
1268
|
+
}
|
|
1269
|
+
/** Lookup options with entity data-selector popup UI (`look: 'lookup'`). */
|
|
1270
|
+
function buildSafetyPlanSectionItemSelectorOptions(entityExpression, options) {
|
|
1251
1271
|
return {
|
|
1252
|
-
...
|
|
1253
|
-
|
|
1254
|
-
...section,
|
|
1255
|
-
sortOrder: index + 1,
|
|
1256
|
-
})),
|
|
1272
|
+
...buildSafetyPlanSectionItemLookupOptions(entityExpression, options),
|
|
1273
|
+
look: 'lookup',
|
|
1257
1274
|
};
|
|
1258
1275
|
}
|
|
1259
|
-
function
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
if (typeSection.isArchived === true) {
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
for (const row of typeSection.sectionTypeIds ?? []) {
|
|
1266
|
-
const id = resolveSectionTypeId(row);
|
|
1267
|
-
if (id) {
|
|
1268
|
-
ids.add(id);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1276
|
+
function resolveSafetyPlanSectionItemColumns(sectionTypeId) {
|
|
1277
|
+
if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards) {
|
|
1278
|
+
return ['code', 'ppeItem.title', 'requirementType'];
|
|
1271
1279
|
}
|
|
1272
|
-
|
|
1280
|
+
if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe) {
|
|
1281
|
+
return ['code', 'hazardRisk.title', 'ppeItem.title', 'requirementType'];
|
|
1282
|
+
}
|
|
1283
|
+
if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.riskMatrix) {
|
|
1284
|
+
return [
|
|
1285
|
+
'code',
|
|
1286
|
+
'title',
|
|
1287
|
+
'matrix',
|
|
1288
|
+
{ name: 'likelihoodIds', visible: false },
|
|
1289
|
+
{ name: 'severityIds', visible: false },
|
|
1290
|
+
{ name: 'likelihoodId', visible: false },
|
|
1291
|
+
{ name: 'severityId', visible: false },
|
|
1292
|
+
{ name: 'matrixOrientation', visible: false },
|
|
1293
|
+
];
|
|
1294
|
+
}
|
|
1295
|
+
return ['code', 'title'];
|
|
1273
1296
|
}
|
|
1274
|
-
function
|
|
1275
|
-
return
|
|
1276
|
-
.filter((row) => row.isArchived !== true && isSafetyPlanTypeSectionDefault(row))
|
|
1277
|
-
.map((row) => row.id)
|
|
1278
|
-
.filter((id) => typeof id === 'string' && id.length > 0);
|
|
1297
|
+
function resolveSafetyPlanSectionItemEntityKey(sectionTypeId, sectionType) {
|
|
1298
|
+
return resolveSectionItemEntityName(sectionTypeId, sectionType);
|
|
1279
1299
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
if (!sectionTypeId ||
|
|
1294
|
-
isSafetyPlanSectionTypeRepeatable(sectionTypeId, {
|
|
1295
|
-
repeatableSectionTypeIds,
|
|
1296
|
-
sectionType: row.sectionType,
|
|
1297
|
-
})) {
|
|
1298
|
-
continue;
|
|
1299
|
-
}
|
|
1300
|
-
const sectionTypeTitle = resolveSectionTypeTitle(sectionTypeId, row.sectionType);
|
|
1301
|
-
if (seen.has(sectionTypeId)) {
|
|
1302
|
-
issues.push({
|
|
1303
|
-
code: 'INVALID_TYPE_SECTION_TEMPLATE',
|
|
1304
|
-
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.invalid-type-template-duplicate',
|
|
1305
|
-
details: {
|
|
1306
|
-
sectionTypeId,
|
|
1307
|
-
sectionTypeTitle,
|
|
1308
|
-
sourceSafetyPlanTypeSectionId: typeSection.id,
|
|
1309
|
-
previousSourceSafetyPlanTypeSectionId: seen.get(sectionTypeId)?.sourceSafetyPlanTypeSectionId,
|
|
1310
|
-
},
|
|
1311
|
-
});
|
|
1312
|
-
continue;
|
|
1313
|
-
}
|
|
1314
|
-
seen.set(sectionTypeId, {
|
|
1315
|
-
title: sectionTypeTitle,
|
|
1316
|
-
sourceSafetyPlanTypeSectionId: typeSection.id,
|
|
1317
|
-
});
|
|
1300
|
+
function mapSafetyPlanSectionSelectedItems(sectionTypeId, selectedItems) {
|
|
1301
|
+
const isPpeHazards = sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards;
|
|
1302
|
+
const isHazardRiskPpe = sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe;
|
|
1303
|
+
return selectedItems
|
|
1304
|
+
.map((item) => {
|
|
1305
|
+
if (isPpeHazards || isHazardRiskPpe) {
|
|
1306
|
+
const ppeItem = item['ppeItem'];
|
|
1307
|
+
const id = String(ppeItem?.['id'] ?? item['id'] ?? '');
|
|
1308
|
+
return {
|
|
1309
|
+
id,
|
|
1310
|
+
title: asSafetyPlanDisplayText(ppeItem?.['title']) || undefined,
|
|
1311
|
+
code: asSafetyPlanDisplayText(ppeItem?.['code']) || undefined,
|
|
1312
|
+
};
|
|
1318
1313
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1314
|
+
return {
|
|
1315
|
+
id: String(item['id'] ?? ''),
|
|
1316
|
+
title: asSafetyPlanDisplayText(item['title']) || undefined,
|
|
1317
|
+
code: asSafetyPlanDisplayText(item['code']) || undefined,
|
|
1318
|
+
};
|
|
1319
|
+
})
|
|
1320
|
+
.filter((item) => item.id.length > 0);
|
|
1321
1321
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
.filter((id) => typeof id === 'string' && id.length > 0));
|
|
1330
|
-
for (const requiredSourceId of context.requiredSourceTypeSectionIds) {
|
|
1331
|
-
if (!presentSourceIds.has(requiredSourceId)) {
|
|
1332
|
-
issues.push({
|
|
1333
|
-
code: 'REQUIRED_SECTION_MISSING',
|
|
1334
|
-
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.required-section-missing',
|
|
1335
|
-
details: { sourceSafetyPlanTypeSectionId: requiredSourceId },
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
const allowed = new Set(context.allowedSectionTypeIds);
|
|
1340
|
-
const seenSectionTypeIds = new Set();
|
|
1341
|
-
const sortOrders = [];
|
|
1342
|
-
for (const section of sections) {
|
|
1343
|
-
const sectionTypeId = resolveEntityRefId(section.sectionTypeId) ??
|
|
1344
|
-
String(section.sectionTypeId ?? '').trim();
|
|
1345
|
-
if (!sectionTypeId) {
|
|
1322
|
+
function dedupeSafetyPlanSectionItemSelection(item) {
|
|
1323
|
+
const seen = new Set();
|
|
1324
|
+
const itemIds = [];
|
|
1325
|
+
const items = [];
|
|
1326
|
+
for (let index = 0; index < (item.itemIds?.length ?? 0); index += 1) {
|
|
1327
|
+
const id = item.itemIds?.[index];
|
|
1328
|
+
if (typeof id !== 'string' || !id || seen.has(id)) {
|
|
1346
1329
|
continue;
|
|
1347
1330
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
});
|
|
1357
|
-
}
|
|
1358
|
-
const isRepeatable = isSafetyPlanSectionTypeRepeatable(sectionTypeId, {
|
|
1359
|
-
repeatableSectionTypeIds: context.repeatableSectionTypeIds,
|
|
1360
|
-
sectionType,
|
|
1361
|
-
});
|
|
1362
|
-
if (!isRepeatable && seenSectionTypeIds.has(sectionTypeId)) {
|
|
1363
|
-
issues.push({
|
|
1364
|
-
code: 'DUPLICATE_NON_REPEATABLE_SECTION',
|
|
1365
|
-
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.duplicate-non-repeatable',
|
|
1366
|
-
details: { sectionTypeId, sectionTypeTitle, sectionId: section.id },
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
seenSectionTypeIds.add(sectionTypeId);
|
|
1370
|
-
const sortOrder = section.sortOrder;
|
|
1371
|
-
if (typeof sortOrder === 'number' && Number.isFinite(sortOrder)) {
|
|
1372
|
-
sortOrders.push(sortOrder);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
if (sortOrders.length !== sections.length) {
|
|
1376
|
-
issues.push({
|
|
1377
|
-
code: 'INVALID_SORT_ORDER',
|
|
1378
|
-
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.invalid-sort-order',
|
|
1331
|
+
seen.add(id);
|
|
1332
|
+
itemIds.push(id);
|
|
1333
|
+
const snapshot = item.items?.find((entry) => entry?.id === id) ??
|
|
1334
|
+
(item.items?.[index]?.id === id || !item.items?.[index]?.id ? item.items?.[index] : undefined);
|
|
1335
|
+
items.push({
|
|
1336
|
+
id,
|
|
1337
|
+
title: asSafetyPlanDisplayText(snapshot?.title) || undefined,
|
|
1338
|
+
code: asSafetyPlanDisplayText(snapshot?.code) || undefined,
|
|
1379
1339
|
});
|
|
1380
1340
|
}
|
|
1381
|
-
|
|
1382
|
-
const unique = new Set(sortOrders);
|
|
1383
|
-
if (unique.size !== sortOrders.length) {
|
|
1384
|
-
issues.push({
|
|
1385
|
-
code: 'INVALID_SORT_ORDER',
|
|
1386
|
-
messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.invalid-sort-order',
|
|
1387
|
-
details: { reason: 'duplicate' },
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
return issues;
|
|
1341
|
+
return { ...item, itemIds, items };
|
|
1392
1342
|
}
|
|
1393
|
-
function
|
|
1394
|
-
return
|
|
1395
|
-
sections: structure.sections.map((section) => ({
|
|
1396
|
-
id: section.id,
|
|
1397
|
-
sectionTypeId: section.sectionTypeId,
|
|
1398
|
-
sectionType: section.sectionType,
|
|
1399
|
-
title: section.title,
|
|
1400
|
-
description: section.description ?? '',
|
|
1401
|
-
safetyPlanId: section.safetyPlanId,
|
|
1402
|
-
code: section.code,
|
|
1403
|
-
sourceSafetyPlanTypeSectionId: section.sourceSafetyPlanTypeSectionId,
|
|
1404
|
-
sortOrder: section.sortOrder,
|
|
1405
|
-
required: section.required,
|
|
1406
|
-
items: section.items.map((item) => ({
|
|
1407
|
-
id: item.id,
|
|
1408
|
-
itemIds: [...(item.itemIds ?? [])],
|
|
1409
|
-
items: item.items ? [...item.items] : [],
|
|
1410
|
-
})),
|
|
1411
|
-
})),
|
|
1412
|
-
};
|
|
1343
|
+
function isPpeRuleSectionTypeId(sectionTypeId) {
|
|
1344
|
+
return isPpeRuleSectionType(sectionTypeId);
|
|
1413
1345
|
}
|
|
1414
1346
|
//#endregion
|
|
1415
1347
|
|
|
1348
|
+
/** Component key for the safety plan Sections page tab. Register via AXP_PAGE_COMPONENT_PROVIDER. */
|
|
1349
|
+
const SAFETY_PLAN_SECTIONS_PAGE_COMPONENT_KEY = 'safety-plan-sections-page';
|
|
1350
|
+
|
|
1351
|
+
//#region ---- Safety plan section builder value types ----
|
|
1352
|
+
//#endregion
|
|
1353
|
+
|
|
1416
1354
|
//#region ---- Imports ----
|
|
1417
1355
|
//#endregion
|
|
1418
1356
|
//#region ---- Local helpers ----
|
|
1419
1357
|
function nextId() {
|
|
1420
|
-
return
|
|
1421
|
-
? crypto.randomUUID()
|
|
1422
|
-
: `tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
1358
|
+
return createRandomUuid();
|
|
1423
1359
|
}
|
|
1424
1360
|
function resolveRefId(value) {
|
|
1425
1361
|
if (typeof value === 'string') {
|
|
@@ -1607,9 +1543,6 @@ var AXMHazardRegisterContextType;
|
|
|
1607
1543
|
//#region ---- Imports ----
|
|
1608
1544
|
//#endregion
|
|
1609
1545
|
|
|
1610
|
-
//#region ---- Imports ----
|
|
1611
|
-
//#endregion
|
|
1612
|
-
|
|
1613
1546
|
//#endregion
|
|
1614
1547
|
|
|
1615
1548
|
//#region ---- Imports ----
|
|
@@ -1642,5 +1575,5 @@ var AXMHazardRegisterContextType;
|
|
|
1642
1575
|
* Generated bundle index. Do not edit.
|
|
1643
1576
|
*/
|
|
1644
1577
|
|
|
1645
|
-
export { AXMHazardPpeRuleRequirementType, AXMHazardRegisterContextType, AXMWorkplaceSafetyManagementCommandKeys, AXMWorkplaceSafetyManagementMenuKeys, AXMWorkplaceSafetyManagementPermissionKeys, AXPSafetyRiskPermissionKeys, PARENT_HAZARD_IDS_CONTEXT_KEY, PARENT_HAZARD_RISK_IDS_CONTEXT_KEY, RISK_MANAGEMENT_HAZARD_CATALOG_SCOPE, RISK_MANAGEMENT_RISK_MATRIX_CELL_ENTITY, RISK_MANAGEMENT_RISK_RATING_ENTITY, RootConfig, SAFETY_PLAN_SECTIONS_PAGE_COMPONENT_KEY,
|
|
1578
|
+
export { AXMHazardPpeRuleRequirementType, AXMHazardRegisterContextType, AXMWorkplaceSafetyManagementCommandKeys, AXMWorkplaceSafetyManagementMenuKeys, AXMWorkplaceSafetyManagementPermissionKeys, AXPSafetyRiskPermissionKeys, PARENT_HAZARD_IDS_CONTEXT_KEY, PARENT_HAZARD_RISK_IDS_CONTEXT_KEY, RISK_MANAGEMENT_HAZARD_CATALOG_SCOPE, RISK_MANAGEMENT_RISK_MATRIX_CELL_ENTITY, RISK_MANAGEMENT_RISK_RATING_ENTITY, RootConfig, SAFETY_PLAN_SECTIONS_PAGE_COMPONENT_KEY, WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID, WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS, WORKPLACE_SAFETY_BUILT_IN_WORK_STEP_TYPE_IDS, WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD, WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD_RISK, WORKPLACE_SAFETY_GHS_SYMBOL_OPTIONS, WORKPLACE_SAFETY_HAZARD_PPE_RULE_REQUIREMENT_TYPE_OPTIONS, WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_CLEAR_ON_SECTION_TYPE_TRIGGERS, WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_REFRESH_ON_SECTION_TYPE_EXPOSE_TRIGGERS, WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_SECTION_TYPE_TRIGGERS, WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES, WORKPLACE_SAFETY_SECTION_TYPE_DESCRIPTION_SYNC_TRIGGERS, WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE, WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_EXPRESSION, WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_EXPRESSION_SIBLING, WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_FALLBACK, WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_EXPOSE, WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_WIDGET_OPTIONS, WORKPLACE_SAFETY_SECTION_TYPE_TITLE_SYNC_TRIGGERS, WORKPLACE_SAFETY_TEMPLATE_TYPE_OPTIONS, asSafetyPlanDisplayText, buildSafetyPlanSectionItemLookupOptions, buildSafetyPlanSectionItemSelectorOptions, buildSectionStructureFromTypeSections, buildSectionTypeRelationSnapshot, buildWorkStepContentFromType, clearDependentItemsOnParentChange, collectAllowedSectionTypeIds, collectBuiltInRepeatableSectionTypeIds, collectRequiredSourceTypeSectionIds, createEmptyStepTableCell, createEmptyStepTableRow, dedupeSafetyPlanSectionItemSelection, dedupeSafetyPlanSectionStructure, enrichSectionTypeSnapshot, extractRiskMatrixIdsFromHazardRisks, getSectionTypeDependency, isPpeRuleSectionTypeId, isSafetyPlanSectionTypeRepeatable, isSafetyPlanTypeSectionDefault, isStepTableColumnMultiLookup, isStepTableSectionType, mapSafetyPlanSectionSelectedItems, normalizeStepTableColumns, normalizeStepTableRows, resolveEntityRefId, resolveParentHazardIds, resolveParentHazardRiskIds, resolveParentItemIds, resolveSafetyPlanSectionItemColumns, resolveSafetyPlanSectionItemEntityKey, resolveSafetyPlanSectionItemFilter, resolveSafetyPlanSectionItemFilterAsync, resolveSafetyPlanSectionPersistedTitle, resolveSectionItemEntityName, resolveSectionTypeKey, resolveStepTableColumnLookupEntity, sectionTypeKeyFromSection, sortStepTableColumns, stepTableColumnKey, stepTableColumnTitle, toBuilderValueFromStructure, toWorkStepBuilderPayload, validateSafetyPlanSectionStructure, validateSafetyPlanTypeSectionTemplate };
|
|
1646
1579
|
//# sourceMappingURL=acorex-platform-module-workplacesafety-management-client-contracts.mjs.map
|