@things-factory/operato-wms 4.3.752 → 4.3.755

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 (78) hide show
  1. package/client/pages/inbound/unload-product.js +30 -33
  2. package/client/pages/outbound/loading-product.js +2 -3
  3. package/client/pages/outbound/loading-v2/loading-execution-base.js +568 -56
  4. package/client/pages/outbound/loading-v2/loading-execution.js +258 -8
  5. package/client/pages/outbound/loading-v2/loading.js +82 -12
  6. package/client/pages/outbound/picking-product.js +11 -6
  7. package/client/pages/outbound/picking-v2/picking-execution-base.js +8 -8
  8. package/client/pages/outbound/picking-v2/picking-execution.js +75 -0
  9. package/client/pages/outbound/route-label-history-dialog.js +253 -0
  10. package/client/pages/outbound/route-label-popup.js +637 -131
  11. package/client/pages/outbound/show-ro-list-popup.js +8 -4
  12. package/client/pages/outbound/single-outbound-worksheet.js +55 -6
  13. package/client/pages/outbound/sorting-product.js +71 -25
  14. package/client/pages/outbound/zone-worksheet.js +62 -3
  15. package/dist-server/entities/index.js +9 -0
  16. package/dist-server/entities/index.js.map +1 -0
  17. package/dist-server/entities/route-label-sequence-counter.js +75 -0
  18. package/dist-server/entities/route-label-sequence-counter.js.map +1 -0
  19. package/dist-server/entities/route-label-sequence-log.js +74 -0
  20. package/dist-server/entities/route-label-sequence-log.js.map +1 -0
  21. package/dist-server/graphql/resolvers/index.js +3 -0
  22. package/dist-server/graphql/resolvers/index.js.map +1 -1
  23. package/dist-server/graphql/resolvers/outbound/complete-load.js +40 -3
  24. package/dist-server/graphql/resolvers/outbound/complete-load.js.map +1 -1
  25. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js +50 -11
  26. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.js.map +1 -1
  27. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good.js +90 -48
  28. package/dist-server/graphql/resolvers/outbound/find-loadable-release-good.js.map +1 -1
  29. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js +15 -7
  30. package/dist-server/graphql/resolvers/outbound/loading-worksheet-v2.js.map +1 -1
  31. package/dist-server/graphql/resolvers/outbound/sort-item.js +95 -16
  32. package/dist-server/graphql/resolvers/outbound/sort-item.js.map +1 -1
  33. package/dist-server/graphql/resolvers/route-label/index.js +7 -0
  34. package/dist-server/graphql/resolvers/route-label/index.js.map +1 -0
  35. package/dist-server/graphql/resolvers/route-label/route-label.js +220 -0
  36. package/dist-server/graphql/resolvers/route-label/route-label.js.map +1 -0
  37. package/dist-server/graphql/resolvers/zone-worksheet/index.js.map +1 -1
  38. package/dist-server/graphql/resolvers/zone-worksheet/zone-picking.js +123 -27
  39. package/dist-server/graphql/resolvers/zone-worksheet/zone-picking.js.map +1 -1
  40. package/dist-server/graphql/resolvers/zone-worksheet/zone-worksheet.js.map +1 -1
  41. package/dist-server/graphql/types/index.js +4 -0
  42. package/dist-server/graphql/types/index.js.map +1 -1
  43. package/dist-server/graphql/types/outbound/index.js +7 -1
  44. package/dist-server/graphql/types/outbound/index.js.map +1 -1
  45. package/dist-server/graphql/types/route-label/index.js +14 -0
  46. package/dist-server/graphql/types/route-label/index.js.map +1 -0
  47. package/dist-server/graphql/types/route-label/route-label-sequence.js +53 -0
  48. package/dist-server/graphql/types/route-label/route-label-sequence.js.map +1 -0
  49. package/dist-server/index.js +5 -0
  50. package/dist-server/index.js.map +1 -1
  51. package/dist-server/validators/setting-validators.js +519 -0
  52. package/dist-server/validators/setting-validators.js.map +1 -0
  53. package/helps/operato-wms/outbound/picking-product.en.md +156 -158
  54. package/package.json +50 -50
  55. package/server/entities/index.ts +6 -0
  56. package/server/entities/route-label-sequence-counter.ts +56 -0
  57. package/server/entities/route-label-sequence-log.ts +47 -0
  58. package/server/graphql/resolvers/index.ts +3 -0
  59. package/server/graphql/resolvers/outbound/complete-load.ts +42 -1
  60. package/server/graphql/resolvers/outbound/find-loadable-release-good-by-bin.ts +56 -13
  61. package/server/graphql/resolvers/outbound/find-loadable-release-good.ts +120 -57
  62. package/server/graphql/resolvers/outbound/loading-worksheet-v2.ts +9 -2
  63. package/server/graphql/resolvers/outbound/sort-item.ts +125 -21
  64. package/server/graphql/resolvers/route-label/index.ts +16 -0
  65. package/server/graphql/resolvers/route-label/route-label.ts +264 -0
  66. package/server/graphql/resolvers/zone-worksheet/index.ts +2 -2
  67. package/server/graphql/resolvers/zone-worksheet/zone-picking.ts +156 -39
  68. package/server/graphql/resolvers/zone-worksheet/zone-worksheet.ts +1 -2
  69. package/server/graphql/types/index.ts +4 -0
  70. package/server/graphql/types/outbound/index.ts +7 -1
  71. package/server/graphql/types/route-label/index.ts +18 -0
  72. package/server/graphql/types/route-label/route-label-sequence.ts +50 -0
  73. package/server/index.ts +7 -1
  74. package/server/validators/setting-validators.ts +668 -0
  75. package/translations/en.json +280 -252
  76. package/translations/ko.json +29 -1
  77. package/translations/ms.json +28 -0
  78. package/translations/zh.json +28 -0
