@acorex-platform/module-workplacesafety-management-client 0.0.21

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.
Files changed (27) hide show
  1. package/fesm2022/acorex-platform-module-workplacesafety-management-client-contracts.mjs +1646 -0
  2. package/fesm2022/acorex-platform-module-workplacesafety-management-client-contracts.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-C-qrrqOV.mjs +203 -0
  4. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-C-qrrqOV.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-BBhH7KP1.mjs +119 -0
  6. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-BBhH7KP1.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-ppe-rule-status.provider-o4bAwYcq.mjs +42 -0
  8. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-ppe-rule-status.provider-o4bAwYcq.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-ppe-rule-status.provider-DtqzcWh0.mjs +42 -0
  10. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-ppe-rule-status.provider-DtqzcWh0.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-status.provider-B6IkRYbW.mjs +42 -0
  12. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-status.provider-B6IkRYbW.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs +1900 -0
  14. package/fesm2022/acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-C2X3VpEt.mjs +175 -0
  16. package/fesm2022/acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-C2X3VpEt.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-module-workplacesafety-management-client-publish-safety-plan.command-Ci-zcrmO.mjs +106 -0
  18. package/fesm2022/acorex-platform-module-workplacesafety-management-client-publish-safety-plan.command-Ci-zcrmO.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-module-workplacesafety-management-client-safety-plan-status.provider-BpWx3gIf.mjs +51 -0
  20. package/fesm2022/acorex-platform-module-workplacesafety-management-client-safety-plan-status.provider-BpWx3gIf.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs +461 -0
  22. package/fesm2022/acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-module-workplacesafety-management-client.mjs +461 -0
  24. package/fesm2022/acorex-platform-module-workplacesafety-management-client.mjs.map +1 -0
  25. package/package.json +28 -0
  26. package/types/acorex-platform-module-workplacesafety-management-client-contracts.d.ts +1111 -0
  27. package/types/acorex-platform-module-workplacesafety-management-client.d.ts +20 -0