@@ -0,0 +1,668 @@
1
+ /**
2
+ * Example: Operato WMS Setting Validators
3
+ *
4
+ * This file demonstrates how to register application-specific validators
5
+ * for settings used in operato-wms.
6
+ */
7
+
8
+ import { settingValidatorRegistry, SettingValidationResult, SettingBatchValidator } from '@things-factory/setting-base'
9
+ import { Domain } from '@things-factory/shell'
10
+ import { User } from '@things-factory/auth-base'
11
+ import { NewSetting, SettingPatch } from '@things-factory/setting-base'
12
+ import { Setting } from '@things-factory/setting-base'
13
+ import { getRepository } from 'typeorm'
14
+
15
+ /**
16
+ * Validate batch-picking-limit setting
17
+ * Ensures the value is a positive number between 1 and 1000
18
+ */
19
+ async function validateBatchPickingLimit(
20
+ setting: NewSetting,
21
+ context: { domain: Domain; user: User }
22
+ ): Promise<SettingValidationResult> {
23
+ if (!setting.value) {
24
+ return { valid: false, error: 'batch-picking-limit value is required' }
25
+ }
26
+
27
+ const limit = parseInt(setting.value, 10)
28
+ if (isNaN(limit) || limit < 1 || limit > 1000) {
29
+ return {
30
+ valid: false,
31
+ error: 'batch-picking-limit must be a number between 1 and 1000'
32
+ }
33
+ }
34
+
35
+ return { valid: true }
36
+ }
37
+
38
+ /**
39
+ * Validate stacking-option-limit setting
40
+ * Ensures the value is a valid JSON with Min and Max properties
41
+ */
42
+ async function validateStackingOptionLimit(
43
+ setting: NewSetting,
44
+ context: { domain: Domain; user: User }
45
+ ): Promise<SettingValidationResult> {
46
+ if (!setting.value) {
47
+ return { valid: false, error: 'stacking-option-limit value is required' }
48
+ }
49
+
50
+ try {
51
+ const parsed = JSON.parse(setting.value)
52
+ if (typeof parsed.Min !== 'number' || typeof parsed.Max !== 'number') {
53
+ return {
54
+ valid: false,
55
+ error: 'stacking-option-limit must have Min and Max as numbers'
56
+ }
57
+ }
58
+ if (parsed.Min < 0 || parsed.Max < parsed.Min) {
59
+ return {
60
+ valid: false,
61
+ error: 'stacking-option-limit: Min must be >= 0 and Max must be >= Min'
62
+ }
63
+ }
64
+ } catch (e) {
65
+ return {
66
+ valid: false,
67
+ error: 'stacking-option-limit must be valid JSON format: { "Min": 1, "Max": 1 }'
68
+ }
69
+ }
70
+
71
+ return { valid: true }
72
+ }
73
+
74
+ /**
75
+ * Validate location sorting rule settings
76
+ * Ensures the value is valid JSON array with name and desc properties
77
+ */
78
+ async function validateLocationSortingRule(
79
+ setting: NewSetting,
80
+ context: { domain: Domain; user: User }
81
+ ): Promise<SettingValidationResult> {
82
+ if (!setting.value) {
83
+ return { valid: true } // Allow empty value
84
+ }
85
+
86
+ try {
87
+ const rules = JSON.parse(setting.value)
88
+ if (!Array.isArray(rules)) {
89
+ return {
90
+ valid: false,
91
+ error: 'Location sorting rule must be a JSON array'
92
+ }
93
+ }
94
+
95
+ for (const rule of rules) {
96
+ if (typeof rule.name !== 'string') {
97
+ return {
98
+ valid: false,
99
+ error: 'Each rule must have a "name" property as string'
100
+ }
101
+ }
102
+ if (typeof rule.desc !== 'boolean') {
103
+ return {
104
+ valid: false,
105
+ error: 'Each rule must have a "desc" property as boolean'
106
+ }
107
+ }
108
+ }
109
+ } catch (e) {
110
+ return {
111
+ valid: false,
112
+ error: 'Location sorting rule must be valid JSON array format'
113
+ }
114
+ }
115
+
116
+ return { valid: true }
117
+ }
118
+
119
+ /**
120
+ * Validate boolean settings (enable/disable flags)
121
+ */
122
+ async function validateBooleanSetting(
123
+ setting: NewSetting,
124
+ context: { domain: Domain; user: User }
125
+ ): Promise<SettingValidationResult> {
126
+ if (setting.value && setting.value !== 'true' && setting.value !== 'false') {
127
+ return {
128
+ valid: false,
129
+ error: `${setting.name} must be "true" or "false"`
130
+ }
131
+ }
132
+
133
+ return { valid: true }
134
+ }
135
+
136
+ /**
137
+ * Validate update for batch-picking-limit
138
+ */
139
+ async function validateBatchPickingLimitUpdate(
140
+ existingSetting: Setting,
141
+ patch: SettingPatch,
142
+ context: { domain: Domain; user: User }
143
+ ): Promise<SettingValidationResult> {
144
+ // Only validate if value is being updated
145
+ if (patch.value === undefined) {
146
+ return { valid: true }
147
+ }
148
+
149
+ return await validateBatchPickingLimit({ ...existingSetting, value: patch.value } as NewSetting, context)
150
+ }
151
+
152
+ /**
153
+ * Validate minimum-seal-number setting
154
+ * Ensures the value is a non-negative integer (0 or above, no decimals)
155
+ */
156
+ async function validateMinimumSealNumber(
157
+ setting: NewSetting,
158
+ context: { domain: Domain; user: User }
159
+ ): Promise<SettingValidationResult> {
160
+ if (!setting.value) {
161
+ return { valid: false, error: 'minimum-seal-number value is required' }
162
+ }
163
+
164
+ // Check if value contains decimal point
165
+ if (setting.value.includes('.')) {
166
+ return {
167
+ valid: false,
168
+ error: 'minimum-seal-number must be an integer (no decimals allowed)'
169
+ }
170
+ }
171
+
172
+ const number = parseInt(setting.value, 10)
173
+
174
+ // Check if parsing resulted in NaN
175
+ if (isNaN(number)) {
176
+ return {
177
+ valid: false,
178
+ error: 'minimum-seal-number must be a valid number'
179
+ }
180
+ }
181
+
182
+ // Check if the parsed value matches the original string (to catch cases like "123abc")
183
+ if (number.toString() !== setting.value.trim()) {
184
+ return {
185
+ valid: false,
186
+ error: 'minimum-seal-number must be a valid integer'
187
+ }
188
+ }
189
+
190
+ // Check if negative
191
+ if (number < 0) {
192
+ return {
193
+ valid: false,
194
+ error: 'minimum-seal-number must be 0 or above (negative numbers not allowed)'
195
+ }
196
+ }
197
+
198
+ return { valid: true }
199
+ }
200
+
201
+ /**
202
+ * Validate update for minimum-seal-number
203
+ */
204
+ async function validateMinimumSealNumberUpdate(
205
+ existingSetting: Setting,
206
+ patch: SettingPatch,
207
+ context: { domain: Domain; user: User }
208
+ ): Promise<SettingValidationResult> {
209
+ // Only validate if value is being updated
210
+ if (patch.value === undefined) {
211
+ return { valid: true }
212
+ }
213
+
214
+ return await validateMinimumSealNumber({ ...existingSetting, value: patch.value } as NewSetting, context)
215
+ }
216
+
217
+ /**
218
+ * Validate enable-tote-scanning setting
219
+ * Ensures the value is a non-negative integer (0 or above, no decimals)
220
+ * 0 = disabled, >= 1 = enabled
221
+ */
222
+ async function validateEnableToteScanning(
223
+ setting: NewSetting,
224
+ context: { domain: Domain; user: User }
225
+ ): Promise<SettingValidationResult> {
226
+ if (!setting.value) {
227
+ return { valid: false, error: 'enable-tote-scanning value is required' }
228
+ }
229
+
230
+ // Check if value contains decimal point
231
+ if (setting.value.includes('.')) {
232
+ return {
233
+ valid: false,
234
+ error: 'enable-tote-scanning must be an integer (no decimals allowed)'
235
+ }
236
+ }
237
+
238
+ const number = parseInt(setting.value, 10)
239
+
240
+ // Check if parsing resulted in NaN
241
+ if (isNaN(number)) {
242
+ return {
243
+ valid: false,
244
+ error: 'enable-tote-scanning must be a valid number'
245
+ }
246
+ }
247
+
248
+ // Check if the parsed value matches the original string (to catch cases like "123abc")
249
+ if (number.toString() !== setting.value.trim()) {
250
+ return {
251
+ valid: false,
252
+ error: 'enable-tote-scanning must be a valid integer'
253
+ }
254
+ }
255
+
256
+ // Check if negative
257
+ if (number < 0) {
258
+ return {
259
+ valid: false,
260
+ error: 'enable-tote-scanning must be 0 or above (negative numbers not allowed)'
261
+ }
262
+ }
263
+
264
+ // Cross-validation: If enable-tote-scanning >= 1, qc-sku must be false
265
+ if (number >= 1) {
266
+ const qcSkuSetting: Setting = await getRepository(Setting).findOne({
267
+ where: { domain: context.domain, name: 'qc-sku' }
268
+ })
269
+
270
+ if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
271
+ return {
272
+ valid: false,
273
+ error: 'enable-tote-scanning cannot be set to 1 or above when qc-sku is enabled. Please disable qc-sku first.'
274
+ }
275
+ }
276
+ }
277
+
278
+ // Cross-validation: If enable-tote-scanning >= 2, disable-qc-in-loading must be false
279
+ if (number >= 2) {
280
+ const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
281
+ where: { domain: context.domain, name: 'disable-QC-in-loading' }
282
+ })
283
+
284
+ if (
285
+ disableQcInLoadingSetting &&
286
+ (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
287
+ ) {
288
+ return {
289
+ valid: false,
290
+ error:
291
+ 'enable-tote-scanning cannot be set to 2 or above when disable-qc-in-loading is true. Please set disable-qc-in-loading to false first.'
292
+ }
293
+ }
294
+ }
295
+
296
+ return { valid: true }
297
+ }
298
+
299
+ /**
300
+ * Validate update for enable-tote-scanning
301
+ */
302
+ async function validateEnableToteScanningUpdate(
303
+ existingSetting: Setting,
304
+ patch: SettingPatch,
305
+ context: { domain: Domain; user: User }
306
+ ): Promise<SettingValidationResult> {
307
+ // Only validate if value is being updated
308
+ if (patch.value === undefined) {
309
+ return { valid: true }
310
+ }
311
+
312
+ const newValue = parseInt(patch.value || '0', 10)
313
+
314
+ // Cross-validation: If enable-tote-scanning is being set to >= 1, qc-sku must be false
315
+ if (newValue >= 1) {
316
+ const qcSkuSetting: Setting = await getRepository(Setting).findOne({
317
+ where: { domain: context.domain, name: 'qc-sku' }
318
+ })
319
+
320
+ if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
321
+ return {
322
+ valid: false,
323
+ error: 'enable-tote-scanning cannot be set to 1 or above when qc-sku is enabled. Please disable qc-sku first.'
324
+ }
325
+ }
326
+ }
327
+
328
+ // Cross-validation: If enable-tote-scanning is being set to >= 2, disable-qc-in-loading must be false
329
+ if (newValue >= 2) {
330
+ const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
331
+ where: { domain: context.domain, name: 'disable-QC-in-loading' }
332
+ })
333
+
334
+ if (
335
+ disableQcInLoadingSetting &&
336
+ (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
337
+ ) {
338
+ return {
339
+ valid: false,
340
+ error:
341
+ 'enable-tote-scanning cannot be set to 2 or above when disable-qc-in-loading is true. Please set disable-qc-in-loading to false first.'
342
+ }
343
+ }
344
+ }
345
+
346
+ return await validateEnableToteScanning({ ...existingSetting, value: patch.value } as NewSetting, context)
347
+ }
348
+
349
+ /**
350
+ * Validate qc-sku setting
351
+ * Ensures qc-sku cannot be enabled when enable-tote-scanning >= 1
352
+ * Ensures qc-sku cannot be enabled when disable-QC-in-loading is true
353
+ */
354
+ async function validateQcSku(
355
+ setting: NewSetting,
356
+ context: { domain: Domain; user: User }
357
+ ): Promise<SettingValidationResult> {
358
+ // Check if qc-sku is being set to true
359
+ if (setting.value === 'true' || setting.value === true) {
360
+ const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
361
+ where: { domain: context.domain, name: 'enable-tote-scanning' }
362
+ })
363
+
364
+ if (enableToteScanningSetting) {
365
+ const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)
366
+
367
+ if (enableToteScanningValue >= 1) {
368
+ return {
369
+ valid: false,
370
+ error:
371
+ 'qc-sku cannot be enabled when enable-tote-scanning is 1 or above. Please set enable-tote-scanning to 0 first.'
372
+ }
373
+ }
374
+ }
375
+
376
+ // Cross-validation: If qc-sku is being set to true, disable-QC-in-loading must be false
377
+ const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
378
+ where: { domain: context.domain, name: 'disable-QC-in-loading' }
379
+ })
380
+
381
+ if (
382
+ disableQcInLoadingSetting &&
383
+ (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
384
+ ) {
385
+ return {
386
+ valid: false,
387
+ error:
388
+ 'qc-sku cannot be enabled when disable-QC-in-loading is true. Please set disable-QC-in-loading to false first.'
389
+ }
390
+ }
391
+ }
392
+
393
+ return { valid: true }
394
+ }
395
+
396
+ /**
397
+ * Validate update for qc-sku
398
+ */
399
+ async function validateQcSkuUpdate(
400
+ existingSetting: Setting,
401
+ patch: SettingPatch,
402
+ context: { domain: Domain; user: User }
403
+ ): Promise<SettingValidationResult> {
404
+ // Only validate if value is being updated
405
+ if (patch.value === undefined) {
406
+ return { valid: true }
407
+ }
408
+
409
+ // Check if qc-sku is being set to true
410
+ if (patch.value === 'true' || patch.value === true) {
411
+ const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
412
+ where: { domain: context.domain, name: 'enable-tote-scanning' }
413
+ })
414
+
415
+ if (enableToteScanningSetting) {
416
+ const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)
417
+
418
+ if (enableToteScanningValue >= 1) {
419
+ return {
420
+ valid: false,
421
+ error:
422
+ 'qc-sku cannot be enabled when enable-tote-scanning is 1 or above. Please set enable-tote-scanning to 0 first.'
423
+ }
424
+ }
425
+ }
426
+
427
+ // Cross-validation: If qc-sku is being set to true, disable-QC-in-loading must be false
428
+ const disableQcInLoadingSetting: Setting = await getRepository(Setting).findOne({
429
+ where: { domain: context.domain, name: 'disable-QC-in-loading' }
430
+ })
431
+
432
+ if (
433
+ disableQcInLoadingSetting &&
434
+ (disableQcInLoadingSetting.value === 'true' || disableQcInLoadingSetting.value === true)
435
+ ) {
436
+ return {
437
+ valid: false,
438
+ error:
439
+ 'qc-sku cannot be enabled when disable-QC-in-loading is true. Please set disable-QC-in-loading to false first.'
440
+ }
441
+ }
442
+ }
443
+
444
+ return await validateQcSku({ ...existingSetting, value: patch.value } as NewSetting, context)
445
+ }
446
+
447
+ /**
448
+ * Validate disable-qc-in-loading setting
449
+ * Ensures disable-qc-in-loading can only be set to true when enable-tote-scanning is 0 or 1
450
+ * Ensures disable-qc-in-loading cannot be set to true when qc-sku is true
451
+ */
452
+ async function validateDisableQcInLoading(
453
+ setting: NewSetting,
454
+ context: { domain: Domain; user: User }
455
+ ): Promise<SettingValidationResult> {
456
+ // Check if disable-qc-in-loading is being set to true
457
+ if (setting.value === 'true' || setting.value === true) {
458
+ const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
459
+ where: { domain: context.domain, name: 'enable-tote-scanning' }
460
+ })
461
+
462
+ if (enableToteScanningSetting) {
463
+ const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)
464
+
465
+ // enable-tote-scanning must be 0 or 1 for disable-qc-in-loading to be true
466
+ if (enableToteScanningValue > 1) {
467
+ return {
468
+ valid: false,
469
+ error:
470
+ 'disable-qc-in-loading cannot be set to true when enable-tote-scanning is 2 or above. Please set enable-tote-scanning to 0 or 1 first.'
471
+ }
472
+ }
473
+ }
474
+
475
+ // Cross-validation: If disable-QC-in-loading is being set to true, qc-sku must be false
476
+ const qcSkuSetting: Setting = await getRepository(Setting).findOne({
477
+ where: { domain: context.domain, name: 'qc-sku' }
478
+ })
479
+
480
+ if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
481
+ return {
482
+ valid: false,
483
+ error: 'disable-QC-in-loading cannot be set to true when qc-sku is enabled. Please set qc-sku to false first.'
484
+ }
485
+ }
486
+ }
487
+
488
+ return { valid: true }
489
+ }
490
+
491
+ /**
492
+ * Validate update for disable-qc-in-loading
493
+ */
494
+ async function validateDisableQcInLoadingUpdate(
495
+ existingSetting: Setting,
496
+ patch: SettingPatch,
497
+ context: { domain: Domain; user: User }
498
+ ): Promise<SettingValidationResult> {
499
+ // Only validate if value is being updated
500
+ if (patch.value === undefined) {
501
+ return { valid: true }
502
+ }
503
+
504
+ // Check if disable-qc-in-loading is being set to true
505
+ if (patch.value === 'true' || patch.value === true) {
506
+ const enableToteScanningSetting: Setting = await getRepository(Setting).findOne({
507
+ where: { domain: context.domain, name: 'enable-tote-scanning' }
508
+ })
509
+
510
+ if (enableToteScanningSetting) {
511
+ const enableToteScanningValue = parseInt(enableToteScanningSetting.value || '0', 10)
512
+
513
+ // enable-tote-scanning must be 0 or 1 for disable-qc-in-loading to be true
514
+ if (enableToteScanningValue > 1) {
515
+ return {
516
+ valid: false,
517
+ error:
518
+ 'disable-qc-in-loading cannot be set to true when enable-tote-scanning is 2 or above. Please set enable-tote-scanning to 0 or 1 first.'
519
+ }
520
+ }
521
+ }
522
+
523
+ // Cross-validation: If disable-QC-in-loading is being set to true, qc-sku must be false
524
+ const qcSkuSetting: Setting = await getRepository(Setting).findOne({
525
+ where: { domain: context.domain, name: 'qc-sku' }
526
+ })
527
+
528
+ if (qcSkuSetting && (qcSkuSetting.value === 'true' || qcSkuSetting.value === true)) {
529
+ return {
530
+ valid: false,
531
+ error: 'disable-QC-in-loading cannot be set to true when qc-sku is enabled. Please set qc-sku to false first.'
532
+ }
533
+ }
534
+ }
535
+
536
+ return await validateDisableQcInLoading({ ...existingSetting, value: patch.value } as NewSetting, context)
537
+ }
538
+
539
+ /**
540
+ * Batch validator for qc-sku and disable-QC-in-loading
541
+ * Validates that both settings cannot be set to true simultaneously
542
+ */
543
+ async function validateQcSkuAndDisableQcInLoadingBatch(
544
+ patches: SettingPatch[],
545
+ existingSettings: Map<string, Setting>,
546
+ context: { domain: Domain; user: User }
547
+ ): Promise<SettingValidationResult> {
548
+ // Find patches for qc-sku and disable-QC-in-loading
549
+ let qcSkuPatch: SettingPatch | undefined
550
+ let disableQcInLoadingPatch: SettingPatch | undefined
551
+ let qcSkuExisting: Setting | undefined
552
+ let disableQcInLoadingExisting: Setting | undefined
553
+
554
+ for (const patch of patches) {
555
+ // Try to find existing setting by id first, then by name
556
+ let existing: Setting | undefined
557
+ if (patch.id) {
558
+ existing = existingSettings.get(patch.id)
559
+ }
560
+
561
+ // If not found by id, try to find by name in the patches
562
+ if (!existing && patch.name) {
563
+ // Look through existing settings map to find by name
564
+ for (const setting of existingSettings.values()) {
565
+ if (setting.name === patch.name) {
566
+ existing = setting
567
+ break
568
+ }
569
+ }
570
+ }
571
+
572
+ if (existing) {
573
+ if (existing.name === 'qc-sku') {
574
+ qcSkuPatch = patch
575
+ qcSkuExisting = existing
576
+ } else if (existing.name === 'disable-QC-in-loading') {
577
+ disableQcInLoadingPatch = patch
578
+ disableQcInLoadingExisting = existing
579
+ }
580
+ } else if (patch.name === 'qc-sku') {
581
+ // Patch might be for a new setting or we need to fetch it
582
+ qcSkuPatch = patch
583
+ qcSkuExisting = await getRepository(Setting).findOne({
584
+ where: { domain: context.domain, name: 'qc-sku' }
585
+ })
586
+ } else if (patch.name === 'disable-QC-in-loading') {
587
+ disableQcInLoadingPatch = patch
588
+ disableQcInLoadingExisting = await getRepository(Setting).findOne({
589
+ where: { domain: context.domain, name: 'disable-QC-in-loading' }
590
+ })
591
+ }
592
+ }
593
+
594
+ // Get the effective values (new value from patch if present, otherwise existing value)
595
+ const qcSkuValue = qcSkuPatch?.value !== undefined ? qcSkuPatch.value : qcSkuExisting?.value
596
+ const disableQcInLoadingValue =
597
+ disableQcInLoadingPatch?.value !== undefined ? disableQcInLoadingPatch.value : disableQcInLoadingExisting?.value
598
+
599
+ // Check if both are being set to true simultaneously
600
+ const qcSkuIsTrue = qcSkuValue === 'true' || qcSkuValue === true
601
+ const disableQcInLoadingIsTrue = disableQcInLoadingValue === 'true' || disableQcInLoadingValue === true
602
+
603
+ if (qcSkuIsTrue && disableQcInLoadingIsTrue) {
604
+ return {
605
+ valid: false,
606
+ error:
607
+ 'qc-sku and disable-QC-in-loading cannot both be set to true simultaneously. Please set one to false first.'
608
+ }
609
+ }
610
+
611
+ return { valid: true }
612
+ }
613
+
614
+ /**
615
+ * Register all operato-wms validators
616
+ * This function should be called during application bootstrap
617
+ */
618
+ export function registerOperatoWmsSettingValidators(): void {
619
+ // Register validators for specific setting names
620
+ settingValidatorRegistry.registerCreateValidator('batch-picking-limit', validateBatchPickingLimit)
621
+ settingValidatorRegistry.registerUpdateValidator('batch-picking-limit', validateBatchPickingLimitUpdate)
622
+
623
+ settingValidatorRegistry.registerCreateValidator('stacking-option-limit', validateStackingOptionLimit)
624
+ settingValidatorRegistry.registerUpdateValidator('stacking-option-limit', validateStackingOptionLimit)
625
+
626
+ settingValidatorRegistry.registerCreateValidator('minimum-seal-number', validateMinimumSealNumber)
627
+ settingValidatorRegistry.registerUpdateValidator('minimum-seal-number', validateMinimumSealNumberUpdate)
628
+
629
+ settingValidatorRegistry.registerCreateValidator('enable-tote-scanning', validateEnableToteScanning)
630
+ settingValidatorRegistry.registerUpdateValidator('enable-tote-scanning', validateEnableToteScanningUpdate)
631
+
632
+ settingValidatorRegistry.registerCreateValidator('qc-sku', validateQcSku)
633
+ settingValidatorRegistry.registerUpdateValidator('qc-sku', validateQcSkuUpdate)
634
+
635
+ settingValidatorRegistry.registerCreateValidator('disable-QC-in-loading', validateDisableQcInLoading)
636
+ settingValidatorRegistry.registerUpdateValidator('disable-QC-in-loading', validateDisableQcInLoadingUpdate)
637
+
638
+ // Register batch validator for cross-validation between qc-sku and disable-QC-in-loading
639
+ settingValidatorRegistry.registerBatchValidator(validateQcSkuAndDisableQcInLoadingBatch)
640
+
641
+ // Register validators for location sorting rules using pattern matching
642
+ settingValidatorRegistry.registerPatternValidator(
643
+ (name: string) => name.startsWith('rule-for-'),
644
+ validateLocationSortingRule,
645
+ validateLocationSortingRule
646
+ )
647
+
648
+ // Register validators for boolean settings using pattern matching
649
+ const booleanSettings = [
650
+ 'enable-bin-picking',
651
+ 'enable-product-scanning',
652
+ 'enable-beta-feature',
653
+ 'enable-carton-label',
654
+ 'enable-item-label',
655
+ 'hide-cross-dock',
656
+ 'disable-reusable-pallet',
657
+ 'enable-direct-activate-packing-worksheet',
658
+ 'enable-print-route-label',
659
+ 'enable-picking-random-seq',
660
+ 'enable-loading-select-all',
661
+ 'smart_batch_picking'
662
+ ]
663
+
664
+ booleanSettings.forEach(settingName => {
665
+ settingValidatorRegistry.registerCreateValidator(settingName, validateBooleanSetting)
666
+ settingValidatorRegistry.registerUpdateValidator(settingName, validateBooleanSetting)
667
+ })
668
+ }