@@ -0,0 +1,1646 @@
1
+ //#region ---- GHS symbol options ----
2
+ /** Asset base path for GHS pictograms (served from demo app assets). */
3
+ const GHS_IMAGE_BASE = '/assets/workplacesafety-management/ghs';
4
+ const WORKPLACE_SAFETY_GHS_SYMBOL_OPTIONS = [
5
+ {
6
+ id: 'GHS01',
7
+ title: 'GHS01 — Exploding bomb',
8
+ description: 'Explosion, blast or projection hazard.',
9
+ imageUrl: `${GHS_IMAGE_BASE}/explos.gif`,
10
+ },
11
+ {
12
+ id: 'GHS02',
13
+ title: 'GHS02 — Flame',
14
+ description: 'Flammable liquids, solids and gases; including self-reactive substances.',
15
+ imageUrl: `${GHS_IMAGE_BASE}/flamme.gif`,
16
+ },
17
+ {
18
+ id: 'GHS03',
19
+ title: 'GHS03 — Flame over circle',
20
+ description: 'Oxidising liquids, solids and gases; may cause or intensify fire.',
21
+ imageUrl: `${GHS_IMAGE_BASE}/rondflam.gif`,
22
+ },
23
+ {
24
+ id: 'GHS04',
25
+ title: 'GHS04 — Gas cylinder',
26
+ description: 'Gases under pressure.',
27
+ imageUrl: `${GHS_IMAGE_BASE}/bottle.gif`,
28
+ },
29
+ {
30
+ id: 'GHS05',
31
+ title: 'GHS05 — Skull and crossbones',
32
+ description: 'Fatal or toxic if swallowed, inhaled or in contact with skin.',
33
+ imageUrl: `${GHS_IMAGE_BASE}/skull.gif`,
34
+ },
35
+ {
36
+ id: 'GHS06',
37
+ title: 'GHS06 — Exclamation mark',
38
+ description: 'Low level toxicity; irritant or harmful to health.',
39
+ imageUrl: `${GHS_IMAGE_BASE}/exclam.gif`,
40
+ },
41
+ {
42
+ id: 'GHS07',
43
+ title: 'GHS07 — Corrosion',
44
+ description: 'Corrosive chemicals; may cause severe skin and eye damage.',
45
+ imageUrl: `${GHS_IMAGE_BASE}/acid_red.gif`,
46
+ },
47
+ {
48
+ id: 'GHS08',
49
+ title: 'GHS08 — Health hazard',
50
+ description: 'Chronic health hazards; carcinogenicity, mutagenicity, reproductive toxicity.',
51
+ imageUrl: `${GHS_IMAGE_BASE}/silhouete.gif`,
52
+ },
53
+ {
54
+ id: 'GHS09',
55
+ title: 'GHS09 — Environment',
56
+ description: 'Hazardous to aquatic life and the environment.',
57
+ imageUrl: `${GHS_IMAGE_BASE}/Aquatic-pollut-red.gif`,
58
+ },
59
+ ];
60
+ //#endregion
61
+
62
+ /**
63
+ * Safety & Risk capabilities used by this module.
64
+ * Domain capability keys for this module. Root groups live in `AXPCommonPermissionGroupKeys` (common contracts).
65
+ */
66
+ const AXPSafetyRiskPermissionKeys = {
67
+ Access: 'SafetyRisk:Permission:Access',
68
+ MaintainSafety: 'SafetyRisk:Permission:MaintainSafety',
69
+ MaintainRisk: 'SafetyRisk:Permission:MaintainRisk',
70
+ MaintainHealth: 'SafetyRisk:Permission:MaintainHealth',
71
+ MaintainAssessments: 'SafetyRisk:Permission:MaintainAssessments',
72
+ Setup: 'SafetyRisk:Permission:Setup',
73
+ };
74
+ /**
75
+ * @deprecated Prefer {@link AXPSafetyRiskPermissionKeys}.
76
+ * Legacy nested shape kept so existing imports compile during migration.
77
+ */
78
+ const AXMWorkplaceSafetyManagementPermissionKeys = {
79
+ WorkplaceSafetyManagement: {
80
+ Management: AXPSafetyRiskPermissionKeys.Access,
81
+ Hazard: {
82
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
83
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
84
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
85
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
86
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
87
+ },
88
+ HazardRegister: {
89
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
90
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
91
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
92
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
93
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
94
+ },
95
+ PpeItem: {
96
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
97
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
98
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
99
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
100
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
101
+ },
102
+ HazardPpeRule: {
103
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
104
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
105
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
106
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
107
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
108
+ },
109
+ HazardRisk: {
110
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
111
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
112
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
113
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
114
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
115
+ },
116
+ HazardRiskPpeRule: {
117
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
118
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
119
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
120
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
121
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
122
+ },
123
+ ControlHierarchyLevels: {
124
+ Management: AXPSafetyRiskPermissionKeys.Setup,
125
+ },
126
+ ControlMeasure: {
127
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
128
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
129
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
130
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
131
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
132
+ },
133
+ SafetyPlan: {
134
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
135
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
136
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
137
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
138
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
139
+ },
140
+ SafetyPlanType: {
141
+ Management: 'WorkplaceSafetyManagement:Permission:SafetyPlanType.Management',
142
+ View: 'WorkplaceSafetyManagement:Permission:SafetyPlanType.View',
143
+ Create: 'WorkplaceSafetyManagement:Permission:SafetyPlanType.Create',
144
+ Update: 'WorkplaceSafetyManagement:Permission:SafetyPlanType.Update',
145
+ Delete: 'WorkplaceSafetyManagement:Permission:SafetyPlanType.Delete',
146
+ },
147
+ SafetyPlanTypeSection: {
148
+ Management: 'WorkplaceSafetyManagement:Permission:SafetyPlanTypeSection.Management',
149
+ View: 'WorkplaceSafetyManagement:Permission:SafetyPlanTypeSection.View',
150
+ Create: 'WorkplaceSafetyManagement:Permission:SafetyPlanTypeSection.Create',
151
+ Update: 'WorkplaceSafetyManagement:Permission:SafetyPlanTypeSection.Update',
152
+ Delete: 'WorkplaceSafetyManagement:Permission:SafetyPlanTypeSection.Delete',
153
+ },
154
+ SectionType: {
155
+ Management: AXPSafetyRiskPermissionKeys.Setup,
156
+ View: AXPSafetyRiskPermissionKeys.Setup,
157
+ Create: AXPSafetyRiskPermissionKeys.Setup,
158
+ Update: AXPSafetyRiskPermissionKeys.Setup,
159
+ Delete: AXPSafetyRiskPermissionKeys.Setup,
160
+ },
161
+ SafetyPlanSection: {
162
+ Management: AXPSafetyRiskPermissionKeys.MaintainSafety,
163
+ View: AXPSafetyRiskPermissionKeys.MaintainSafety,
164
+ Create: AXPSafetyRiskPermissionKeys.MaintainSafety,
165
+ Update: AXPSafetyRiskPermissionKeys.MaintainSafety,
166
+ Delete: AXPSafetyRiskPermissionKeys.MaintainSafety,
167
+ },
168
+ },
169
+ };
170
+
171
+ //#region ---- Command keys ----
172
+ const AXMWorkplaceSafetyManagementCommandKeys = {
173
+ SafetyPlan: {
174
+ Publish: 'WorkplaceSafetyManagement.SafetyPlan:Publish',
175
+ ChangeType: 'WorkplaceSafetyManagement.SafetyPlan:ChangeType',
176
+ },
177
+ };
178
+ //#endregion
179
+
180
+ //#region ---- Menu keys ----
181
+ const AXMWorkplaceSafetyManagementMenuKeys = {
182
+ Root: 'WorkplaceSafetyManagement:Menu',
183
+ SafetyPlans: 'WorkplaceSafetyManagement:Menu:SafetyPlans',
184
+ SafetyPlanSections: 'WorkplaceSafetyManagement:Menu:SafetyPlanSections',
185
+ SectionTypes: 'WorkplaceSafetyManagement:Menu:SectionTypes',
186
+ WorkStepTypes: 'WorkplaceSafetyManagement:Menu:WorkStepTypes',
187
+ SafetyPlanTypes: 'WorkplaceSafetyManagement:Menu:SafetyPlanTypes',
188
+ SafetyPlanTypeSections: 'WorkplaceSafetyManagement:Menu:SafetyPlanTypeSections',
189
+ Configuration: 'WorkplaceSafetyManagement:Menu:Configuration',
190
+ };
191
+ //#endregion
192
+
193
+ const config = {
194
+ i18n: 'workplacesafety-management',
195
+ module: 'WorkplaceSafetyManagement',
196
+ };
197
+ const RootConfig = {
198
+ config,
199
+ module: {
200
+ name: config.module,
201
+ title: `@${config.i18n}:module.title`,
202
+ icon: 'fa-light fa-building-shield',
203
+ },
204
+ entities: {
205
+ hazard: {
206
+ name: 'Hazard',
207
+ source: `${config.module}.Hazard`,
208
+ title: `@${config.i18n}:hazards.entities.hazard.title`,
209
+ titlePlural: `@${config.i18n}:hazards.entities.hazard.plural`,
210
+ icon: 'fa-light fa-triangle-exclamation',
211
+ },
212
+ hazardRegister: {
213
+ name: 'HazardRegister',
214
+ source: `${config.module}.HazardRegister`,
215
+ title: `@${config.i18n}:hazard-registers.entities.hazard-register.title`,
216
+ titlePlural: `@${config.i18n}:hazard-registers.entities.hazard-register.plural`,
217
+ icon: 'fa-light fa-clipboard-list',
218
+ },
219
+ ppeItem: {
220
+ name: 'PpeItem',
221
+ source: `${config.module}.PpeItem`,
222
+ title: `@${config.i18n}:ppe-items.entities.ppe-item.title`,
223
+ titlePlural: `@${config.i18n}:ppe-items.entities.ppe-item.plural`,
224
+ icon: 'fa-light fa-helmet-safety',
225
+ },
226
+ hazardPpeRule: {
227
+ name: 'HazardPpeRule',
228
+ source: `${config.module}.HazardPpeRule`,
229
+ title: `@${config.i18n}:hazard-ppe-rules.entities.hazard-ppe-rule.title`,
230
+ titlePlural: `@${config.i18n}:hazard-ppe-rules.entities.hazard-ppe-rule.plural`,
231
+ icon: 'fa-light fa-user-helmet-safety',
232
+ },
233
+ hazardRisk: {
234
+ name: 'HazardRisk',
235
+ source: `${config.module}.HazardRisk`,
236
+ title: `@${config.i18n}:hazard-risks.entities.hazard-risk.title`,
237
+ titlePlural: `@${config.i18n}:hazard-risks.entities.hazard-risk.plural`,
238
+ icon: 'fa-light fa-shield-exclamation',
239
+ },
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
+ controlHierarchyLevels: {
248
+ name: 'ControlHierarchyLevels',
249
+ source: `${config.module}.ControlHierarchyLevels`,
250
+ title: `@${config.i18n}:control-measure-types.entities.control-measure-type.title`,
251
+ titlePlural: `@${config.i18n}:control-measure-types.entities.control-measure-type.plural`,
252
+ icon: 'fa-light fa-layer-group',
253
+ },
254
+ controlMeasure: {
255
+ name: 'ControlMeasure',
256
+ source: `${config.module}.ControlMeasure`,
257
+ title: `@${config.i18n}:control-measures.entities.control-measure.title`,
258
+ titlePlural: `@${config.i18n}:control-measures.entities.control-measure.plural`,
259
+ icon: 'fa-light fa-shield-check',
260
+ },
261
+ safetyPlan: {
262
+ name: 'SafetyPlan',
263
+ source: `${config.module}.SafetyPlan`,
264
+ title: `@${config.i18n}:safety-plans.entities.safety-plan.title`,
265
+ titlePlural: `@${config.i18n}:safety-plans.entities.safety-plan.plural`,
266
+ icon: 'fa-light fa-file-shield',
267
+ },
268
+ safetyPlanType: {
269
+ name: 'SafetyPlanType',
270
+ source: `${config.module}.SafetyPlanType`,
271
+ title: `@${config.i18n}:safety-plan-types.entities.safety-plan-type.title`,
272
+ titlePlural: `@${config.i18n}:safety-plan-types.entities.safety-plan-type.plural`,
273
+ icon: 'fa-light fa-file-lines',
274
+ },
275
+ safetyPlanTypeSection: {
276
+ name: 'SafetyPlanTypeSection',
277
+ source: `${config.module}.SafetyPlanTypeSection`,
278
+ title: `@${config.i18n}:safety-plan-type-sections.entities.safety-plan-type-section.title`,
279
+ titlePlural: `@${config.i18n}:safety-plan-type-sections.entities.safety-plan-type-section.plural`,
280
+ icon: 'fa-light fa-list-ol',
281
+ },
282
+ sectionType: {
283
+ name: 'SectionType',
284
+ source: `${config.module}.SectionType`,
285
+ title: `@${config.i18n}:section-types.entities.section-type.title`,
286
+ titlePlural: `@${config.i18n}:section-types.entities.section-type.plural`,
287
+ icon: 'fa-light fa-list-tree',
288
+ },
289
+ safetyTemplate: {
290
+ name: 'SafetyTemplate',
291
+ source: `${config.module}.SafetyTemplate`,
292
+ title: `@${config.i18n}:safety-templates.entities.safety-template.title`,
293
+ titlePlural: `@${config.i18n}:safety-templates.entities.safety-template.plural`,
294
+ icon: 'fa-light fa-file-lines',
295
+ },
296
+ safetyPlanSection: {
297
+ name: 'SafetyPlanSection',
298
+ source: `${config.module}.SafetyPlanSection`,
299
+ title: `@${config.i18n}:safety-plan-sections.entities.safety-plan-section.title`,
300
+ titlePlural: `@${config.i18n}:safety-plan-sections.entities.safety-plan-section.plural`,
301
+ icon: 'fa-light fa-list-ol',
302
+ },
303
+ workStepType: {
304
+ name: 'WorkStepType',
305
+ source: `${config.module}.WorkStepType`,
306
+ title: `@${config.i18n}:work-step-types.entities.work-step-type.title`,
307
+ titlePlural: `@${config.i18n}:work-step-types.entities.work-step-type.plural`,
308
+ icon: 'fa-light fa-list-tree',
309
+ },
310
+ safetyPlanWorkStep: {
311
+ name: 'SafetyPlanWorkStep',
312
+ source: `${config.module}.SafetyPlanWorkStep`,
313
+ title: `@${config.i18n}:safety-plan-work-steps.entities.safety-plan-work-step.title`,
314
+ titlePlural: `@${config.i18n}:safety-plan-work-steps.entities.safety-plan-work-step.plural`,
315
+ icon: 'fa-light fa-list-check',
316
+ },
317
+ },
318
+ queries: {
319
+ dashboard: {
320
+ openHazardsKpi: `${config.module}:Dashboard:OpenHazardsKpi`,
321
+ highRisksKpi: `${config.module}:Dashboard:HighRisksKpi`,
322
+ activeSafetyPlansKpi: `${config.module}:Dashboard:ActiveSafetyPlansKpi`,
323
+ take5ChecklistsKpi: `${config.module}:Dashboard:Take5ChecklistsKpi`,
324
+ take5CompletedKpi: `${config.module}:Dashboard:Take5CompletedKpi`,
325
+ hazardsByCategoryDonut: `${config.module}:Dashboard:HazardsByCategoryDonut`,
326
+ risksByRatingBar: `${config.module}:Dashboard:RisksByRatingBar`,
327
+ plansByTypeBar: `${config.module}:Dashboard:PlansByTypeBar`,
328
+ hazardsByStatusBar: `${config.module}:Dashboard:HazardsByStatusBar`,
329
+ ppeCoverageDonut: `${config.module}:Dashboard:PpeCoverageDonut`,
330
+ },
331
+ },
332
+ };
333
+
334
+ //#region ---- Imports ----
335
+ //#endregion
336
+ //#region ---- Enums ----
337
+ var AXMHazardPpeRuleRequirementType;
338
+ (function (AXMHazardPpeRuleRequirementType) {
339
+ AXMHazardPpeRuleRequirementType["Required"] = "required";
340
+ AXMHazardPpeRuleRequirementType["Recommended"] = "recommended";
341
+ AXMHazardPpeRuleRequirementType["Conditional"] = "conditional";
342
+ })(AXMHazardPpeRuleRequirementType || (AXMHazardPpeRuleRequirementType = {}));
343
+ //#endregion
344
+
345
+ //#region ---- Hazard PPE rule requirement type options ----
346
+ const WORKPLACE_SAFETY_HAZARD_PPE_RULE_REQUIREMENT_TYPE_OPTIONS = [
347
+ {
348
+ id: AXMHazardPpeRuleRequirementType.Required,
349
+ title: '@workplacesafety-management:hazard-ppe-rules.entities.hazard-ppe-rule.fields.requirement-type.options.required',
350
+ },
351
+ {
352
+ id: AXMHazardPpeRuleRequirementType.Recommended,
353
+ title: '@workplacesafety-management:hazard-ppe-rules.entities.hazard-ppe-rule.fields.requirement-type.options.recommended',
354
+ },
355
+ {
356
+ id: AXMHazardPpeRuleRequirementType.Conditional,
357
+ title: '@workplacesafety-management:hazard-ppe-rules.entities.hazard-ppe-rule.fields.requirement-type.options.conditional',
358
+ },
359
+ ];
360
+ //#endregion
361
+
362
+ /** Risk rating entity key in the risk-management module. */
363
+ const RISK_MANAGEMENT_RISK_RATING_ENTITY = 'RiskManagement.RiskRating';
364
+ /** Risk matrix cell entity key in the risk-management module. */
365
+ const RISK_MANAGEMENT_RISK_MATRIX_CELL_ENTITY = 'RiskManagement.RiskMatrixCell';
366
+ /** Hazard catalog scope for risk matrix lookup filtering. */
367
+ const RISK_MANAGEMENT_HAZARD_CATALOG_SCOPE = 'risk-management.hazard';
368
+
369
+ //#region ---- Safety template type options ----
370
+ const WORKPLACE_SAFETY_TEMPLATE_TYPE_OPTIONS = [
371
+ {
372
+ id: 'SWMS',
373
+ title: '@workplacesafety-management:safety-templates.entities.safety-template.fields.template-type.options.swms',
374
+ },
375
+ {
376
+ id: 'JSA',
377
+ title: '@workplacesafety-management:safety-templates.entities.safety-template.fields.template-type.options.jsa',
378
+ },
379
+ {
380
+ id: 'Forms',
381
+ title: '@workplacesafety-management:safety-templates.entities.safety-template.fields.template-type.options.forms',
382
+ },
383
+ {
384
+ id: 'Permits',
385
+ title: '@workplacesafety-management:safety-templates.entities.safety-template.fields.template-type.options.permits',
386
+ },
387
+ {
388
+ id: 'Plans',
389
+ title: '@workplacesafety-management:safety-templates.entities.safety-template.fields.template-type.options.plans',
390
+ },
391
+ ];
392
+ //#endregion
393
+
394
+ //#region ---- Imports ----
395
+ //#endregion
396
+ //#region ---- Built-in section type IDs ----
397
+ /** Stable IDs for seeded built-in section types (shared by server mocks and client lookups). */
398
+ const WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS = {
399
+ hazard: 'c1d2e3f4-a5b6-4789-c001-000000000001',
400
+ hazardRisk: 'c1d2e3f4-a5b6-4789-c001-000000000002',
401
+ ppe: 'c1d2e3f4-a5b6-4789-c001-000000000003',
402
+ control: 'c1d2e3f4-a5b6-4789-c001-000000000004',
403
+ hierarchyLevel: 'c1d2e3f4-a5b6-4789-c001-000000000005',
404
+ custom: 'c1d2e3f4-a5b6-4789-c001-000000000006',
405
+ ppeHazards: 'c1d2e3f4-a5b6-4789-c001-000000000007',
406
+ riskMatrix: 'c1d2e3f4-a5b6-4789-c001-000000000008',
407
+ hazardRiskPpe: 'c1d2e3f4-a5b6-4789-c001-000000000009',
408
+ stepTable: 'c1d2e3f4-a5b6-4789-c001-00000000000a',
409
+ };
410
+ const moduleName = RootConfig.module.name;
411
+ /** Maps section type codes to the entity that supplies selectable items. */
412
+ const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE = {
413
+ Hazard: `${moduleName}.${RootConfig.entities.hazard.name}`,
414
+ HazardRisk: `${moduleName}.${RootConfig.entities.hazardRisk.name}`,
415
+ Ppe: `${moduleName}.${RootConfig.entities.ppeItem.name}`,
416
+ PpeHazards: `${moduleName}.${RootConfig.entities.hazardPpeRule.name}`,
417
+ HazardRiskPpe: `${moduleName}.${RootConfig.entities.hazardRiskPpeRule.name}`,
418
+ Control: `${moduleName}.${RootConfig.entities.controlMeasure.name}`,
419
+ HierarchyLevel: `${moduleName}.${RootConfig.entities.controlHierarchyLevels.name}`,
420
+ RiskMatrix: 'RiskManagement.RiskMatrixCell',
421
+ Custom: undefined,
422
+ /** Host section for SafetyPlanWorkStep rows (managed by the section builder, not a catalog picker). */
423
+ StepTable: undefined,
424
+ };
425
+ /** Built-in section types resolved by stable section type id. */
426
+ const WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID = {
427
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazard]: {
428
+ sectionTypeKey: 'Hazard',
429
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.Hazard,
430
+ },
431
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRisk]: {
432
+ sectionTypeKey: 'HazardRisk',
433
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.HazardRisk,
434
+ },
435
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppe]: {
436
+ sectionTypeKey: 'Ppe',
437
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.Ppe,
438
+ },
439
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards]: {
440
+ sectionTypeKey: 'PpeHazards',
441
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.PpeHazards,
442
+ },
443
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe]: {
444
+ sectionTypeKey: 'HazardRiskPpe',
445
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.HazardRiskPpe,
446
+ },
447
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.control]: {
448
+ sectionTypeKey: 'Control',
449
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.Control,
450
+ },
451
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hierarchyLevel]: {
452
+ sectionTypeKey: 'HierarchyLevel',
453
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.HierarchyLevel,
454
+ },
455
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.riskMatrix]: {
456
+ sectionTypeKey: 'RiskMatrix',
457
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.RiskMatrix,
458
+ },
459
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.custom]: {
460
+ sectionTypeKey: 'Custom',
461
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.Custom,
462
+ isRepeatable: true,
463
+ },
464
+ [WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.stepTable]: {
465
+ sectionTypeKey: 'StepTable',
466
+ itemEntityName: WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_BY_CODE.StepTable,
467
+ isRepeatable: true,
468
+ },
469
+ };
470
+ /** Default entity so the lookup widget mounts before a section type is chosen. */
471
+ const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_FALLBACK = `${moduleName}.${RootConfig.entities.hazard.name}`;
472
+ //#endregion
473
+ //#region ---- Snapshot helpers ----
474
+ function resolveSectionTypeKey(sectionTypeId, snapshot) {
475
+ const fromSnapshot = snapshot?.sectionTypeKey;
476
+ if (typeof fromSnapshot === 'string' && fromSnapshot.length > 0) {
477
+ return fromSnapshot;
478
+ }
479
+ if (!sectionTypeId) {
480
+ return undefined;
481
+ }
482
+ return WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID[sectionTypeId]
483
+ ?.sectionTypeKey;
484
+ }
485
+ function resolveSectionItemEntityName(sectionTypeId, snapshot) {
486
+ const fromSnapshot = snapshot?.itemEntityName;
487
+ if (typeof fromSnapshot === 'string' && fromSnapshot.length > 0) {
488
+ return fromSnapshot;
489
+ }
490
+ if (!sectionTypeId) {
491
+ return undefined;
492
+ }
493
+ return WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID[sectionTypeId]
494
+ ?.itemEntityName;
495
+ }
496
+ /** Adds runtime lookup metadata (`sectionTypeKey`, `itemEntityName`) from built-in id map. */
497
+ function enrichSectionTypeSnapshot(sectionTypeId, snapshot) {
498
+ if (!sectionTypeId) {
499
+ return snapshot;
500
+ }
501
+ return {
502
+ ...snapshot,
503
+ id: sectionTypeId,
504
+ sectionTypeKey: resolveSectionTypeKey(sectionTypeId, snapshot),
505
+ itemEntityName: resolveSectionItemEntityName(sectionTypeId, snapshot),
506
+ isRepeatable: snapshot?.isRepeatable ??
507
+ WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID[sectionTypeId]?.isRepeatable,
508
+ };
509
+ }
510
+ //#endregion
511
+ //#region ---- Dynamic entity expressions ----
512
+ function buildSectionTypeEntityExpressionById(sectionTypeIdPath) {
513
+ const parts = Object.entries(WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID)
514
+ .filter(([, meta]) => meta.itemEntityName)
515
+ .map(([id, meta]) => `context.eval('${sectionTypeIdPath}') === '${id}' ? '${meta.itemEntityName}'`);
516
+ return `{{ ${parts.join(' : ')} : '${WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_FALLBACK}' }}`;
517
+ }
518
+ function buildSectionTypeEntityExpressionWithSnapshotFallback(sectionTypeIdPath, itemEntityNamePath) {
519
+ const byId = buildSectionTypeEntityExpressionById(sectionTypeIdPath).slice(3, -2);
520
+ return `{{ context.eval('${itemEntityNamePath}') || (${byId}) }}`;
521
+ }
522
+ /**
523
+ * For repeater / nested FormField widgets — `>` walks up to the row where `sectionType` is exposed.
524
+ */
525
+ const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_EXPRESSION = buildSectionTypeEntityExpressionWithSnapshotFallback('>sectionType.id', '>sectionType.itemEntityName');
526
+ /**
527
+ * For flat dialog / dynamic forms — sibling fields share the same context (no parent path).
528
+ */
529
+ const WORKPLACE_SAFETY_SECTION_TYPE_ENTITY_EXPRESSION_SIBLING = buildSectionTypeEntityExpressionWithSnapshotFallback('sectionType.id', 'sectionType.itemEntityName');
530
+ //#endregion
531
+
532
+ //#region ---- Imports ----
533
+ /** Declarative section-type dependencies resolved from prior sections in plan order. */
534
+ const WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES = {
535
+ HazardRisk: {
536
+ kind: 'parentFilter',
537
+ parentSectionTypeKey: 'Hazard',
538
+ filterField: 'hazardId',
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;
630
+ }
631
+ return WORKPLACE_SAFETY_SECTION_TYPE_DEPENDENCIES[sectionTypeKey];
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 [];
641
+ }
642
+ const ids = new Set();
643
+ for (const item of section.items) {
644
+ for (const id of item.itemIds ?? []) {
645
+ if (typeof id === 'string' && id.length > 0) {
646
+ ids.add(id);
647
+ }
648
+ }
649
+ }
650
+ return [...ids];
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('|');
671
+ }
672
+ function shouldClearSectionOnParentChange(section, sectionIndex, changeIndex, parentTypeKey, downstreamTypes) {
673
+ if (sectionIndex < changeIndex) {
674
+ return false;
675
+ }
676
+ const sectionKey = sectionTypeKeyFromSection(section);
677
+ if (!sectionKey || !downstreamTypes.includes(sectionKey)) {
678
+ return false;
679
+ }
680
+ if (sectionIndex === changeIndex && sectionKey === parentTypeKey) {
681
+ return false;
682
+ }
683
+ return section.items.some((item) => (item.itemIds?.length ?? 0) > 0);
684
+ }
685
+ function clearSectionItems(section) {
686
+ return {
687
+ ...section,
688
+ items: section.items.map((item) => ({ ...item, itemIds: [], items: [] })),
689
+ };
690
+ }
691
+ /**
692
+ * When upstream hazard or hazard-risk selections change, clears item rows in later dependent sections.
693
+ */
694
+ function clearDependentItemsOnParentChange(previous, next) {
695
+ if (!next.sections.length) {
696
+ return next;
697
+ }
698
+ let changeIndex = next.sections.length;
699
+ let hazardChanged = false;
700
+ let hazardRiskChanged = false;
701
+ for (let index = 0; index < next.sections.length; index += 1) {
702
+ const prevHazardSig = parentItemSignature(previous.sections, index, 'Hazard');
703
+ const nextHazardSig = parentItemSignature(next.sections, index, 'Hazard');
704
+ if (prevHazardSig !== nextHazardSig) {
705
+ changeIndex = Math.min(changeIndex, index);
706
+ hazardChanged = true;
707
+ }
708
+ const prevRiskSig = parentItemSignature(previous.sections, index, 'HazardRisk');
709
+ const nextRiskSig = parentItemSignature(next.sections, index, 'HazardRisk');
710
+ if (prevRiskSig !== nextRiskSig) {
711
+ changeIndex = Math.min(changeIndex, index);
712
+ hazardRiskChanged = true;
713
+ }
714
+ }
715
+ if (changeIndex === next.sections.length) {
716
+ return next;
717
+ }
718
+ return {
719
+ sections: next.sections.map((section, sectionIndex) => {
720
+ const clearFromHazard = hazardChanged &&
721
+ shouldClearSectionOnParentChange(section, sectionIndex, changeIndex, 'Hazard', WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD);
722
+ const clearFromHazardRisk = hazardRiskChanged &&
723
+ shouldClearSectionOnParentChange(section, sectionIndex, changeIndex, 'HazardRisk', WORKPLACE_SAFETY_DOWNSTREAM_FROM_HAZARD_RISK);
724
+ if (!clearFromHazard && !clearFromHazardRisk) {
725
+ return section;
726
+ }
727
+ return clearSectionItems(section);
728
+ }),
729
+ };
730
+ }
731
+ function extractRiskMatrixIdsFromHazardRisks(records) {
732
+ const ids = new Set();
733
+ for (const record of records) {
734
+ const id = record['riskMatrixId'];
735
+ if (typeof id === 'string' && id.length > 0) {
736
+ ids.add(id);
737
+ }
738
+ }
739
+ return [...ids];
740
+ }
741
+ function extractControlMeasureIdsFromHazardRisks(records) {
742
+ const ids = new Set();
743
+ for (const record of records) {
744
+ const rows = record['controlMeasureIds'];
745
+ if (!Array.isArray(rows)) {
746
+ continue;
747
+ }
748
+ for (const row of rows) {
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
+ }
761
+ }
762
+ return [...ids];
763
+ }
764
+ function buildInFilter(field, values) {
765
+ const unique = [...new Set(values.filter((id) => id.length > 0))];
766
+ if (!unique.length) {
767
+ return null;
768
+ }
769
+ return {
770
+ field,
771
+ operator: { type: 'in' },
772
+ value: unique,
773
+ };
774
+ }
775
+ /** Filter that matches no records when parent context exists but derived IDs are empty. */
776
+ function buildEmptyResultFilter(field) {
777
+ return {
778
+ field,
779
+ operator: { type: 'in' },
780
+ value: ['00000000-0000-0000-0000-000000000000'],
781
+ };
782
+ }
783
+ function resolveSafetyPlanSectionItemFilter(sectionTypeId, parentHazardIds, parentHazardRiskIds = []) {
784
+ const sectionTypeKey = resolveSectionTypeKey(sectionTypeId);
785
+ const dependency = getSectionTypeDependency(sectionTypeKey);
786
+ if (!dependency || dependency.kind !== 'parentFilter') {
787
+ return null;
788
+ }
789
+ const parentIds = dependency.parentSectionTypeKey === 'Hazard' ? parentHazardIds : parentHazardRiskIds;
790
+ if (!parentIds.length) {
791
+ return null;
792
+ }
793
+ return buildInFilter(dependency.filterField, parentIds);
794
+ }
795
+ async function resolveSafetyPlanSectionItemFilterAsync(sectionTypeId, sections, sectionIndex, loadHazardRisks) {
796
+ const sectionTypeKey = resolveSectionTypeKey(sectionTypeId);
797
+ const dependency = getSectionTypeDependency(sectionTypeKey);
798
+ if (!dependency) {
799
+ return null;
800
+ }
801
+ if (dependency.kind === 'parentFilter') {
802
+ const parentIds = resolveParentItemIds(sections, sectionIndex, dependency.parentSectionTypeKey);
803
+ if (!parentIds.length) {
804
+ return null;
805
+ }
806
+ return buildInFilter(dependency.filterField, parentIds);
807
+ }
808
+ const parentIds = resolveParentItemIds(sections, sectionIndex, dependency.parentSectionTypeKey);
809
+ if (!parentIds.length) {
810
+ return null;
811
+ }
812
+ const records = await loadHazardRisks(parentIds);
813
+ const derivedIds = dependency.resolveField === 'riskMatrixId'
814
+ ? extractRiskMatrixIdsFromHazardRisks(records)
815
+ : extractControlMeasureIdsFromHazardRisks(records);
816
+ if (!derivedIds.length) {
817
+ return buildEmptyResultFilter(dependency.filterField);
818
+ }
819
+ return buildInFilter(dependency.filterField, derivedIds);
820
+ }
821
+ const defaultItemIdsPlaceholderKey = '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.item-ids.placeholder';
822
+ /** Clears item selection when section type changes in item dialog context. */
823
+ const WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_CLEAR_ON_SECTION_TYPE_TRIGGERS = [
824
+ {
825
+ event: "{{ events.context('sectionType.id') }}",
826
+ action: "{{ widget.setValue([]); context.set('items', []); }}",
827
+ },
828
+ {
829
+ event: "{{ events.context('sectionTypeId') }}",
830
+ action: "{{ widget.setValue([]); context.set('items', []); }}",
831
+ },
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
+ {
838
+ event: "{{ events.context('sectionType.id') }}",
839
+ action: '{{ widget.refresh(); }}',
840
+ },
841
+ {
842
+ event: "{{ events.context('sectionType.itemEntityName') }}",
843
+ action: '{{ widget.refresh(); }}',
844
+ },
845
+ {
846
+ event: "{{ events.context('sectionTypeId') }}",
847
+ action: '{{ widget.refresh(); }}',
848
+ },
849
+ ];
850
+ const WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_SECTION_TYPE_TRIGGERS = [
851
+ ...WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_CLEAR_ON_SECTION_TYPE_TRIGGERS,
852
+ ...WORKPLACE_SAFETY_SECTION_ITEM_LOOKUP_REFRESH_ON_SECTION_TYPE_EXPOSE_TRIGGERS,
853
+ ];
854
+ function isPpeRuleSectionType(sectionTypeId) {
855
+ return (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards ||
856
+ sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe);
857
+ }
858
+ function buildSectionTypeItemTextFieldExpression(sectionTypeIdPath) {
859
+ const ppeHazardsId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards;
860
+ const hazardRiskPpeId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe;
861
+ return `{{ [ '${ppeHazardsId}', '${hazardRiskPpeId}' ].includes(context.eval('${sectionTypeIdPath}')) ? 'ppeItem.title' : 'title' }}`;
862
+ }
863
+ function buildSectionTypeItemColumnsExpression(sectionTypeIdPath) {
864
+ const ppeHazardsId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards;
865
+ const hazardRiskPpeId = WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.hazardRiskPpe;
866
+ return `{{ [ '${ppeHazardsId}', '${hazardRiskPpeId}' ].includes(context.eval('${sectionTypeIdPath}')) ? ['code', 'ppeItem.title', 'requirementType'] : ['code', 'title'] }}`;
867
+ }
868
+ function buildSectionTypeDependentParentFilter(sectionTypeIdPath, parentIdsContextKey, parentSectionTypeKey) {
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 };
920
+ }
921
+ return lookup;
922
+ }
923
+ /** Lookup options with entity data-selector popup UI (`look: 'lookup'`). */
924
+ function buildSafetyPlanSectionItemSelectorOptions(entityExpression, options) {
925
+ return {
926
+ ...buildSafetyPlanSectionItemLookupOptions(entityExpression, options),
927
+ look: 'lookup',
928
+ };
929
+ }
930
+ function resolveSafetyPlanSectionItemColumns(sectionTypeId) {
931
+ if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.ppeHazards) {
932
+ return ['code', 'ppeItem.title', 'requirementType'];
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'];
939
+ }
940
+ return ['code', 'title'];
941
+ }
942
+ function resolveSafetyPlanSectionItemEntityKey(sectionTypeId, sectionType) {
943
+ return resolveSectionItemEntityName(sectionTypeId, sectionType);
944
+ }
945
+ function mapSafetyPlanSectionSelectedItems(sectionTypeId, selectedItems) {
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
+ };
958
+ }
959
+ return {
960
+ id: String(item['id'] ?? ''),
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);
966
+ }
967
+ function dedupeSafetyPlanSectionItemSelection(item) {
968
+ const seen = new Set();
969
+ const itemIds = [];
970
+ const items = [];
971
+ for (let index = 0; index < (item.itemIds?.length ?? 0); index += 1) {
972
+ const id = item.itemIds?.[index];
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 });
980
+ }
981
+ return { ...item, itemIds, items };
982
+ }
983
+ function isPpeRuleSectionTypeId(sectionTypeId) {
984
+ return isPpeRuleSectionType(sectionTypeId);
985
+ }
986
+ //#endregion
987
+
988
+ /** Component key for the safety plan Sections page tab. Register via AXP_PAGE_COMPONENT_PROVIDER. */
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 '';
1018
+ }
1019
+ /** Resolves the section type id from a plan section identity or nested snapshot. */
1020
+ function resolveSafetyPlanSectionTypeId(section) {
1021
+ return resolveRefId$1(section.sectionTypeId) || resolveRefId$1(section.sectionType);
1022
+ }
1023
+ /**
1024
+ * True when this section type may appear more than once in a Safety Plan.
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) {
1030
+ return false;
1031
+ }
1032
+ if (options?.sectionType?.isRepeatable === true) {
1033
+ return true;
1034
+ }
1035
+ const allowList = new Set((options?.repeatableSectionTypeIds ?? []).map((entry) => resolveRefId$1(entry)).filter(Boolean));
1036
+ if (allowList.has(id)) {
1037
+ return true;
1038
+ }
1039
+ return WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID[id]?.isRepeatable === true;
1040
+ }
1041
+ /** Built-in section type ids that are always treated as repeatable. */
1042
+ function collectBuiltInRepeatableSectionTypeIds() {
1043
+ return Object.entries(WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_BY_ID)
1044
+ .filter(([, meta]) => meta.isRepeatable === true)
1045
+ .map(([id]) => id);
1046
+ }
1047
+ //#endregion
1048
+ //#region ---- Validation ----
1049
+ /**
1050
+ * Returns the first duplicate non-repeatable `sectionTypeId` in the list, or `null`
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.
1053
+ */
1054
+ function findDuplicateSafetyPlanSectionTypeId(sections, options) {
1055
+ const excludeId = options?.excludeSectionId?.trim() || '';
1056
+ const seen = new Set();
1057
+ for (const section of sections) {
1058
+ const id = resolveRefId$1(section.id);
1059
+ if (excludeId && id === excludeId) {
1060
+ continue;
1061
+ }
1062
+ const sectionTypeId = resolveSafetyPlanSectionTypeId(section);
1063
+ if (!sectionTypeId ||
1064
+ isSafetyPlanSectionTypeRepeatable(sectionTypeId, {
1065
+ repeatableSectionTypeIds: options?.repeatableSectionTypeIds,
1066
+ sectionType: section.sectionType,
1067
+ })) {
1068
+ continue;
1069
+ }
1070
+ if (seen.has(sectionTypeId)) {
1071
+ return sectionTypeId;
1072
+ }
1073
+ seen.add(sectionTypeId);
1074
+ }
1075
+ return null;
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;
1091
+ }
1092
+ const excludeId = options?.excludeSectionId?.trim() || '';
1093
+ return sections.some((section) => {
1094
+ const id = resolveRefId$1(section.id);
1095
+ if (excludeId && id === excludeId) {
1096
+ return false;
1097
+ }
1098
+ return resolveSafetyPlanSectionTypeId(section) === target;
1099
+ });
1100
+ }
1101
+ //#endregion
1102
+
1103
+ //#region ---- Imports ----
1104
+ function resolveSectionTypeTitle(sectionTypeId, sectionType) {
1105
+ if (typeof sectionType?.title === 'string' && sectionType.title.trim()) {
1106
+ return sectionType.title.trim();
1107
+ }
1108
+ if (typeof sectionType?.code === 'string' && sectionType.code.trim()) {
1109
+ return sectionType.code.trim();
1110
+ }
1111
+ return sectionTypeId;
1112
+ }
1113
+ /** True when a type-section row is marked as the plan-type default template. */
1114
+ function isSafetyPlanTypeSectionDefault(row) {
1115
+ const value = row?.isDefault;
1116
+ return value === true || value === 1 || value === 'true';
1117
+ }
1118
+ /** Extracts a stable entity id from a raw lookup / context value. */
1119
+ function resolveEntityRefId(raw) {
1120
+ if (typeof raw === 'string' && raw.trim().length > 0) {
1121
+ return raw.trim();
1122
+ }
1123
+ if (typeof raw === 'number' && Number.isFinite(raw)) {
1124
+ return String(raw);
1125
+ }
1126
+ if (raw && typeof raw === 'object') {
1127
+ const id = raw.id;
1128
+ if (typeof id === 'string' && id.trim().length > 0) {
1129
+ return id.trim();
1130
+ }
1131
+ }
1132
+ return null;
1133
+ }
1134
+ function resolveSectionTypeId(typeRow) {
1135
+ if (!typeRow) {
1136
+ return '';
1137
+ }
1138
+ return (resolveEntityRefId(typeRow.sectionTypeId) ??
1139
+ resolveEntityRefId(typeRow.sectionType) ??
1140
+ resolveEntityRefId(typeRow.sectionType?.id) ??
1141
+ '');
1142
+ }
1143
+ //#endregion
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;
1152
+ }
1153
+ /**
1154
+ * Builds an empty editable section structure from SafetyPlanTypeSection rows for one plan type.
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.
1159
+ */
1160
+ function buildSectionStructureFromTypeSections(typeSections, options) {
1161
+ const createId = options?.createId ?? (() => nextStructureId(''));
1162
+ const active = typeSections
1163
+ .filter((row) => row && row.isArchived !== true)
1164
+ .slice()
1165
+ .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || String(a.title ?? '').localeCompare(String(b.title ?? '')));
1166
+ const defaults = active.filter((row) => isSafetyPlanTypeSectionDefault(row));
1167
+ const sourceRows = defaults.length > 0 ? defaults : active;
1168
+ const sections = [];
1169
+ const seenSourceTypePairs = new Set();
1170
+ let sortOrder = 1;
1171
+ for (const typeSection of sourceRows) {
1172
+ const typeRows = Array.isArray(typeSection.sectionTypeIds) ? typeSection.sectionTypeIds : [];
1173
+ if (!typeRows.length) {
1174
+ continue;
1175
+ }
1176
+ for (const typeRow of typeRows) {
1177
+ const sectionTypeId = resolveSectionTypeId(typeRow);
1178
+ if (!sectionTypeId) {
1179
+ continue;
1180
+ }
1181
+ // One plan section per (type-section source, section type) — prevents
1182
+ // accidental double expansion when the same type is listed twice.
1183
+ const sourceId = typeof typeSection.id === 'string' ? typeSection.id : '';
1184
+ const pairKey = sourceId ? `${sourceId}|${sectionTypeId}` : sectionTypeId;
1185
+ if (seenSourceTypePairs.has(pairKey)) {
1186
+ continue;
1187
+ }
1188
+ seenSourceTypePairs.add(pairKey);
1189
+ const sectionType = typeRow.sectionType
1190
+ ? { id: sectionTypeId, title: typeRow.sectionType.title, code: typeRow.sectionType.code }
1191
+ : { id: sectionTypeId };
1192
+ const sectionId = createId();
1193
+ sections.push({
1194
+ id: sectionId,
1195
+ sectionTypeId,
1196
+ sectionType,
1197
+ title: resolveSectionTypeTitle(sectionTypeId, sectionType),
1198
+ description: '',
1199
+ sourceSafetyPlanTypeSectionId: typeSection.id,
1200
+ sortOrder: sortOrder++,
1201
+ required: isSafetyPlanTypeSectionDefault(typeSection) || defaults.length === 0,
1202
+ items: [
1203
+ {
1204
+ id: `${sectionId}__item_1`,
1205
+ itemIds: [],
1206
+ items: [],
1207
+ },
1208
+ ],
1209
+ });
1210
+ }
1211
+ }
1212
+ return { sections };
1213
+ }
1214
+ /**
1215
+ * Removes accidental duplicate plan sections.
1216
+ * Non-repeatable section types are kept once (first wins); repeatable types may appear more than once.
1217
+ * Reassigns contiguous 1-based `sortOrder` to preserve display order.
1218
+ */
1219
+ function dedupeSafetyPlanSectionStructure(structure, options) {
1220
+ const sections = Array.isArray(structure?.sections)
1221
+ ? structure.sections
1222
+ : [];
1223
+ const seenSectionTypeIds = new Set();
1224
+ const seenSourceTypePairs = new Set();
1225
+ const deduped = [];
1226
+ for (const section of sections) {
1227
+ const sectionTypeId = resolveEntityRefId(section.sectionTypeId) ?? String(section.sectionTypeId ?? '').trim();
1228
+ if (!sectionTypeId) {
1229
+ continue;
1230
+ }
1231
+ const sourceId = String(section.sourceSafetyPlanTypeSectionId ?? '').trim();
1232
+ const sourcePairKey = sourceId ? `${sourceId}|${sectionTypeId}` : '';
1233
+ // Same type-section source + section type must never appear twice (double materialization).
1234
+ if (sourcePairKey && seenSourceTypePairs.has(sourcePairKey)) {
1235
+ continue;
1236
+ }
1237
+ const sectionType = section.sectionType;
1238
+ const isRepeatable = isSafetyPlanSectionTypeRepeatable(sectionTypeId, {
1239
+ repeatableSectionTypeIds: options?.repeatableSectionTypeIds,
1240
+ sectionType,
1241
+ });
1242
+ if (!isRepeatable && seenSectionTypeIds.has(sectionTypeId)) {
1243
+ continue;
1244
+ }
1245
+ if (sourcePairKey) {
1246
+ seenSourceTypePairs.add(sourcePairKey);
1247
+ }
1248
+ seenSectionTypeIds.add(sectionTypeId);
1249
+ deduped.push(section);
1250
+ }
1251
+ return {
1252
+ ...structure,
1253
+ sections: deduped.map((section, index) => ({
1254
+ ...section,
1255
+ sortOrder: index + 1,
1256
+ })),
1257
+ };
1258
+ }
1259
+ function collectAllowedSectionTypeIds(typeSections) {
1260
+ const ids = new Set();
1261
+ for (const typeSection of typeSections) {
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
+ }
1271
+ }
1272
+ return [...ids];
1273
+ }
1274
+ function collectRequiredSourceTypeSectionIds(typeSections) {
1275
+ return typeSections
1276
+ .filter((row) => row.isArchived !== true && isSafetyPlanTypeSectionDefault(row))
1277
+ .map((row) => row.id)
1278
+ .filter((id) => typeof id === 'string' && id.length > 0);
1279
+ }
1280
+ /**
1281
+ * Validates that a plan-type section template does not list the same
1282
+ * non-repeatable section type more than once (across rows and within a single row's repeater).
1283
+ */
1284
+ function validateSafetyPlanTypeSectionTemplate(typeSections, repeatableSectionTypeIds = []) {
1285
+ const seen = new Map();
1286
+ const issues = [];
1287
+ for (const typeSection of typeSections) {
1288
+ if (typeSection.isArchived === true) {
1289
+ continue;
1290
+ }
1291
+ for (const row of typeSection.sectionTypeIds ?? []) {
1292
+ const sectionTypeId = resolveSectionTypeId(row);
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
+ });
1318
+ }
1319
+ }
1320
+ return issues;
1321
+ }
1322
+ //#endregion
1323
+ //#region ---- Validation ----
1324
+ function validateSafetyPlanSectionStructure(structure, context) {
1325
+ const sections = Array.isArray(structure?.sections) ? structure.sections : [];
1326
+ const issues = [];
1327
+ const presentSourceIds = new Set(sections
1328
+ .map((s) => s.sourceSafetyPlanTypeSectionId)
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) {
1346
+ continue;
1347
+ }
1348
+ const sectionType = section
1349
+ .sectionType;
1350
+ const sectionTypeTitle = resolveSectionTypeTitle(sectionTypeId, sectionType);
1351
+ if (allowed.size > 0 && !allowed.has(sectionTypeId)) {
1352
+ issues.push({
1353
+ code: 'SECTION_TYPE_NOT_ALLOWED',
1354
+ messageKey: '@workplacesafety-management:safety-plans.messages.section-structure.section-type-not-allowed',
1355
+ details: { sectionTypeId, sectionTypeTitle, sectionId: section.id },
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',
1379
+ });
1380
+ }
1381
+ else {
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;
1392
+ }
1393
+ function toBuilderValueFromStructure(structure) {
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
+ };
1413
+ }
1414
+ //#endregion
1415
+
1416
+ //#region ---- Imports ----
1417
+ //#endregion
1418
+ //#region ---- Local helpers ----
1419
+ function nextId() {
1420
+ return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
1421
+ ? crypto.randomUUID()
1422
+ : `tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
1423
+ }
1424
+ function resolveRefId(value) {
1425
+ if (typeof value === 'string') {
1426
+ return value.trim();
1427
+ }
1428
+ if (value != null && typeof value === 'object' && 'id' in value) {
1429
+ const id = value.id;
1430
+ return typeof id === 'string' ? id.trim() : '';
1431
+ }
1432
+ return '';
1433
+ }
1434
+ //#endregion
1435
+ //#region ---- Detection ----
1436
+ /** True when the section type is the Step Table host for SafetyPlanWorkStep rows. */
1437
+ function isStepTableSectionType(sectionTypeId, snapshot) {
1438
+ if (sectionTypeId === WORKPLACE_SAFETY_BUILT_IN_SECTION_TYPE_IDS.stepTable) {
1439
+ return true;
1440
+ }
1441
+ return resolveSectionTypeKey(sectionTypeId, snapshot) === 'StepTable';
1442
+ }
1443
+ //#endregion
1444
+ //#region ---- Column helpers ----
1445
+ /** Stable key used on cells for this column (`sectionTypeId`). */
1446
+ function stepTableColumnKey(column) {
1447
+ return resolveRefId(column.sectionTypeId) || resolveRefId(column.sectionType);
1448
+ }
1449
+ function stepTableColumnTitle(column) {
1450
+ const explicit = typeof column.title === 'string' ? column.title.trim() : '';
1451
+ if (explicit) {
1452
+ return explicit;
1453
+ }
1454
+ const fromType = (typeof column.sectionType?.title === 'string' && column.sectionType.title.trim()) ||
1455
+ (typeof column.sectionType?.code === 'string' && column.sectionType.code.trim()) ||
1456
+ '';
1457
+ return fromType || 'Column';
1458
+ }
1459
+ /** Resolves the catalog entity for a column from its SectionType mapping. */
1460
+ function resolveStepTableColumnLookupEntity(column) {
1461
+ const sectionTypeId = stepTableColumnKey(column);
1462
+ return resolveSectionItemEntityName(sectionTypeId, enrichSectionTypeSnapshot(sectionTypeId, column.sectionType));
1463
+ }
1464
+ /** Multi-select when the mapped section type typically holds many items. */
1465
+ function isStepTableColumnMultiLookup(column) {
1466
+ const key = resolveSectionTypeKey(stepTableColumnKey(column), enrichSectionTypeSnapshot(stepTableColumnKey(column), column.sectionType));
1467
+ return key === 'Control' || key === 'Ppe' || key === 'PpeHazards' || key === 'HazardRiskPpe';
1468
+ }
1469
+ function sortStepTableColumns(columns) {
1470
+ return [...columns].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) ||
1471
+ stepTableColumnKey(a).localeCompare(stepTableColumnKey(b)));
1472
+ }
1473
+ function createEmptyStepTableRow(columns, sortOrder = 1) {
1474
+ const ordered = sortStepTableColumns(columns);
1475
+ return {
1476
+ id: nextId(),
1477
+ sortOrder,
1478
+ cells: ordered.map((column) => createEmptyStepTableCell(stepTableColumnKey(column))),
1479
+ };
1480
+ }
1481
+ function createEmptyStepTableCell(columnKey) {
1482
+ return { columnKey, refIds: [], refs: [] };
1483
+ }
1484
+ /** Builds a new work-step payload from a WorkStepType (columns snapshot + one empty row). */
1485
+ function buildWorkStepContentFromType(type) {
1486
+ const columns = normalizeStepTableColumns(type.columns ?? []);
1487
+ return {
1488
+ columns,
1489
+ rows: columns.length > 0 ? [createEmptyStepTableRow(columns, 1)] : [],
1490
+ };
1491
+ }
1492
+ function normalizeStepTableColumns(columns) {
1493
+ if (!Array.isArray(columns)) {
1494
+ return [];
1495
+ }
1496
+ return sortStepTableColumns(columns
1497
+ .filter((column) => column != null && typeof column === 'object')
1498
+ .map((column, index) => {
1499
+ const sectionTypeId = resolveRefId(column.sectionTypeId) || resolveRefId(column.sectionType);
1500
+ const sectionType = enrichSectionTypeSnapshot(sectionTypeId, column.sectionType);
1501
+ const title = typeof column.title === 'string' && column.title.trim()
1502
+ ? column.title.trim()
1503
+ : sectionType?.title;
1504
+ return {
1505
+ sectionTypeId,
1506
+ sectionType,
1507
+ title,
1508
+ sortOrder: typeof column.sortOrder === 'number' ? column.sortOrder : index + 1,
1509
+ required: column.required === true,
1510
+ };
1511
+ })
1512
+ .filter((column) => column.sectionTypeId.length > 0));
1513
+ }
1514
+ function normalizeStepTableRows(rows, columns) {
1515
+ if (!Array.isArray(rows)) {
1516
+ return [];
1517
+ }
1518
+ const columnKeys = new Set(columns.map((column) => stepTableColumnKey(column)).filter(Boolean));
1519
+ return [...rows]
1520
+ .filter((row) => row != null && typeof row === 'object')
1521
+ .map((row, index) => {
1522
+ const id = typeof row.id === 'string' && row.id.trim() ? row.id.trim() : nextId();
1523
+ const cellsByKey = new Map();
1524
+ for (const cell of Array.isArray(row.cells) ? row.cells : []) {
1525
+ if (!cell || typeof cell !== 'object' || typeof cell.columnKey !== 'string') {
1526
+ continue;
1527
+ }
1528
+ cellsByKey.set(cell.columnKey, {
1529
+ columnKey: cell.columnKey,
1530
+ textValue: typeof cell.textValue === 'string' ? cell.textValue : undefined,
1531
+ numberValue: typeof cell.numberValue === 'number' ? cell.numberValue : undefined,
1532
+ refIds: Array.isArray(cell.refIds)
1533
+ ? cell.refIds.filter((id) => typeof id === 'string' && id.length > 0)
1534
+ : [],
1535
+ refs: Array.isArray(cell.refs)
1536
+ ? cell.refs
1537
+ .filter((ref) => ref != null &&
1538
+ typeof ref === 'object' &&
1539
+ typeof ref.id === 'string')
1540
+ .map((ref) => ({
1541
+ id: ref.id,
1542
+ title: typeof ref.title === 'string' ? ref.title : undefined,
1543
+ code: typeof ref.code === 'string' ? ref.code : undefined,
1544
+ }))
1545
+ : [],
1546
+ });
1547
+ }
1548
+ const cells = columns.map((column) => {
1549
+ const key = stepTableColumnKey(column);
1550
+ return cellsByKey.get(key) ?? createEmptyStepTableCell(key);
1551
+ });
1552
+ for (const [key, cell] of cellsByKey) {
1553
+ if (!columnKeys.has(key)) {
1554
+ cells.push(cell);
1555
+ }
1556
+ }
1557
+ return {
1558
+ id,
1559
+ sortOrder: typeof row.sortOrder === 'number' ? row.sortOrder : index + 1,
1560
+ cells,
1561
+ };
1562
+ })
1563
+ .sort((a, b) => a.sortOrder - b.sortOrder);
1564
+ }
1565
+ function toWorkStepBuilderPayload(entity) {
1566
+ const columns = normalizeStepTableColumns(entity.columns);
1567
+ return {
1568
+ id: entity.id,
1569
+ code: entity.code,
1570
+ workStepTypeId: entity.workStepTypeId,
1571
+ workStepType: entity.workStepType,
1572
+ title: entity.title,
1573
+ columns,
1574
+ cells: normalizeStepTableRows([{ id: entity.id, sortOrder: entity.sortOrder ?? 1, cells: entity.cells ?? [] }], columns)[0]?.cells ?? entity.cells ?? [],
1575
+ sortOrder: entity.sortOrder,
1576
+ };
1577
+ }
1578
+ //#endregion
1579
+
1580
+ //#region ---- Built-in work step type IDs ----
1581
+ /** Stable IDs for seeded Work Step Types (shared by server mocks and client). */
1582
+ const WORKPLACE_SAFETY_BUILT_IN_WORK_STEP_TYPE_IDS = {
1583
+ generalTask: 'c1d2e5f4-a5b6-4789-d001-000000000001',
1584
+ sitePreparation: 'c1d2e5f4-a5b6-4789-d001-000000000002',
1585
+ performWork: 'c1d2e5f4-a5b6-4789-d001-000000000003',
1586
+ };
1587
+ //#endregion
1588
+
1589
+ //#region ---- Imports ----
1590
+ //#endregion
1591
+
1592
+ //#region ---- Imports ----
1593
+ //#endregion
1594
+
1595
+ //#region ---- Imports ----
1596
+ //#endregion
1597
+ //#region ---- Enums ----
1598
+ var AXMHazardRegisterContextType;
1599
+ (function (AXMHazardRegisterContextType) {
1600
+ AXMHazardRegisterContextType["Asset"] = "asset";
1601
+ AXMHazardRegisterContextType["Site"] = "site";
1602
+ AXMHazardRegisterContextType["WorkOrder"] = "work-order";
1603
+ AXMHazardRegisterContextType["Project"] = "project";
1604
+ })(AXMHazardRegisterContextType || (AXMHazardRegisterContextType = {}));
1605
+ //#endregion
1606
+
1607
+ //#region ---- Imports ----
1608
+ //#endregion
1609
+
1610
+ //#region ---- Imports ----
1611
+ //#endregion
1612
+
1613
+ //#endregion
1614
+
1615
+ //#region ---- Imports ----
1616
+ //#endregion
1617
+
1618
+ //#region ---- Imports ----
1619
+ //#endregion
1620
+
1621
+ //#region ---- Imports ----
1622
+ //#endregion
1623
+
1624
+ //#region ---- Imports ----
1625
+ //#endregion
1626
+
1627
+ //#region ---- Imports ----
1628
+ //#endregion
1629
+
1630
+ //#region ---- Imports ----
1631
+ //#endregion
1632
+
1633
+ //#region ---- Imports ----
1634
+ //#endregion
1635
+
1636
+ //#region ---- Imports ----
1637
+ //#endregion
1638
+
1639
+ //#region ---- Contracts barrel ----
1640
+
1641
+ /**
1642
+ * Generated bundle index. Do not edit.
1643
+ */
1644
+
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, SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE_CODE, SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE_MESSAGE_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, buildSafetyPlanSectionItemLookupOptions, buildSafetyPlanSectionItemSelectorOptions, buildSectionStructureFromTypeSections, buildWorkStepContentFromType, clearDependentItemsOnParentChange, collectAllowedSectionTypeIds, collectBuiltInRepeatableSectionTypeIds, collectRequiredSourceTypeSectionIds, createEmptyStepTableCell, createEmptyStepTableRow, dedupeSafetyPlanSectionItemSelection, dedupeSafetyPlanSectionStructure, enrichSectionTypeSnapshot, extractControlMeasureIdsFromHazardRisks, extractRiskMatrixIdsFromHazardRisks, findDuplicateSafetyPlanSectionTypeId, getSectionTypeDependency, isPpeRuleSectionTypeId, isSafetyPlanSectionTypeRepeatable, isSafetyPlanTypeSectionDefault, isStepTableColumnMultiLookup, isStepTableSectionType, mapSafetyPlanSectionSelectedItems, normalizeStepTableColumns, normalizeStepTableRows, resolveEntityRefId, resolveParentHazardIds, resolveParentHazardRiskIds, resolveParentItemIds, resolveSafetyPlanSectionItemColumns, resolveSafetyPlanSectionItemEntityKey, resolveSafetyPlanSectionItemFilter, resolveSafetyPlanSectionItemFilterAsync, resolveSafetyPlanSectionTypeId, resolveSectionItemEntityName, resolveSectionTypeKey, resolveStepTableColumnLookupEntity, safetyPlanSectionTypeAlreadyExists, sectionTypeKeyFromSection, sortStepTableColumns, stepTableColumnKey, stepTableColumnTitle, toBuilderValueFromStructure, toWorkStepBuilderPayload, validateSafetyPlanSectionStructure, validateSafetyPlanTypeSectionTemplate };
1646
+ //# sourceMappingURL=acorex-platform-module-workplacesafety-management-client-contracts.mjs.map