sp2react 0.1.0__py3-none-any.whl

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 (79) hide show
  1. sp2react/__init__.py +18 -0
  2. sp2react/agents/__init__.py +0 -0
  3. sp2react/agents/core.py +146 -0
  4. sp2react/agents/prompts/dbmap.md +28 -0
  5. sp2react/agents/prompts/fix.md +11 -0
  6. sp2react/agents/prompts/handler.md +22 -0
  7. sp2react/agents/prompts/page.md +40 -0
  8. sp2react/agents/prompts/review.md +21 -0
  9. sp2react/agents/prompts/spec.md +79 -0
  10. sp2react/agents/tasks.py +374 -0
  11. sp2react/analysis/__init__.py +0 -0
  12. sp2react/analysis/aspx.py +254 -0
  13. sp2react/analysis/behaviors.py +357 -0
  14. sp2react/analysis/fields.py +152 -0
  15. sp2react/analysis/inventory.py +314 -0
  16. sp2react/analysis/jsscan.py +330 -0
  17. sp2react/checks.py +105 -0
  18. sp2react/cli.py +188 -0
  19. sp2react/config.py +172 -0
  20. sp2react/dbmap.py +245 -0
  21. sp2react/llm/__init__.py +0 -0
  22. sp2react/llm/providers.py +219 -0
  23. sp2react/migrate.py +255 -0
  24. sp2react/net.py +60 -0
  25. sp2react/pipeline.py +583 -0
  26. sp2react/scaffold/__init__.py +0 -0
  27. sp2react/scaffold/generate.py +279 -0
  28. sp2react/scaffold/rule_vectors.json +3826 -0
  29. sp2react/scaffold/templates/backend/app/__init__.py +0 -0
  30. sp2react/scaffold/templates/backend/app/api.py +293 -0
  31. sp2react/scaffold/templates/backend/app/auth.py +33 -0
  32. sp2react/scaffold/templates/backend/app/custom/__init__.py +22 -0
  33. sp2react/scaffold/templates/backend/app/main.py +47 -0
  34. sp2react/scaffold/templates/backend/app/odata.py +185 -0
  35. sp2react/scaffold/templates/backend/app/rules.py +256 -0
  36. sp2react/scaffold/templates/backend/app/sp_repo.py +159 -0
  37. sp2react/scaffold/templates/backend/app/sql_repo.py +437 -0
  38. sp2react/scaffold/templates/backend/app/store.py +224 -0
  39. sp2react/scaffold/templates/backend/requirements.txt +8 -0
  40. sp2react/scaffold/templates/backend/tests/conftest.py +10 -0
  41. sp2react/scaffold/templates/backend/tests/test_rule_vectors.py +38 -0
  42. sp2react/scaffold/templates/frontend/index.html +12 -0
  43. sp2react/scaffold/templates/frontend/package.json +24 -0
  44. sp2react/scaffold/templates/frontend/scripts/check-rules.ts +23 -0
  45. sp2react/scaffold/templates/frontend/src/api.ts +88 -0
  46. sp2react/scaffold/templates/frontend/src/components/sp/Banner.tsx +4 -0
  47. sp2react/scaffold/templates/frontend/src/components/sp/DataTable.tsx +38 -0
  48. sp2react/scaffold/templates/frontend/src/components/sp/Form.tsx +252 -0
  49. sp2react/scaffold/templates/frontend/src/components/sp/GenericPage.tsx +28 -0
  50. sp2react/scaffold/templates/frontend/src/components/sp/Layout.tsx +54 -0
  51. sp2react/scaffold/templates/frontend/src/components/sp/LegacyHtml.tsx +9 -0
  52. sp2react/scaffold/templates/frontend/src/components/sp/ListView.tsx +59 -0
  53. sp2react/scaffold/templates/frontend/src/components/sp/Tiles.tsx +21 -0
  54. sp2react/scaffold/templates/frontend/src/components/sp/fields.tsx +132 -0
  55. sp2react/scaffold/templates/frontend/src/custom/types.ts +10 -0
  56. sp2react/scaffold/templates/frontend/src/main.tsx +14 -0
  57. sp2react/scaffold/templates/frontend/src/runtime/format.ts +36 -0
  58. sp2react/scaffold/templates/frontend/src/runtime/rules.ts +225 -0
  59. sp2react/scaffold/templates/frontend/src/runtime/spec.ts +102 -0
  60. sp2react/scaffold/templates/frontend/src/runtime/user.tsx +21 -0
  61. sp2react/scaffold/templates/frontend/src/screens/LegacyRedirect.tsx +27 -0
  62. sp2react/scaffold/templates/frontend/src/screens/ListFormScreen.tsx +33 -0
  63. sp2react/scaffold/templates/frontend/src/styles/sp.css +95 -0
  64. sp2react/scaffold/templates/frontend/src/vite-env.d.ts +1 -0
  65. sp2react/scaffold/templates/frontend/tsconfig.json +18 -0
  66. sp2react/scaffold/templates/frontend/vite.config.ts +7 -0
  67. sp2react/sharepoint/__init__.py +0 -0
  68. sp2react/sharepoint/client.py +210 -0
  69. sp2react/sharepoint/extract.py +217 -0
  70. sp2react/spec/__init__.py +0 -0
  71. sp2react/spec/base.py +80 -0
  72. sp2react/spec/coverage.py +191 -0
  73. sp2react/spec/model.py +133 -0
  74. sp2react-0.1.0.dist-info/METADATA +238 -0
  75. sp2react-0.1.0.dist-info/RECORD +79 -0
  76. sp2react-0.1.0.dist-info/WHEEL +5 -0
  77. sp2react-0.1.0.dist-info/entry_points.txt +2 -0
  78. sp2react-0.1.0.dist-info/licenses/LICENSE +21 -0
  79. sp2react-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3826 @@
1
+ [
2
+ {
3
+ "name": "eq text",
4
+ "type": "evaluate",
5
+ "cond": {
6
+ "field": "Status",
7
+ "eq": "Approved"
8
+ },
9
+ "ctx": {
10
+ "mode": "edit",
11
+ "values": {
12
+ "Status": "Approved",
13
+ "ContractValue": "240,000",
14
+ "Country": {
15
+ "id": 8,
16
+ "title": "Nigeria"
17
+ },
18
+ "Flag": true,
19
+ "Empty": ""
20
+ },
21
+ "original": {
22
+ "Status": "Submitted"
23
+ },
24
+ "user": {
25
+ "id": 6,
26
+ "login": "corp\\alice",
27
+ "title": "Alice",
28
+ "groups": [
29
+ "Procurement Members"
30
+ ]
31
+ }
32
+ },
33
+ "expected": true
34
+ },
35
+ {
36
+ "name": "ne",
37
+ "type": "evaluate",
38
+ "cond": {
39
+ "field": "Status",
40
+ "ne": "Approved"
41
+ },
42
+ "ctx": {
43
+ "mode": "edit",
44
+ "values": {
45
+ "Status": "Approved",
46
+ "ContractValue": "240,000",
47
+ "Country": {
48
+ "id": 8,
49
+ "title": "Nigeria"
50
+ },
51
+ "Flag": true,
52
+ "Empty": ""
53
+ },
54
+ "original": {
55
+ "Status": "Submitted"
56
+ },
57
+ "user": {
58
+ "id": 6,
59
+ "login": "corp\\alice",
60
+ "title": "Alice",
61
+ "groups": [
62
+ "Procurement Members"
63
+ ]
64
+ }
65
+ },
66
+ "expected": false
67
+ },
68
+ {
69
+ "name": "numeric string gt",
70
+ "type": "evaluate",
71
+ "cond": {
72
+ "field": "ContractValue",
73
+ "gt": 100000
74
+ },
75
+ "ctx": {
76
+ "mode": "edit",
77
+ "values": {
78
+ "Status": "Approved",
79
+ "ContractValue": "240,000",
80
+ "Country": {
81
+ "id": 8,
82
+ "title": "Nigeria"
83
+ },
84
+ "Flag": true,
85
+ "Empty": ""
86
+ },
87
+ "original": {
88
+ "Status": "Submitted"
89
+ },
90
+ "user": {
91
+ "id": 6,
92
+ "login": "corp\\alice",
93
+ "title": "Alice",
94
+ "groups": [
95
+ "Procurement Members"
96
+ ]
97
+ }
98
+ },
99
+ "expected": true
100
+ },
101
+ {
102
+ "name": "lookup title eq",
103
+ "type": "evaluate",
104
+ "cond": {
105
+ "field": "Country",
106
+ "eq": "Nigeria"
107
+ },
108
+ "ctx": {
109
+ "mode": "edit",
110
+ "values": {
111
+ "Status": "Approved",
112
+ "ContractValue": "240,000",
113
+ "Country": {
114
+ "id": 8,
115
+ "title": "Nigeria"
116
+ },
117
+ "Flag": true,
118
+ "Empty": ""
119
+ },
120
+ "original": {
121
+ "Status": "Submitted"
122
+ },
123
+ "user": {
124
+ "id": 6,
125
+ "login": "corp\\alice",
126
+ "title": "Alice",
127
+ "groups": [
128
+ "Procurement Members"
129
+ ]
130
+ }
131
+ },
132
+ "expected": true
133
+ },
134
+ {
135
+ "name": "lookup id eq",
136
+ "type": "evaluate",
137
+ "cond": {
138
+ "field": "Country",
139
+ "eq": 8
140
+ },
141
+ "ctx": {
142
+ "mode": "edit",
143
+ "values": {
144
+ "Status": "Approved",
145
+ "ContractValue": "240,000",
146
+ "Country": {
147
+ "id": 8,
148
+ "title": "Nigeria"
149
+ },
150
+ "Flag": true,
151
+ "Empty": ""
152
+ },
153
+ "original": {
154
+ "Status": "Submitted"
155
+ },
156
+ "user": {
157
+ "id": 6,
158
+ "login": "corp\\alice",
159
+ "title": "Alice",
160
+ "groups": [
161
+ "Procurement Members"
162
+ ]
163
+ }
164
+ },
165
+ "expected": true
166
+ },
167
+ {
168
+ "name": "lookup path",
169
+ "type": "evaluate",
170
+ "cond": {
171
+ "field": "Country.title",
172
+ "eq": "Nigeria"
173
+ },
174
+ "ctx": {
175
+ "mode": "edit",
176
+ "values": {
177
+ "Status": "Approved",
178
+ "ContractValue": "240,000",
179
+ "Country": {
180
+ "id": 8,
181
+ "title": "Nigeria"
182
+ },
183
+ "Flag": true,
184
+ "Empty": ""
185
+ },
186
+ "original": {
187
+ "Status": "Submitted"
188
+ },
189
+ "user": {
190
+ "id": 6,
191
+ "login": "corp\\alice",
192
+ "title": "Alice",
193
+ "groups": [
194
+ "Procurement Members"
195
+ ]
196
+ }
197
+ },
198
+ "expected": true
199
+ },
200
+ {
201
+ "name": "in",
202
+ "type": "evaluate",
203
+ "cond": {
204
+ "field": "Status",
205
+ "in": [
206
+ "Approved",
207
+ "Rejected"
208
+ ]
209
+ },
210
+ "ctx": {
211
+ "mode": "edit",
212
+ "values": {
213
+ "Status": "Approved",
214
+ "ContractValue": "240,000",
215
+ "Country": {
216
+ "id": 8,
217
+ "title": "Nigeria"
218
+ },
219
+ "Flag": true,
220
+ "Empty": ""
221
+ },
222
+ "original": {
223
+ "Status": "Submitted"
224
+ },
225
+ "user": {
226
+ "id": 6,
227
+ "login": "corp\\alice",
228
+ "title": "Alice",
229
+ "groups": [
230
+ "Procurement Members"
231
+ ]
232
+ }
233
+ },
234
+ "expected": true
235
+ },
236
+ {
237
+ "name": "nin",
238
+ "type": "evaluate",
239
+ "cond": {
240
+ "field": "Status",
241
+ "nin": [
242
+ "Approved",
243
+ "Rejected"
244
+ ]
245
+ },
246
+ "ctx": {
247
+ "mode": "edit",
248
+ "values": {
249
+ "Status": "Approved",
250
+ "ContractValue": "240,000",
251
+ "Country": {
252
+ "id": 8,
253
+ "title": "Nigeria"
254
+ },
255
+ "Flag": true,
256
+ "Empty": ""
257
+ },
258
+ "original": {
259
+ "Status": "Submitted"
260
+ },
261
+ "user": {
262
+ "id": 6,
263
+ "login": "corp\\alice",
264
+ "title": "Alice",
265
+ "groups": [
266
+ "Procurement Members"
267
+ ]
268
+ }
269
+ },
270
+ "expected": false
271
+ },
272
+ {
273
+ "name": "empty",
274
+ "type": "evaluate",
275
+ "cond": {
276
+ "field": "Empty",
277
+ "empty": true
278
+ },
279
+ "ctx": {
280
+ "mode": "edit",
281
+ "values": {
282
+ "Status": "Approved",
283
+ "ContractValue": "240,000",
284
+ "Country": {
285
+ "id": 8,
286
+ "title": "Nigeria"
287
+ },
288
+ "Flag": true,
289
+ "Empty": ""
290
+ },
291
+ "original": {
292
+ "Status": "Submitted"
293
+ },
294
+ "user": {
295
+ "id": 6,
296
+ "login": "corp\\alice",
297
+ "title": "Alice",
298
+ "groups": [
299
+ "Procurement Members"
300
+ ]
301
+ }
302
+ },
303
+ "expected": true
304
+ },
305
+ {
306
+ "name": "notEmpty missing",
307
+ "type": "evaluate",
308
+ "cond": {
309
+ "field": "Nope",
310
+ "notEmpty": true
311
+ },
312
+ "ctx": {
313
+ "mode": "edit",
314
+ "values": {
315
+ "Status": "Approved",
316
+ "ContractValue": "240,000",
317
+ "Country": {
318
+ "id": 8,
319
+ "title": "Nigeria"
320
+ },
321
+ "Flag": true,
322
+ "Empty": ""
323
+ },
324
+ "original": {
325
+ "Status": "Submitted"
326
+ },
327
+ "user": {
328
+ "id": 6,
329
+ "login": "corp\\alice",
330
+ "title": "Alice",
331
+ "groups": [
332
+ "Procurement Members"
333
+ ]
334
+ }
335
+ },
336
+ "expected": false
337
+ },
338
+ {
339
+ "name": "changed",
340
+ "type": "evaluate",
341
+ "cond": {
342
+ "field": "Status",
343
+ "changed": true
344
+ },
345
+ "ctx": {
346
+ "mode": "edit",
347
+ "values": {
348
+ "Status": "Approved",
349
+ "ContractValue": "240,000",
350
+ "Country": {
351
+ "id": 8,
352
+ "title": "Nigeria"
353
+ },
354
+ "Flag": true,
355
+ "Empty": ""
356
+ },
357
+ "original": {
358
+ "Status": "Submitted"
359
+ },
360
+ "user": {
361
+ "id": 6,
362
+ "login": "corp\\alice",
363
+ "title": "Alice",
364
+ "groups": [
365
+ "Procurement Members"
366
+ ]
367
+ }
368
+ },
369
+ "expected": true
370
+ },
371
+ {
372
+ "name": "boolean eq",
373
+ "type": "evaluate",
374
+ "cond": {
375
+ "field": "Flag",
376
+ "eq": true
377
+ },
378
+ "ctx": {
379
+ "mode": "edit",
380
+ "values": {
381
+ "Status": "Approved",
382
+ "ContractValue": "240,000",
383
+ "Country": {
384
+ "id": 8,
385
+ "title": "Nigeria"
386
+ },
387
+ "Flag": true,
388
+ "Empty": ""
389
+ },
390
+ "original": {
391
+ "Status": "Submitted"
392
+ },
393
+ "user": {
394
+ "id": 6,
395
+ "login": "corp\\alice",
396
+ "title": "Alice",
397
+ "groups": [
398
+ "Procurement Members"
399
+ ]
400
+ }
401
+ },
402
+ "expected": true
403
+ },
404
+ {
405
+ "name": "role no",
406
+ "type": "evaluate",
407
+ "cond": {
408
+ "role": "Compliance Reviewers"
409
+ },
410
+ "ctx": {
411
+ "mode": "edit",
412
+ "values": {
413
+ "Status": "Approved",
414
+ "ContractValue": "240,000",
415
+ "Country": {
416
+ "id": 8,
417
+ "title": "Nigeria"
418
+ },
419
+ "Flag": true,
420
+ "Empty": ""
421
+ },
422
+ "original": {
423
+ "Status": "Submitted"
424
+ },
425
+ "user": {
426
+ "id": 6,
427
+ "login": "corp\\alice",
428
+ "title": "Alice",
429
+ "groups": [
430
+ "Procurement Members"
431
+ ]
432
+ }
433
+ },
434
+ "expected": false
435
+ },
436
+ {
437
+ "name": "not role",
438
+ "type": "evaluate",
439
+ "cond": {
440
+ "not": {
441
+ "role": "Compliance Reviewers"
442
+ }
443
+ },
444
+ "ctx": {
445
+ "mode": "edit",
446
+ "values": {
447
+ "Status": "Approved",
448
+ "ContractValue": "240,000",
449
+ "Country": {
450
+ "id": 8,
451
+ "title": "Nigeria"
452
+ },
453
+ "Flag": true,
454
+ "Empty": ""
455
+ },
456
+ "original": {
457
+ "Status": "Submitted"
458
+ },
459
+ "user": {
460
+ "id": 6,
461
+ "login": "corp\\alice",
462
+ "title": "Alice",
463
+ "groups": [
464
+ "Procurement Members"
465
+ ]
466
+ }
467
+ },
468
+ "expected": true
469
+ },
470
+ {
471
+ "name": "mode list",
472
+ "type": "evaluate",
473
+ "cond": {
474
+ "mode": [
475
+ "new",
476
+ "edit"
477
+ ]
478
+ },
479
+ "ctx": {
480
+ "mode": "edit",
481
+ "values": {
482
+ "Status": "Approved",
483
+ "ContractValue": "240,000",
484
+ "Country": {
485
+ "id": 8,
486
+ "title": "Nigeria"
487
+ },
488
+ "Flag": true,
489
+ "Empty": ""
490
+ },
491
+ "original": {
492
+ "Status": "Submitted"
493
+ },
494
+ "user": {
495
+ "id": 6,
496
+ "login": "corp\\alice",
497
+ "title": "Alice",
498
+ "groups": [
499
+ "Procurement Members"
500
+ ]
501
+ }
502
+ },
503
+ "expected": true
504
+ },
505
+ {
506
+ "name": "all/any",
507
+ "type": "evaluate",
508
+ "cond": {
509
+ "all": [
510
+ {
511
+ "field": "Status",
512
+ "eq": "Approved"
513
+ },
514
+ {
515
+ "any": [
516
+ {
517
+ "field": "Flag",
518
+ "eq": false
519
+ },
520
+ {
521
+ "mode": "edit"
522
+ }
523
+ ]
524
+ }
525
+ ]
526
+ },
527
+ "ctx": {
528
+ "mode": "edit",
529
+ "values": {
530
+ "Status": "Approved",
531
+ "ContractValue": "240,000",
532
+ "Country": {
533
+ "id": 8,
534
+ "title": "Nigeria"
535
+ },
536
+ "Flag": true,
537
+ "Empty": ""
538
+ },
539
+ "original": {
540
+ "Status": "Submitted"
541
+ },
542
+ "user": {
543
+ "id": 6,
544
+ "login": "corp\\alice",
545
+ "title": "Alice",
546
+ "groups": [
547
+ "Procurement Members"
548
+ ]
549
+ }
550
+ },
551
+ "expected": true
552
+ },
553
+ {
554
+ "name": "gt with empty",
555
+ "type": "evaluate",
556
+ "cond": {
557
+ "field": "Empty",
558
+ "gt": 1
559
+ },
560
+ "ctx": {
561
+ "mode": "edit",
562
+ "values": {
563
+ "Status": "Approved",
564
+ "ContractValue": "240,000",
565
+ "Country": {
566
+ "id": 8,
567
+ "title": "Nigeria"
568
+ },
569
+ "Flag": true,
570
+ "Empty": ""
571
+ },
572
+ "original": {
573
+ "Status": "Submitted"
574
+ },
575
+ "user": {
576
+ "id": 6,
577
+ "login": "corp\\alice",
578
+ "title": "Alice",
579
+ "groups": [
580
+ "Procurement Members"
581
+ ]
582
+ }
583
+ },
584
+ "expected": false
585
+ },
586
+ {
587
+ "name": "valid new",
588
+ "type": "validate",
589
+ "fields": [
590
+ {
591
+ "name": "Title",
592
+ "label": "Vendor Name",
593
+ "type": "text",
594
+ "required": true,
595
+ "max_length": 20
596
+ },
597
+ {
598
+ "name": "Country",
599
+ "label": "Country",
600
+ "type": "lookup",
601
+ "required": true,
602
+ "lookup": {
603
+ "list": "Countries",
604
+ "field": "Title"
605
+ }
606
+ },
607
+ {
608
+ "name": "RiskRating",
609
+ "label": "Risk Rating",
610
+ "type": "text"
611
+ },
612
+ {
613
+ "name": "Justification",
614
+ "label": "Justification",
615
+ "type": "note"
616
+ },
617
+ {
618
+ "name": "ContractValue",
619
+ "label": "Contract Value",
620
+ "type": "currency",
621
+ "required": true,
622
+ "min": 0
623
+ },
624
+ {
625
+ "name": "SecondApprover",
626
+ "label": "Second Approver",
627
+ "type": "user"
628
+ },
629
+ {
630
+ "name": "RequestDate",
631
+ "label": "Request Date",
632
+ "type": "date"
633
+ },
634
+ {
635
+ "name": "StartDate",
636
+ "label": "Start Date",
637
+ "type": "date"
638
+ },
639
+ {
640
+ "name": "Status",
641
+ "label": "Status",
642
+ "type": "choice",
643
+ "choices": [
644
+ "Submitted",
645
+ "Approved",
646
+ "Rejected"
647
+ ]
648
+ },
649
+ {
650
+ "name": "TaxID",
651
+ "label": "Tax ID",
652
+ "type": "text"
653
+ }
654
+ ],
655
+ "rules": [
656
+ {
657
+ "id": "R1",
658
+ "kind": "visible",
659
+ "field": "Justification",
660
+ "when": {
661
+ "field": "RiskRating",
662
+ "eq": "High"
663
+ }
664
+ },
665
+ {
666
+ "id": "R2",
667
+ "kind": "required",
668
+ "field": "Justification",
669
+ "when": {
670
+ "field": "RiskRating",
671
+ "eq": "High"
672
+ },
673
+ "message": "Justification is required for high-risk vendors.",
674
+ "presentation": "alert"
675
+ },
676
+ {
677
+ "id": "R3",
678
+ "kind": "required",
679
+ "field": "SecondApprover",
680
+ "when": {
681
+ "field": "ContractValue",
682
+ "gt": 100000
683
+ },
684
+ "message": "A Second Approver is required for contracts over $100,000.",
685
+ "presentation": "alert"
686
+ },
687
+ {
688
+ "id": "R4",
689
+ "kind": "compare",
690
+ "field": "StartDate",
691
+ "params": {
692
+ "op": "gte",
693
+ "other": "RequestDate"
694
+ },
695
+ "message": "Start Date cannot be before the Request Date.",
696
+ "presentation": "alert"
697
+ },
698
+ {
699
+ "id": "R5",
700
+ "kind": "readonly",
701
+ "field": "Status",
702
+ "when": {
703
+ "not": {
704
+ "role": "Compliance Reviewers"
705
+ }
706
+ }
707
+ },
708
+ {
709
+ "id": "R6",
710
+ "kind": "length",
711
+ "field": "TaxID",
712
+ "params": {
713
+ "op": "eq",
714
+ "value": 9,
715
+ "allow_empty": true
716
+ },
717
+ "message": "Tax ID must be exactly 9 digits."
718
+ },
719
+ {
720
+ "id": "R7",
721
+ "kind": "visible",
722
+ "field": "Status",
723
+ "modes": [
724
+ "edit",
725
+ "display"
726
+ ]
727
+ },
728
+ {
729
+ "id": "R8",
730
+ "kind": "lock_item",
731
+ "modes": [
732
+ "edit"
733
+ ],
734
+ "when": {
735
+ "all": [
736
+ {
737
+ "field": "Status",
738
+ "in": [
739
+ "Approved",
740
+ "Rejected"
741
+ ]
742
+ },
743
+ {
744
+ "not": {
745
+ "role": "Compliance Reviewers"
746
+ }
747
+ }
748
+ ]
749
+ },
750
+ "message": "This request has been {Status|lower} and can no longer be edited."
751
+ },
752
+ {
753
+ "id": "R9",
754
+ "kind": "set_on_save",
755
+ "field": "Status",
756
+ "modes": [
757
+ "new"
758
+ ],
759
+ "params": {
760
+ "value": "Submitted"
761
+ }
762
+ },
763
+ {
764
+ "id": "R10",
765
+ "kind": "banner",
766
+ "modes": [
767
+ "display"
768
+ ],
769
+ "when": {
770
+ "field": "Country.title",
771
+ "eq": "Iran"
772
+ },
773
+ "message": "Sanctioned country: {Country.title}"
774
+ }
775
+ ],
776
+ "ctx": {
777
+ "mode": "new",
778
+ "values": {
779
+ "Title": "Acme",
780
+ "Country": {
781
+ "id": 3,
782
+ "title": "Germany"
783
+ },
784
+ "RiskRating": "Low",
785
+ "ContractValue": "18,500",
786
+ "RequestDate": "2026-10-01",
787
+ "StartDate": "2026-10-05",
788
+ "TaxID": "123456789"
789
+ },
790
+ "original": {},
791
+ "user": {
792
+ "id": 6,
793
+ "login": "corp\\alice",
794
+ "title": "Alice",
795
+ "groups": [
796
+ "Procurement Members"
797
+ ]
798
+ }
799
+ },
800
+ "expected": []
801
+ },
802
+ {
803
+ "name": "missing required",
804
+ "type": "validate",
805
+ "fields": [
806
+ {
807
+ "name": "Title",
808
+ "label": "Vendor Name",
809
+ "type": "text",
810
+ "required": true,
811
+ "max_length": 20
812
+ },
813
+ {
814
+ "name": "Country",
815
+ "label": "Country",
816
+ "type": "lookup",
817
+ "required": true,
818
+ "lookup": {
819
+ "list": "Countries",
820
+ "field": "Title"
821
+ }
822
+ },
823
+ {
824
+ "name": "RiskRating",
825
+ "label": "Risk Rating",
826
+ "type": "text"
827
+ },
828
+ {
829
+ "name": "Justification",
830
+ "label": "Justification",
831
+ "type": "note"
832
+ },
833
+ {
834
+ "name": "ContractValue",
835
+ "label": "Contract Value",
836
+ "type": "currency",
837
+ "required": true,
838
+ "min": 0
839
+ },
840
+ {
841
+ "name": "SecondApprover",
842
+ "label": "Second Approver",
843
+ "type": "user"
844
+ },
845
+ {
846
+ "name": "RequestDate",
847
+ "label": "Request Date",
848
+ "type": "date"
849
+ },
850
+ {
851
+ "name": "StartDate",
852
+ "label": "Start Date",
853
+ "type": "date"
854
+ },
855
+ {
856
+ "name": "Status",
857
+ "label": "Status",
858
+ "type": "choice",
859
+ "choices": [
860
+ "Submitted",
861
+ "Approved",
862
+ "Rejected"
863
+ ]
864
+ },
865
+ {
866
+ "name": "TaxID",
867
+ "label": "Tax ID",
868
+ "type": "text"
869
+ }
870
+ ],
871
+ "rules": [
872
+ {
873
+ "id": "R1",
874
+ "kind": "visible",
875
+ "field": "Justification",
876
+ "when": {
877
+ "field": "RiskRating",
878
+ "eq": "High"
879
+ }
880
+ },
881
+ {
882
+ "id": "R2",
883
+ "kind": "required",
884
+ "field": "Justification",
885
+ "when": {
886
+ "field": "RiskRating",
887
+ "eq": "High"
888
+ },
889
+ "message": "Justification is required for high-risk vendors.",
890
+ "presentation": "alert"
891
+ },
892
+ {
893
+ "id": "R3",
894
+ "kind": "required",
895
+ "field": "SecondApprover",
896
+ "when": {
897
+ "field": "ContractValue",
898
+ "gt": 100000
899
+ },
900
+ "message": "A Second Approver is required for contracts over $100,000.",
901
+ "presentation": "alert"
902
+ },
903
+ {
904
+ "id": "R4",
905
+ "kind": "compare",
906
+ "field": "StartDate",
907
+ "params": {
908
+ "op": "gte",
909
+ "other": "RequestDate"
910
+ },
911
+ "message": "Start Date cannot be before the Request Date.",
912
+ "presentation": "alert"
913
+ },
914
+ {
915
+ "id": "R5",
916
+ "kind": "readonly",
917
+ "field": "Status",
918
+ "when": {
919
+ "not": {
920
+ "role": "Compliance Reviewers"
921
+ }
922
+ }
923
+ },
924
+ {
925
+ "id": "R6",
926
+ "kind": "length",
927
+ "field": "TaxID",
928
+ "params": {
929
+ "op": "eq",
930
+ "value": 9,
931
+ "allow_empty": true
932
+ },
933
+ "message": "Tax ID must be exactly 9 digits."
934
+ },
935
+ {
936
+ "id": "R7",
937
+ "kind": "visible",
938
+ "field": "Status",
939
+ "modes": [
940
+ "edit",
941
+ "display"
942
+ ]
943
+ },
944
+ {
945
+ "id": "R8",
946
+ "kind": "lock_item",
947
+ "modes": [
948
+ "edit"
949
+ ],
950
+ "when": {
951
+ "all": [
952
+ {
953
+ "field": "Status",
954
+ "in": [
955
+ "Approved",
956
+ "Rejected"
957
+ ]
958
+ },
959
+ {
960
+ "not": {
961
+ "role": "Compliance Reviewers"
962
+ }
963
+ }
964
+ ]
965
+ },
966
+ "message": "This request has been {Status|lower} and can no longer be edited."
967
+ },
968
+ {
969
+ "id": "R9",
970
+ "kind": "set_on_save",
971
+ "field": "Status",
972
+ "modes": [
973
+ "new"
974
+ ],
975
+ "params": {
976
+ "value": "Submitted"
977
+ }
978
+ },
979
+ {
980
+ "id": "R10",
981
+ "kind": "banner",
982
+ "modes": [
983
+ "display"
984
+ ],
985
+ "when": {
986
+ "field": "Country.title",
987
+ "eq": "Iran"
988
+ },
989
+ "message": "Sanctioned country: {Country.title}"
990
+ }
991
+ ],
992
+ "ctx": {
993
+ "mode": "new",
994
+ "values": {
995
+ "RiskRating": "Low"
996
+ },
997
+ "original": {},
998
+ "user": {
999
+ "id": 6,
1000
+ "login": "corp\\alice",
1001
+ "title": "Alice",
1002
+ "groups": [
1003
+ "Procurement Members"
1004
+ ]
1005
+ }
1006
+ },
1007
+ "expected": [
1008
+ [
1009
+ "Title",
1010
+ "You can't leave this blank.",
1011
+ "schema"
1012
+ ],
1013
+ [
1014
+ "Country",
1015
+ "You can't leave this blank.",
1016
+ "schema"
1017
+ ],
1018
+ [
1019
+ "ContractValue",
1020
+ "You can't leave this blank.",
1021
+ "schema"
1022
+ ]
1023
+ ]
1024
+ },
1025
+ {
1026
+ "name": "high risk needs justification",
1027
+ "type": "validate",
1028
+ "fields": [
1029
+ {
1030
+ "name": "Title",
1031
+ "label": "Vendor Name",
1032
+ "type": "text",
1033
+ "required": true,
1034
+ "max_length": 20
1035
+ },
1036
+ {
1037
+ "name": "Country",
1038
+ "label": "Country",
1039
+ "type": "lookup",
1040
+ "required": true,
1041
+ "lookup": {
1042
+ "list": "Countries",
1043
+ "field": "Title"
1044
+ }
1045
+ },
1046
+ {
1047
+ "name": "RiskRating",
1048
+ "label": "Risk Rating",
1049
+ "type": "text"
1050
+ },
1051
+ {
1052
+ "name": "Justification",
1053
+ "label": "Justification",
1054
+ "type": "note"
1055
+ },
1056
+ {
1057
+ "name": "ContractValue",
1058
+ "label": "Contract Value",
1059
+ "type": "currency",
1060
+ "required": true,
1061
+ "min": 0
1062
+ },
1063
+ {
1064
+ "name": "SecondApprover",
1065
+ "label": "Second Approver",
1066
+ "type": "user"
1067
+ },
1068
+ {
1069
+ "name": "RequestDate",
1070
+ "label": "Request Date",
1071
+ "type": "date"
1072
+ },
1073
+ {
1074
+ "name": "StartDate",
1075
+ "label": "Start Date",
1076
+ "type": "date"
1077
+ },
1078
+ {
1079
+ "name": "Status",
1080
+ "label": "Status",
1081
+ "type": "choice",
1082
+ "choices": [
1083
+ "Submitted",
1084
+ "Approved",
1085
+ "Rejected"
1086
+ ]
1087
+ },
1088
+ {
1089
+ "name": "TaxID",
1090
+ "label": "Tax ID",
1091
+ "type": "text"
1092
+ }
1093
+ ],
1094
+ "rules": [
1095
+ {
1096
+ "id": "R1",
1097
+ "kind": "visible",
1098
+ "field": "Justification",
1099
+ "when": {
1100
+ "field": "RiskRating",
1101
+ "eq": "High"
1102
+ }
1103
+ },
1104
+ {
1105
+ "id": "R2",
1106
+ "kind": "required",
1107
+ "field": "Justification",
1108
+ "when": {
1109
+ "field": "RiskRating",
1110
+ "eq": "High"
1111
+ },
1112
+ "message": "Justification is required for high-risk vendors.",
1113
+ "presentation": "alert"
1114
+ },
1115
+ {
1116
+ "id": "R3",
1117
+ "kind": "required",
1118
+ "field": "SecondApprover",
1119
+ "when": {
1120
+ "field": "ContractValue",
1121
+ "gt": 100000
1122
+ },
1123
+ "message": "A Second Approver is required for contracts over $100,000.",
1124
+ "presentation": "alert"
1125
+ },
1126
+ {
1127
+ "id": "R4",
1128
+ "kind": "compare",
1129
+ "field": "StartDate",
1130
+ "params": {
1131
+ "op": "gte",
1132
+ "other": "RequestDate"
1133
+ },
1134
+ "message": "Start Date cannot be before the Request Date.",
1135
+ "presentation": "alert"
1136
+ },
1137
+ {
1138
+ "id": "R5",
1139
+ "kind": "readonly",
1140
+ "field": "Status",
1141
+ "when": {
1142
+ "not": {
1143
+ "role": "Compliance Reviewers"
1144
+ }
1145
+ }
1146
+ },
1147
+ {
1148
+ "id": "R6",
1149
+ "kind": "length",
1150
+ "field": "TaxID",
1151
+ "params": {
1152
+ "op": "eq",
1153
+ "value": 9,
1154
+ "allow_empty": true
1155
+ },
1156
+ "message": "Tax ID must be exactly 9 digits."
1157
+ },
1158
+ {
1159
+ "id": "R7",
1160
+ "kind": "visible",
1161
+ "field": "Status",
1162
+ "modes": [
1163
+ "edit",
1164
+ "display"
1165
+ ]
1166
+ },
1167
+ {
1168
+ "id": "R8",
1169
+ "kind": "lock_item",
1170
+ "modes": [
1171
+ "edit"
1172
+ ],
1173
+ "when": {
1174
+ "all": [
1175
+ {
1176
+ "field": "Status",
1177
+ "in": [
1178
+ "Approved",
1179
+ "Rejected"
1180
+ ]
1181
+ },
1182
+ {
1183
+ "not": {
1184
+ "role": "Compliance Reviewers"
1185
+ }
1186
+ }
1187
+ ]
1188
+ },
1189
+ "message": "This request has been {Status|lower} and can no longer be edited."
1190
+ },
1191
+ {
1192
+ "id": "R9",
1193
+ "kind": "set_on_save",
1194
+ "field": "Status",
1195
+ "modes": [
1196
+ "new"
1197
+ ],
1198
+ "params": {
1199
+ "value": "Submitted"
1200
+ }
1201
+ },
1202
+ {
1203
+ "id": "R10",
1204
+ "kind": "banner",
1205
+ "modes": [
1206
+ "display"
1207
+ ],
1208
+ "when": {
1209
+ "field": "Country.title",
1210
+ "eq": "Iran"
1211
+ },
1212
+ "message": "Sanctioned country: {Country.title}"
1213
+ }
1214
+ ],
1215
+ "ctx": {
1216
+ "mode": "new",
1217
+ "values": {
1218
+ "Title": "Acme",
1219
+ "Country": {
1220
+ "id": 3,
1221
+ "title": "Germany"
1222
+ },
1223
+ "RiskRating": "High",
1224
+ "ContractValue": "18,500",
1225
+ "RequestDate": "2026-10-01",
1226
+ "StartDate": "2026-10-05",
1227
+ "TaxID": "123456789"
1228
+ },
1229
+ "original": {},
1230
+ "user": {
1231
+ "id": 6,
1232
+ "login": "corp\\alice",
1233
+ "title": "Alice",
1234
+ "groups": [
1235
+ "Procurement Members"
1236
+ ]
1237
+ }
1238
+ },
1239
+ "expected": [
1240
+ [
1241
+ "Justification",
1242
+ "Justification is required for high-risk vendors.",
1243
+ "R2"
1244
+ ]
1245
+ ]
1246
+ },
1247
+ {
1248
+ "name": "big contract needs approver",
1249
+ "type": "validate",
1250
+ "fields": [
1251
+ {
1252
+ "name": "Title",
1253
+ "label": "Vendor Name",
1254
+ "type": "text",
1255
+ "required": true,
1256
+ "max_length": 20
1257
+ },
1258
+ {
1259
+ "name": "Country",
1260
+ "label": "Country",
1261
+ "type": "lookup",
1262
+ "required": true,
1263
+ "lookup": {
1264
+ "list": "Countries",
1265
+ "field": "Title"
1266
+ }
1267
+ },
1268
+ {
1269
+ "name": "RiskRating",
1270
+ "label": "Risk Rating",
1271
+ "type": "text"
1272
+ },
1273
+ {
1274
+ "name": "Justification",
1275
+ "label": "Justification",
1276
+ "type": "note"
1277
+ },
1278
+ {
1279
+ "name": "ContractValue",
1280
+ "label": "Contract Value",
1281
+ "type": "currency",
1282
+ "required": true,
1283
+ "min": 0
1284
+ },
1285
+ {
1286
+ "name": "SecondApprover",
1287
+ "label": "Second Approver",
1288
+ "type": "user"
1289
+ },
1290
+ {
1291
+ "name": "RequestDate",
1292
+ "label": "Request Date",
1293
+ "type": "date"
1294
+ },
1295
+ {
1296
+ "name": "StartDate",
1297
+ "label": "Start Date",
1298
+ "type": "date"
1299
+ },
1300
+ {
1301
+ "name": "Status",
1302
+ "label": "Status",
1303
+ "type": "choice",
1304
+ "choices": [
1305
+ "Submitted",
1306
+ "Approved",
1307
+ "Rejected"
1308
+ ]
1309
+ },
1310
+ {
1311
+ "name": "TaxID",
1312
+ "label": "Tax ID",
1313
+ "type": "text"
1314
+ }
1315
+ ],
1316
+ "rules": [
1317
+ {
1318
+ "id": "R1",
1319
+ "kind": "visible",
1320
+ "field": "Justification",
1321
+ "when": {
1322
+ "field": "RiskRating",
1323
+ "eq": "High"
1324
+ }
1325
+ },
1326
+ {
1327
+ "id": "R2",
1328
+ "kind": "required",
1329
+ "field": "Justification",
1330
+ "when": {
1331
+ "field": "RiskRating",
1332
+ "eq": "High"
1333
+ },
1334
+ "message": "Justification is required for high-risk vendors.",
1335
+ "presentation": "alert"
1336
+ },
1337
+ {
1338
+ "id": "R3",
1339
+ "kind": "required",
1340
+ "field": "SecondApprover",
1341
+ "when": {
1342
+ "field": "ContractValue",
1343
+ "gt": 100000
1344
+ },
1345
+ "message": "A Second Approver is required for contracts over $100,000.",
1346
+ "presentation": "alert"
1347
+ },
1348
+ {
1349
+ "id": "R4",
1350
+ "kind": "compare",
1351
+ "field": "StartDate",
1352
+ "params": {
1353
+ "op": "gte",
1354
+ "other": "RequestDate"
1355
+ },
1356
+ "message": "Start Date cannot be before the Request Date.",
1357
+ "presentation": "alert"
1358
+ },
1359
+ {
1360
+ "id": "R5",
1361
+ "kind": "readonly",
1362
+ "field": "Status",
1363
+ "when": {
1364
+ "not": {
1365
+ "role": "Compliance Reviewers"
1366
+ }
1367
+ }
1368
+ },
1369
+ {
1370
+ "id": "R6",
1371
+ "kind": "length",
1372
+ "field": "TaxID",
1373
+ "params": {
1374
+ "op": "eq",
1375
+ "value": 9,
1376
+ "allow_empty": true
1377
+ },
1378
+ "message": "Tax ID must be exactly 9 digits."
1379
+ },
1380
+ {
1381
+ "id": "R7",
1382
+ "kind": "visible",
1383
+ "field": "Status",
1384
+ "modes": [
1385
+ "edit",
1386
+ "display"
1387
+ ]
1388
+ },
1389
+ {
1390
+ "id": "R8",
1391
+ "kind": "lock_item",
1392
+ "modes": [
1393
+ "edit"
1394
+ ],
1395
+ "when": {
1396
+ "all": [
1397
+ {
1398
+ "field": "Status",
1399
+ "in": [
1400
+ "Approved",
1401
+ "Rejected"
1402
+ ]
1403
+ },
1404
+ {
1405
+ "not": {
1406
+ "role": "Compliance Reviewers"
1407
+ }
1408
+ }
1409
+ ]
1410
+ },
1411
+ "message": "This request has been {Status|lower} and can no longer be edited."
1412
+ },
1413
+ {
1414
+ "id": "R9",
1415
+ "kind": "set_on_save",
1416
+ "field": "Status",
1417
+ "modes": [
1418
+ "new"
1419
+ ],
1420
+ "params": {
1421
+ "value": "Submitted"
1422
+ }
1423
+ },
1424
+ {
1425
+ "id": "R10",
1426
+ "kind": "banner",
1427
+ "modes": [
1428
+ "display"
1429
+ ],
1430
+ "when": {
1431
+ "field": "Country.title",
1432
+ "eq": "Iran"
1433
+ },
1434
+ "message": "Sanctioned country: {Country.title}"
1435
+ }
1436
+ ],
1437
+ "ctx": {
1438
+ "mode": "new",
1439
+ "values": {
1440
+ "Title": "Acme",
1441
+ "Country": {
1442
+ "id": 3,
1443
+ "title": "Germany"
1444
+ },
1445
+ "RiskRating": "Low",
1446
+ "ContractValue": "240000",
1447
+ "RequestDate": "2026-10-01",
1448
+ "StartDate": "2026-10-05",
1449
+ "TaxID": "123456789"
1450
+ },
1451
+ "original": {},
1452
+ "user": {
1453
+ "id": 6,
1454
+ "login": "corp\\alice",
1455
+ "title": "Alice",
1456
+ "groups": [
1457
+ "Procurement Members"
1458
+ ]
1459
+ }
1460
+ },
1461
+ "expected": [
1462
+ [
1463
+ "SecondApprover",
1464
+ "A Second Approver is required for contracts over $100,000.",
1465
+ "R3"
1466
+ ]
1467
+ ]
1468
+ },
1469
+ {
1470
+ "name": "dates",
1471
+ "type": "validate",
1472
+ "fields": [
1473
+ {
1474
+ "name": "Title",
1475
+ "label": "Vendor Name",
1476
+ "type": "text",
1477
+ "required": true,
1478
+ "max_length": 20
1479
+ },
1480
+ {
1481
+ "name": "Country",
1482
+ "label": "Country",
1483
+ "type": "lookup",
1484
+ "required": true,
1485
+ "lookup": {
1486
+ "list": "Countries",
1487
+ "field": "Title"
1488
+ }
1489
+ },
1490
+ {
1491
+ "name": "RiskRating",
1492
+ "label": "Risk Rating",
1493
+ "type": "text"
1494
+ },
1495
+ {
1496
+ "name": "Justification",
1497
+ "label": "Justification",
1498
+ "type": "note"
1499
+ },
1500
+ {
1501
+ "name": "ContractValue",
1502
+ "label": "Contract Value",
1503
+ "type": "currency",
1504
+ "required": true,
1505
+ "min": 0
1506
+ },
1507
+ {
1508
+ "name": "SecondApprover",
1509
+ "label": "Second Approver",
1510
+ "type": "user"
1511
+ },
1512
+ {
1513
+ "name": "RequestDate",
1514
+ "label": "Request Date",
1515
+ "type": "date"
1516
+ },
1517
+ {
1518
+ "name": "StartDate",
1519
+ "label": "Start Date",
1520
+ "type": "date"
1521
+ },
1522
+ {
1523
+ "name": "Status",
1524
+ "label": "Status",
1525
+ "type": "choice",
1526
+ "choices": [
1527
+ "Submitted",
1528
+ "Approved",
1529
+ "Rejected"
1530
+ ]
1531
+ },
1532
+ {
1533
+ "name": "TaxID",
1534
+ "label": "Tax ID",
1535
+ "type": "text"
1536
+ }
1537
+ ],
1538
+ "rules": [
1539
+ {
1540
+ "id": "R1",
1541
+ "kind": "visible",
1542
+ "field": "Justification",
1543
+ "when": {
1544
+ "field": "RiskRating",
1545
+ "eq": "High"
1546
+ }
1547
+ },
1548
+ {
1549
+ "id": "R2",
1550
+ "kind": "required",
1551
+ "field": "Justification",
1552
+ "when": {
1553
+ "field": "RiskRating",
1554
+ "eq": "High"
1555
+ },
1556
+ "message": "Justification is required for high-risk vendors.",
1557
+ "presentation": "alert"
1558
+ },
1559
+ {
1560
+ "id": "R3",
1561
+ "kind": "required",
1562
+ "field": "SecondApprover",
1563
+ "when": {
1564
+ "field": "ContractValue",
1565
+ "gt": 100000
1566
+ },
1567
+ "message": "A Second Approver is required for contracts over $100,000.",
1568
+ "presentation": "alert"
1569
+ },
1570
+ {
1571
+ "id": "R4",
1572
+ "kind": "compare",
1573
+ "field": "StartDate",
1574
+ "params": {
1575
+ "op": "gte",
1576
+ "other": "RequestDate"
1577
+ },
1578
+ "message": "Start Date cannot be before the Request Date.",
1579
+ "presentation": "alert"
1580
+ },
1581
+ {
1582
+ "id": "R5",
1583
+ "kind": "readonly",
1584
+ "field": "Status",
1585
+ "when": {
1586
+ "not": {
1587
+ "role": "Compliance Reviewers"
1588
+ }
1589
+ }
1590
+ },
1591
+ {
1592
+ "id": "R6",
1593
+ "kind": "length",
1594
+ "field": "TaxID",
1595
+ "params": {
1596
+ "op": "eq",
1597
+ "value": 9,
1598
+ "allow_empty": true
1599
+ },
1600
+ "message": "Tax ID must be exactly 9 digits."
1601
+ },
1602
+ {
1603
+ "id": "R7",
1604
+ "kind": "visible",
1605
+ "field": "Status",
1606
+ "modes": [
1607
+ "edit",
1608
+ "display"
1609
+ ]
1610
+ },
1611
+ {
1612
+ "id": "R8",
1613
+ "kind": "lock_item",
1614
+ "modes": [
1615
+ "edit"
1616
+ ],
1617
+ "when": {
1618
+ "all": [
1619
+ {
1620
+ "field": "Status",
1621
+ "in": [
1622
+ "Approved",
1623
+ "Rejected"
1624
+ ]
1625
+ },
1626
+ {
1627
+ "not": {
1628
+ "role": "Compliance Reviewers"
1629
+ }
1630
+ }
1631
+ ]
1632
+ },
1633
+ "message": "This request has been {Status|lower} and can no longer be edited."
1634
+ },
1635
+ {
1636
+ "id": "R9",
1637
+ "kind": "set_on_save",
1638
+ "field": "Status",
1639
+ "modes": [
1640
+ "new"
1641
+ ],
1642
+ "params": {
1643
+ "value": "Submitted"
1644
+ }
1645
+ },
1646
+ {
1647
+ "id": "R10",
1648
+ "kind": "banner",
1649
+ "modes": [
1650
+ "display"
1651
+ ],
1652
+ "when": {
1653
+ "field": "Country.title",
1654
+ "eq": "Iran"
1655
+ },
1656
+ "message": "Sanctioned country: {Country.title}"
1657
+ }
1658
+ ],
1659
+ "ctx": {
1660
+ "mode": "new",
1661
+ "values": {
1662
+ "Title": "Acme",
1663
+ "Country": {
1664
+ "id": 3,
1665
+ "title": "Germany"
1666
+ },
1667
+ "RiskRating": "Low",
1668
+ "ContractValue": "18,500",
1669
+ "RequestDate": "2026-10-01",
1670
+ "StartDate": "2026-09-01",
1671
+ "TaxID": "123456789"
1672
+ },
1673
+ "original": {},
1674
+ "user": {
1675
+ "id": 6,
1676
+ "login": "corp\\alice",
1677
+ "title": "Alice",
1678
+ "groups": [
1679
+ "Procurement Members"
1680
+ ]
1681
+ }
1682
+ },
1683
+ "expected": [
1684
+ [
1685
+ "StartDate",
1686
+ "Start Date cannot be before the Request Date.",
1687
+ "R4"
1688
+ ]
1689
+ ]
1690
+ },
1691
+ {
1692
+ "name": "taxid length",
1693
+ "type": "validate",
1694
+ "fields": [
1695
+ {
1696
+ "name": "Title",
1697
+ "label": "Vendor Name",
1698
+ "type": "text",
1699
+ "required": true,
1700
+ "max_length": 20
1701
+ },
1702
+ {
1703
+ "name": "Country",
1704
+ "label": "Country",
1705
+ "type": "lookup",
1706
+ "required": true,
1707
+ "lookup": {
1708
+ "list": "Countries",
1709
+ "field": "Title"
1710
+ }
1711
+ },
1712
+ {
1713
+ "name": "RiskRating",
1714
+ "label": "Risk Rating",
1715
+ "type": "text"
1716
+ },
1717
+ {
1718
+ "name": "Justification",
1719
+ "label": "Justification",
1720
+ "type": "note"
1721
+ },
1722
+ {
1723
+ "name": "ContractValue",
1724
+ "label": "Contract Value",
1725
+ "type": "currency",
1726
+ "required": true,
1727
+ "min": 0
1728
+ },
1729
+ {
1730
+ "name": "SecondApprover",
1731
+ "label": "Second Approver",
1732
+ "type": "user"
1733
+ },
1734
+ {
1735
+ "name": "RequestDate",
1736
+ "label": "Request Date",
1737
+ "type": "date"
1738
+ },
1739
+ {
1740
+ "name": "StartDate",
1741
+ "label": "Start Date",
1742
+ "type": "date"
1743
+ },
1744
+ {
1745
+ "name": "Status",
1746
+ "label": "Status",
1747
+ "type": "choice",
1748
+ "choices": [
1749
+ "Submitted",
1750
+ "Approved",
1751
+ "Rejected"
1752
+ ]
1753
+ },
1754
+ {
1755
+ "name": "TaxID",
1756
+ "label": "Tax ID",
1757
+ "type": "text"
1758
+ }
1759
+ ],
1760
+ "rules": [
1761
+ {
1762
+ "id": "R1",
1763
+ "kind": "visible",
1764
+ "field": "Justification",
1765
+ "when": {
1766
+ "field": "RiskRating",
1767
+ "eq": "High"
1768
+ }
1769
+ },
1770
+ {
1771
+ "id": "R2",
1772
+ "kind": "required",
1773
+ "field": "Justification",
1774
+ "when": {
1775
+ "field": "RiskRating",
1776
+ "eq": "High"
1777
+ },
1778
+ "message": "Justification is required for high-risk vendors.",
1779
+ "presentation": "alert"
1780
+ },
1781
+ {
1782
+ "id": "R3",
1783
+ "kind": "required",
1784
+ "field": "SecondApprover",
1785
+ "when": {
1786
+ "field": "ContractValue",
1787
+ "gt": 100000
1788
+ },
1789
+ "message": "A Second Approver is required for contracts over $100,000.",
1790
+ "presentation": "alert"
1791
+ },
1792
+ {
1793
+ "id": "R4",
1794
+ "kind": "compare",
1795
+ "field": "StartDate",
1796
+ "params": {
1797
+ "op": "gte",
1798
+ "other": "RequestDate"
1799
+ },
1800
+ "message": "Start Date cannot be before the Request Date.",
1801
+ "presentation": "alert"
1802
+ },
1803
+ {
1804
+ "id": "R5",
1805
+ "kind": "readonly",
1806
+ "field": "Status",
1807
+ "when": {
1808
+ "not": {
1809
+ "role": "Compliance Reviewers"
1810
+ }
1811
+ }
1812
+ },
1813
+ {
1814
+ "id": "R6",
1815
+ "kind": "length",
1816
+ "field": "TaxID",
1817
+ "params": {
1818
+ "op": "eq",
1819
+ "value": 9,
1820
+ "allow_empty": true
1821
+ },
1822
+ "message": "Tax ID must be exactly 9 digits."
1823
+ },
1824
+ {
1825
+ "id": "R7",
1826
+ "kind": "visible",
1827
+ "field": "Status",
1828
+ "modes": [
1829
+ "edit",
1830
+ "display"
1831
+ ]
1832
+ },
1833
+ {
1834
+ "id": "R8",
1835
+ "kind": "lock_item",
1836
+ "modes": [
1837
+ "edit"
1838
+ ],
1839
+ "when": {
1840
+ "all": [
1841
+ {
1842
+ "field": "Status",
1843
+ "in": [
1844
+ "Approved",
1845
+ "Rejected"
1846
+ ]
1847
+ },
1848
+ {
1849
+ "not": {
1850
+ "role": "Compliance Reviewers"
1851
+ }
1852
+ }
1853
+ ]
1854
+ },
1855
+ "message": "This request has been {Status|lower} and can no longer be edited."
1856
+ },
1857
+ {
1858
+ "id": "R9",
1859
+ "kind": "set_on_save",
1860
+ "field": "Status",
1861
+ "modes": [
1862
+ "new"
1863
+ ],
1864
+ "params": {
1865
+ "value": "Submitted"
1866
+ }
1867
+ },
1868
+ {
1869
+ "id": "R10",
1870
+ "kind": "banner",
1871
+ "modes": [
1872
+ "display"
1873
+ ],
1874
+ "when": {
1875
+ "field": "Country.title",
1876
+ "eq": "Iran"
1877
+ },
1878
+ "message": "Sanctioned country: {Country.title}"
1879
+ }
1880
+ ],
1881
+ "ctx": {
1882
+ "mode": "new",
1883
+ "values": {
1884
+ "Title": "Acme",
1885
+ "Country": {
1886
+ "id": 3,
1887
+ "title": "Germany"
1888
+ },
1889
+ "RiskRating": "Low",
1890
+ "ContractValue": "18,500",
1891
+ "RequestDate": "2026-10-01",
1892
+ "StartDate": "2026-10-05",
1893
+ "TaxID": "123"
1894
+ },
1895
+ "original": {},
1896
+ "user": {
1897
+ "id": 6,
1898
+ "login": "corp\\alice",
1899
+ "title": "Alice",
1900
+ "groups": [
1901
+ "Procurement Members"
1902
+ ]
1903
+ }
1904
+ },
1905
+ "expected": [
1906
+ [
1907
+ "TaxID",
1908
+ "Tax ID must be exactly 9 digits.",
1909
+ "R6"
1910
+ ]
1911
+ ]
1912
+ },
1913
+ {
1914
+ "name": "max length + min",
1915
+ "type": "validate",
1916
+ "fields": [
1917
+ {
1918
+ "name": "Title",
1919
+ "label": "Vendor Name",
1920
+ "type": "text",
1921
+ "required": true,
1922
+ "max_length": 20
1923
+ },
1924
+ {
1925
+ "name": "Country",
1926
+ "label": "Country",
1927
+ "type": "lookup",
1928
+ "required": true,
1929
+ "lookup": {
1930
+ "list": "Countries",
1931
+ "field": "Title"
1932
+ }
1933
+ },
1934
+ {
1935
+ "name": "RiskRating",
1936
+ "label": "Risk Rating",
1937
+ "type": "text"
1938
+ },
1939
+ {
1940
+ "name": "Justification",
1941
+ "label": "Justification",
1942
+ "type": "note"
1943
+ },
1944
+ {
1945
+ "name": "ContractValue",
1946
+ "label": "Contract Value",
1947
+ "type": "currency",
1948
+ "required": true,
1949
+ "min": 0
1950
+ },
1951
+ {
1952
+ "name": "SecondApprover",
1953
+ "label": "Second Approver",
1954
+ "type": "user"
1955
+ },
1956
+ {
1957
+ "name": "RequestDate",
1958
+ "label": "Request Date",
1959
+ "type": "date"
1960
+ },
1961
+ {
1962
+ "name": "StartDate",
1963
+ "label": "Start Date",
1964
+ "type": "date"
1965
+ },
1966
+ {
1967
+ "name": "Status",
1968
+ "label": "Status",
1969
+ "type": "choice",
1970
+ "choices": [
1971
+ "Submitted",
1972
+ "Approved",
1973
+ "Rejected"
1974
+ ]
1975
+ },
1976
+ {
1977
+ "name": "TaxID",
1978
+ "label": "Tax ID",
1979
+ "type": "text"
1980
+ }
1981
+ ],
1982
+ "rules": [
1983
+ {
1984
+ "id": "R1",
1985
+ "kind": "visible",
1986
+ "field": "Justification",
1987
+ "when": {
1988
+ "field": "RiskRating",
1989
+ "eq": "High"
1990
+ }
1991
+ },
1992
+ {
1993
+ "id": "R2",
1994
+ "kind": "required",
1995
+ "field": "Justification",
1996
+ "when": {
1997
+ "field": "RiskRating",
1998
+ "eq": "High"
1999
+ },
2000
+ "message": "Justification is required for high-risk vendors.",
2001
+ "presentation": "alert"
2002
+ },
2003
+ {
2004
+ "id": "R3",
2005
+ "kind": "required",
2006
+ "field": "SecondApprover",
2007
+ "when": {
2008
+ "field": "ContractValue",
2009
+ "gt": 100000
2010
+ },
2011
+ "message": "A Second Approver is required for contracts over $100,000.",
2012
+ "presentation": "alert"
2013
+ },
2014
+ {
2015
+ "id": "R4",
2016
+ "kind": "compare",
2017
+ "field": "StartDate",
2018
+ "params": {
2019
+ "op": "gte",
2020
+ "other": "RequestDate"
2021
+ },
2022
+ "message": "Start Date cannot be before the Request Date.",
2023
+ "presentation": "alert"
2024
+ },
2025
+ {
2026
+ "id": "R5",
2027
+ "kind": "readonly",
2028
+ "field": "Status",
2029
+ "when": {
2030
+ "not": {
2031
+ "role": "Compliance Reviewers"
2032
+ }
2033
+ }
2034
+ },
2035
+ {
2036
+ "id": "R6",
2037
+ "kind": "length",
2038
+ "field": "TaxID",
2039
+ "params": {
2040
+ "op": "eq",
2041
+ "value": 9,
2042
+ "allow_empty": true
2043
+ },
2044
+ "message": "Tax ID must be exactly 9 digits."
2045
+ },
2046
+ {
2047
+ "id": "R7",
2048
+ "kind": "visible",
2049
+ "field": "Status",
2050
+ "modes": [
2051
+ "edit",
2052
+ "display"
2053
+ ]
2054
+ },
2055
+ {
2056
+ "id": "R8",
2057
+ "kind": "lock_item",
2058
+ "modes": [
2059
+ "edit"
2060
+ ],
2061
+ "when": {
2062
+ "all": [
2063
+ {
2064
+ "field": "Status",
2065
+ "in": [
2066
+ "Approved",
2067
+ "Rejected"
2068
+ ]
2069
+ },
2070
+ {
2071
+ "not": {
2072
+ "role": "Compliance Reviewers"
2073
+ }
2074
+ }
2075
+ ]
2076
+ },
2077
+ "message": "This request has been {Status|lower} and can no longer be edited."
2078
+ },
2079
+ {
2080
+ "id": "R9",
2081
+ "kind": "set_on_save",
2082
+ "field": "Status",
2083
+ "modes": [
2084
+ "new"
2085
+ ],
2086
+ "params": {
2087
+ "value": "Submitted"
2088
+ }
2089
+ },
2090
+ {
2091
+ "id": "R10",
2092
+ "kind": "banner",
2093
+ "modes": [
2094
+ "display"
2095
+ ],
2096
+ "when": {
2097
+ "field": "Country.title",
2098
+ "eq": "Iran"
2099
+ },
2100
+ "message": "Sanctioned country: {Country.title}"
2101
+ }
2102
+ ],
2103
+ "ctx": {
2104
+ "mode": "new",
2105
+ "values": {
2106
+ "Title": "xxxxxxxxxxxxxxxxxxxxxxxxx",
2107
+ "Country": {
2108
+ "id": 3,
2109
+ "title": "Germany"
2110
+ },
2111
+ "RiskRating": "Low",
2112
+ "ContractValue": "-5",
2113
+ "RequestDate": "2026-10-01",
2114
+ "StartDate": "2026-10-05",
2115
+ "TaxID": "123456789"
2116
+ },
2117
+ "original": {},
2118
+ "user": {
2119
+ "id": 6,
2120
+ "login": "corp\\alice",
2121
+ "title": "Alice",
2122
+ "groups": [
2123
+ "Procurement Members"
2124
+ ]
2125
+ }
2126
+ },
2127
+ "expected": [
2128
+ [
2129
+ "Title",
2130
+ "The value of this field may not contain more than 20 characters.",
2131
+ "schema"
2132
+ ],
2133
+ [
2134
+ "ContractValue",
2135
+ "The value of this field must be greater than or equal to 0.",
2136
+ "schema"
2137
+ ]
2138
+ ]
2139
+ },
2140
+ {
2141
+ "name": "display mode never validates",
2142
+ "type": "validate",
2143
+ "fields": [
2144
+ {
2145
+ "name": "Title",
2146
+ "label": "Vendor Name",
2147
+ "type": "text",
2148
+ "required": true,
2149
+ "max_length": 20
2150
+ },
2151
+ {
2152
+ "name": "Country",
2153
+ "label": "Country",
2154
+ "type": "lookup",
2155
+ "required": true,
2156
+ "lookup": {
2157
+ "list": "Countries",
2158
+ "field": "Title"
2159
+ }
2160
+ },
2161
+ {
2162
+ "name": "RiskRating",
2163
+ "label": "Risk Rating",
2164
+ "type": "text"
2165
+ },
2166
+ {
2167
+ "name": "Justification",
2168
+ "label": "Justification",
2169
+ "type": "note"
2170
+ },
2171
+ {
2172
+ "name": "ContractValue",
2173
+ "label": "Contract Value",
2174
+ "type": "currency",
2175
+ "required": true,
2176
+ "min": 0
2177
+ },
2178
+ {
2179
+ "name": "SecondApprover",
2180
+ "label": "Second Approver",
2181
+ "type": "user"
2182
+ },
2183
+ {
2184
+ "name": "RequestDate",
2185
+ "label": "Request Date",
2186
+ "type": "date"
2187
+ },
2188
+ {
2189
+ "name": "StartDate",
2190
+ "label": "Start Date",
2191
+ "type": "date"
2192
+ },
2193
+ {
2194
+ "name": "Status",
2195
+ "label": "Status",
2196
+ "type": "choice",
2197
+ "choices": [
2198
+ "Submitted",
2199
+ "Approved",
2200
+ "Rejected"
2201
+ ]
2202
+ },
2203
+ {
2204
+ "name": "TaxID",
2205
+ "label": "Tax ID",
2206
+ "type": "text"
2207
+ }
2208
+ ],
2209
+ "rules": [
2210
+ {
2211
+ "id": "R1",
2212
+ "kind": "visible",
2213
+ "field": "Justification",
2214
+ "when": {
2215
+ "field": "RiskRating",
2216
+ "eq": "High"
2217
+ }
2218
+ },
2219
+ {
2220
+ "id": "R2",
2221
+ "kind": "required",
2222
+ "field": "Justification",
2223
+ "when": {
2224
+ "field": "RiskRating",
2225
+ "eq": "High"
2226
+ },
2227
+ "message": "Justification is required for high-risk vendors.",
2228
+ "presentation": "alert"
2229
+ },
2230
+ {
2231
+ "id": "R3",
2232
+ "kind": "required",
2233
+ "field": "SecondApprover",
2234
+ "when": {
2235
+ "field": "ContractValue",
2236
+ "gt": 100000
2237
+ },
2238
+ "message": "A Second Approver is required for contracts over $100,000.",
2239
+ "presentation": "alert"
2240
+ },
2241
+ {
2242
+ "id": "R4",
2243
+ "kind": "compare",
2244
+ "field": "StartDate",
2245
+ "params": {
2246
+ "op": "gte",
2247
+ "other": "RequestDate"
2248
+ },
2249
+ "message": "Start Date cannot be before the Request Date.",
2250
+ "presentation": "alert"
2251
+ },
2252
+ {
2253
+ "id": "R5",
2254
+ "kind": "readonly",
2255
+ "field": "Status",
2256
+ "when": {
2257
+ "not": {
2258
+ "role": "Compliance Reviewers"
2259
+ }
2260
+ }
2261
+ },
2262
+ {
2263
+ "id": "R6",
2264
+ "kind": "length",
2265
+ "field": "TaxID",
2266
+ "params": {
2267
+ "op": "eq",
2268
+ "value": 9,
2269
+ "allow_empty": true
2270
+ },
2271
+ "message": "Tax ID must be exactly 9 digits."
2272
+ },
2273
+ {
2274
+ "id": "R7",
2275
+ "kind": "visible",
2276
+ "field": "Status",
2277
+ "modes": [
2278
+ "edit",
2279
+ "display"
2280
+ ]
2281
+ },
2282
+ {
2283
+ "id": "R8",
2284
+ "kind": "lock_item",
2285
+ "modes": [
2286
+ "edit"
2287
+ ],
2288
+ "when": {
2289
+ "all": [
2290
+ {
2291
+ "field": "Status",
2292
+ "in": [
2293
+ "Approved",
2294
+ "Rejected"
2295
+ ]
2296
+ },
2297
+ {
2298
+ "not": {
2299
+ "role": "Compliance Reviewers"
2300
+ }
2301
+ }
2302
+ ]
2303
+ },
2304
+ "message": "This request has been {Status|lower} and can no longer be edited."
2305
+ },
2306
+ {
2307
+ "id": "R9",
2308
+ "kind": "set_on_save",
2309
+ "field": "Status",
2310
+ "modes": [
2311
+ "new"
2312
+ ],
2313
+ "params": {
2314
+ "value": "Submitted"
2315
+ }
2316
+ },
2317
+ {
2318
+ "id": "R10",
2319
+ "kind": "banner",
2320
+ "modes": [
2321
+ "display"
2322
+ ],
2323
+ "when": {
2324
+ "field": "Country.title",
2325
+ "eq": "Iran"
2326
+ },
2327
+ "message": "Sanctioned country: {Country.title}"
2328
+ }
2329
+ ],
2330
+ "ctx": {
2331
+ "mode": "display",
2332
+ "values": {},
2333
+ "original": {},
2334
+ "user": {
2335
+ "id": 6,
2336
+ "login": "corp\\alice",
2337
+ "title": "Alice",
2338
+ "groups": [
2339
+ "Procurement Members"
2340
+ ]
2341
+ }
2342
+ },
2343
+ "expected": []
2344
+ },
2345
+ {
2346
+ "name": "status hidden on new",
2347
+ "type": "field_state",
2348
+ "field": {
2349
+ "name": "Status",
2350
+ "label": "Status",
2351
+ "type": "choice",
2352
+ "choices": [
2353
+ "Submitted",
2354
+ "Approved",
2355
+ "Rejected"
2356
+ ]
2357
+ },
2358
+ "rules": [
2359
+ {
2360
+ "id": "R1",
2361
+ "kind": "visible",
2362
+ "field": "Justification",
2363
+ "when": {
2364
+ "field": "RiskRating",
2365
+ "eq": "High"
2366
+ }
2367
+ },
2368
+ {
2369
+ "id": "R2",
2370
+ "kind": "required",
2371
+ "field": "Justification",
2372
+ "when": {
2373
+ "field": "RiskRating",
2374
+ "eq": "High"
2375
+ },
2376
+ "message": "Justification is required for high-risk vendors.",
2377
+ "presentation": "alert"
2378
+ },
2379
+ {
2380
+ "id": "R3",
2381
+ "kind": "required",
2382
+ "field": "SecondApprover",
2383
+ "when": {
2384
+ "field": "ContractValue",
2385
+ "gt": 100000
2386
+ },
2387
+ "message": "A Second Approver is required for contracts over $100,000.",
2388
+ "presentation": "alert"
2389
+ },
2390
+ {
2391
+ "id": "R4",
2392
+ "kind": "compare",
2393
+ "field": "StartDate",
2394
+ "params": {
2395
+ "op": "gte",
2396
+ "other": "RequestDate"
2397
+ },
2398
+ "message": "Start Date cannot be before the Request Date.",
2399
+ "presentation": "alert"
2400
+ },
2401
+ {
2402
+ "id": "R5",
2403
+ "kind": "readonly",
2404
+ "field": "Status",
2405
+ "when": {
2406
+ "not": {
2407
+ "role": "Compliance Reviewers"
2408
+ }
2409
+ }
2410
+ },
2411
+ {
2412
+ "id": "R6",
2413
+ "kind": "length",
2414
+ "field": "TaxID",
2415
+ "params": {
2416
+ "op": "eq",
2417
+ "value": 9,
2418
+ "allow_empty": true
2419
+ },
2420
+ "message": "Tax ID must be exactly 9 digits."
2421
+ },
2422
+ {
2423
+ "id": "R7",
2424
+ "kind": "visible",
2425
+ "field": "Status",
2426
+ "modes": [
2427
+ "edit",
2428
+ "display"
2429
+ ]
2430
+ },
2431
+ {
2432
+ "id": "R8",
2433
+ "kind": "lock_item",
2434
+ "modes": [
2435
+ "edit"
2436
+ ],
2437
+ "when": {
2438
+ "all": [
2439
+ {
2440
+ "field": "Status",
2441
+ "in": [
2442
+ "Approved",
2443
+ "Rejected"
2444
+ ]
2445
+ },
2446
+ {
2447
+ "not": {
2448
+ "role": "Compliance Reviewers"
2449
+ }
2450
+ }
2451
+ ]
2452
+ },
2453
+ "message": "This request has been {Status|lower} and can no longer be edited."
2454
+ },
2455
+ {
2456
+ "id": "R9",
2457
+ "kind": "set_on_save",
2458
+ "field": "Status",
2459
+ "modes": [
2460
+ "new"
2461
+ ],
2462
+ "params": {
2463
+ "value": "Submitted"
2464
+ }
2465
+ },
2466
+ {
2467
+ "id": "R10",
2468
+ "kind": "banner",
2469
+ "modes": [
2470
+ "display"
2471
+ ],
2472
+ "when": {
2473
+ "field": "Country.title",
2474
+ "eq": "Iran"
2475
+ },
2476
+ "message": "Sanctioned country: {Country.title}"
2477
+ }
2478
+ ],
2479
+ "ctx": {
2480
+ "mode": "new",
2481
+ "values": {
2482
+ "Title": "Acme",
2483
+ "Country": {
2484
+ "id": 3,
2485
+ "title": "Germany"
2486
+ },
2487
+ "RiskRating": "Low",
2488
+ "ContractValue": "18,500",
2489
+ "RequestDate": "2026-10-01",
2490
+ "StartDate": "2026-10-05",
2491
+ "TaxID": "123456789"
2492
+ },
2493
+ "original": {},
2494
+ "user": {
2495
+ "id": 6,
2496
+ "login": "corp\\alice",
2497
+ "title": "Alice",
2498
+ "groups": [
2499
+ "Procurement Members"
2500
+ ]
2501
+ }
2502
+ },
2503
+ "expected": {
2504
+ "visible": false,
2505
+ "readonly": true,
2506
+ "required": false
2507
+ }
2508
+ },
2509
+ {
2510
+ "name": "status readonly for requester",
2511
+ "type": "field_state",
2512
+ "field": {
2513
+ "name": "Status",
2514
+ "label": "Status",
2515
+ "type": "choice",
2516
+ "choices": [
2517
+ "Submitted",
2518
+ "Approved",
2519
+ "Rejected"
2520
+ ]
2521
+ },
2522
+ "rules": [
2523
+ {
2524
+ "id": "R1",
2525
+ "kind": "visible",
2526
+ "field": "Justification",
2527
+ "when": {
2528
+ "field": "RiskRating",
2529
+ "eq": "High"
2530
+ }
2531
+ },
2532
+ {
2533
+ "id": "R2",
2534
+ "kind": "required",
2535
+ "field": "Justification",
2536
+ "when": {
2537
+ "field": "RiskRating",
2538
+ "eq": "High"
2539
+ },
2540
+ "message": "Justification is required for high-risk vendors.",
2541
+ "presentation": "alert"
2542
+ },
2543
+ {
2544
+ "id": "R3",
2545
+ "kind": "required",
2546
+ "field": "SecondApprover",
2547
+ "when": {
2548
+ "field": "ContractValue",
2549
+ "gt": 100000
2550
+ },
2551
+ "message": "A Second Approver is required for contracts over $100,000.",
2552
+ "presentation": "alert"
2553
+ },
2554
+ {
2555
+ "id": "R4",
2556
+ "kind": "compare",
2557
+ "field": "StartDate",
2558
+ "params": {
2559
+ "op": "gte",
2560
+ "other": "RequestDate"
2561
+ },
2562
+ "message": "Start Date cannot be before the Request Date.",
2563
+ "presentation": "alert"
2564
+ },
2565
+ {
2566
+ "id": "R5",
2567
+ "kind": "readonly",
2568
+ "field": "Status",
2569
+ "when": {
2570
+ "not": {
2571
+ "role": "Compliance Reviewers"
2572
+ }
2573
+ }
2574
+ },
2575
+ {
2576
+ "id": "R6",
2577
+ "kind": "length",
2578
+ "field": "TaxID",
2579
+ "params": {
2580
+ "op": "eq",
2581
+ "value": 9,
2582
+ "allow_empty": true
2583
+ },
2584
+ "message": "Tax ID must be exactly 9 digits."
2585
+ },
2586
+ {
2587
+ "id": "R7",
2588
+ "kind": "visible",
2589
+ "field": "Status",
2590
+ "modes": [
2591
+ "edit",
2592
+ "display"
2593
+ ]
2594
+ },
2595
+ {
2596
+ "id": "R8",
2597
+ "kind": "lock_item",
2598
+ "modes": [
2599
+ "edit"
2600
+ ],
2601
+ "when": {
2602
+ "all": [
2603
+ {
2604
+ "field": "Status",
2605
+ "in": [
2606
+ "Approved",
2607
+ "Rejected"
2608
+ ]
2609
+ },
2610
+ {
2611
+ "not": {
2612
+ "role": "Compliance Reviewers"
2613
+ }
2614
+ }
2615
+ ]
2616
+ },
2617
+ "message": "This request has been {Status|lower} and can no longer be edited."
2618
+ },
2619
+ {
2620
+ "id": "R9",
2621
+ "kind": "set_on_save",
2622
+ "field": "Status",
2623
+ "modes": [
2624
+ "new"
2625
+ ],
2626
+ "params": {
2627
+ "value": "Submitted"
2628
+ }
2629
+ },
2630
+ {
2631
+ "id": "R10",
2632
+ "kind": "banner",
2633
+ "modes": [
2634
+ "display"
2635
+ ],
2636
+ "when": {
2637
+ "field": "Country.title",
2638
+ "eq": "Iran"
2639
+ },
2640
+ "message": "Sanctioned country: {Country.title}"
2641
+ }
2642
+ ],
2643
+ "ctx": {
2644
+ "mode": "edit",
2645
+ "values": {
2646
+ "Title": "Acme",
2647
+ "Country": {
2648
+ "id": 3,
2649
+ "title": "Germany"
2650
+ },
2651
+ "RiskRating": "Low",
2652
+ "ContractValue": "18,500",
2653
+ "RequestDate": "2026-10-01",
2654
+ "StartDate": "2026-10-05",
2655
+ "TaxID": "123456789"
2656
+ },
2657
+ "original": {},
2658
+ "user": {
2659
+ "id": 6,
2660
+ "login": "corp\\alice",
2661
+ "title": "Alice",
2662
+ "groups": [
2663
+ "Procurement Members"
2664
+ ]
2665
+ }
2666
+ },
2667
+ "expected": {
2668
+ "visible": true,
2669
+ "readonly": true,
2670
+ "required": false
2671
+ }
2672
+ },
2673
+ {
2674
+ "name": "status editable for reviewer",
2675
+ "type": "field_state",
2676
+ "field": {
2677
+ "name": "Status",
2678
+ "label": "Status",
2679
+ "type": "choice",
2680
+ "choices": [
2681
+ "Submitted",
2682
+ "Approved",
2683
+ "Rejected"
2684
+ ]
2685
+ },
2686
+ "rules": [
2687
+ {
2688
+ "id": "R1",
2689
+ "kind": "visible",
2690
+ "field": "Justification",
2691
+ "when": {
2692
+ "field": "RiskRating",
2693
+ "eq": "High"
2694
+ }
2695
+ },
2696
+ {
2697
+ "id": "R2",
2698
+ "kind": "required",
2699
+ "field": "Justification",
2700
+ "when": {
2701
+ "field": "RiskRating",
2702
+ "eq": "High"
2703
+ },
2704
+ "message": "Justification is required for high-risk vendors.",
2705
+ "presentation": "alert"
2706
+ },
2707
+ {
2708
+ "id": "R3",
2709
+ "kind": "required",
2710
+ "field": "SecondApprover",
2711
+ "when": {
2712
+ "field": "ContractValue",
2713
+ "gt": 100000
2714
+ },
2715
+ "message": "A Second Approver is required for contracts over $100,000.",
2716
+ "presentation": "alert"
2717
+ },
2718
+ {
2719
+ "id": "R4",
2720
+ "kind": "compare",
2721
+ "field": "StartDate",
2722
+ "params": {
2723
+ "op": "gte",
2724
+ "other": "RequestDate"
2725
+ },
2726
+ "message": "Start Date cannot be before the Request Date.",
2727
+ "presentation": "alert"
2728
+ },
2729
+ {
2730
+ "id": "R5",
2731
+ "kind": "readonly",
2732
+ "field": "Status",
2733
+ "when": {
2734
+ "not": {
2735
+ "role": "Compliance Reviewers"
2736
+ }
2737
+ }
2738
+ },
2739
+ {
2740
+ "id": "R6",
2741
+ "kind": "length",
2742
+ "field": "TaxID",
2743
+ "params": {
2744
+ "op": "eq",
2745
+ "value": 9,
2746
+ "allow_empty": true
2747
+ },
2748
+ "message": "Tax ID must be exactly 9 digits."
2749
+ },
2750
+ {
2751
+ "id": "R7",
2752
+ "kind": "visible",
2753
+ "field": "Status",
2754
+ "modes": [
2755
+ "edit",
2756
+ "display"
2757
+ ]
2758
+ },
2759
+ {
2760
+ "id": "R8",
2761
+ "kind": "lock_item",
2762
+ "modes": [
2763
+ "edit"
2764
+ ],
2765
+ "when": {
2766
+ "all": [
2767
+ {
2768
+ "field": "Status",
2769
+ "in": [
2770
+ "Approved",
2771
+ "Rejected"
2772
+ ]
2773
+ },
2774
+ {
2775
+ "not": {
2776
+ "role": "Compliance Reviewers"
2777
+ }
2778
+ }
2779
+ ]
2780
+ },
2781
+ "message": "This request has been {Status|lower} and can no longer be edited."
2782
+ },
2783
+ {
2784
+ "id": "R9",
2785
+ "kind": "set_on_save",
2786
+ "field": "Status",
2787
+ "modes": [
2788
+ "new"
2789
+ ],
2790
+ "params": {
2791
+ "value": "Submitted"
2792
+ }
2793
+ },
2794
+ {
2795
+ "id": "R10",
2796
+ "kind": "banner",
2797
+ "modes": [
2798
+ "display"
2799
+ ],
2800
+ "when": {
2801
+ "field": "Country.title",
2802
+ "eq": "Iran"
2803
+ },
2804
+ "message": "Sanctioned country: {Country.title}"
2805
+ }
2806
+ ],
2807
+ "ctx": {
2808
+ "mode": "edit",
2809
+ "values": {
2810
+ "Title": "Acme",
2811
+ "Country": {
2812
+ "id": 3,
2813
+ "title": "Germany"
2814
+ },
2815
+ "RiskRating": "Low",
2816
+ "ContractValue": "18,500",
2817
+ "RequestDate": "2026-10-01",
2818
+ "StartDate": "2026-10-05",
2819
+ "TaxID": "123456789"
2820
+ },
2821
+ "original": {},
2822
+ "user": {
2823
+ "id": 7,
2824
+ "login": "corp\\bob",
2825
+ "title": "Bob",
2826
+ "groups": [
2827
+ "Compliance Reviewers"
2828
+ ]
2829
+ }
2830
+ },
2831
+ "expected": {
2832
+ "visible": true,
2833
+ "readonly": false,
2834
+ "required": false
2835
+ }
2836
+ },
2837
+ {
2838
+ "name": "justification shown when high",
2839
+ "type": "field_state",
2840
+ "field": {
2841
+ "name": "Justification",
2842
+ "label": "Justification",
2843
+ "type": "note"
2844
+ },
2845
+ "rules": [
2846
+ {
2847
+ "id": "R1",
2848
+ "kind": "visible",
2849
+ "field": "Justification",
2850
+ "when": {
2851
+ "field": "RiskRating",
2852
+ "eq": "High"
2853
+ }
2854
+ },
2855
+ {
2856
+ "id": "R2",
2857
+ "kind": "required",
2858
+ "field": "Justification",
2859
+ "when": {
2860
+ "field": "RiskRating",
2861
+ "eq": "High"
2862
+ },
2863
+ "message": "Justification is required for high-risk vendors.",
2864
+ "presentation": "alert"
2865
+ },
2866
+ {
2867
+ "id": "R3",
2868
+ "kind": "required",
2869
+ "field": "SecondApprover",
2870
+ "when": {
2871
+ "field": "ContractValue",
2872
+ "gt": 100000
2873
+ },
2874
+ "message": "A Second Approver is required for contracts over $100,000.",
2875
+ "presentation": "alert"
2876
+ },
2877
+ {
2878
+ "id": "R4",
2879
+ "kind": "compare",
2880
+ "field": "StartDate",
2881
+ "params": {
2882
+ "op": "gte",
2883
+ "other": "RequestDate"
2884
+ },
2885
+ "message": "Start Date cannot be before the Request Date.",
2886
+ "presentation": "alert"
2887
+ },
2888
+ {
2889
+ "id": "R5",
2890
+ "kind": "readonly",
2891
+ "field": "Status",
2892
+ "when": {
2893
+ "not": {
2894
+ "role": "Compliance Reviewers"
2895
+ }
2896
+ }
2897
+ },
2898
+ {
2899
+ "id": "R6",
2900
+ "kind": "length",
2901
+ "field": "TaxID",
2902
+ "params": {
2903
+ "op": "eq",
2904
+ "value": 9,
2905
+ "allow_empty": true
2906
+ },
2907
+ "message": "Tax ID must be exactly 9 digits."
2908
+ },
2909
+ {
2910
+ "id": "R7",
2911
+ "kind": "visible",
2912
+ "field": "Status",
2913
+ "modes": [
2914
+ "edit",
2915
+ "display"
2916
+ ]
2917
+ },
2918
+ {
2919
+ "id": "R8",
2920
+ "kind": "lock_item",
2921
+ "modes": [
2922
+ "edit"
2923
+ ],
2924
+ "when": {
2925
+ "all": [
2926
+ {
2927
+ "field": "Status",
2928
+ "in": [
2929
+ "Approved",
2930
+ "Rejected"
2931
+ ]
2932
+ },
2933
+ {
2934
+ "not": {
2935
+ "role": "Compliance Reviewers"
2936
+ }
2937
+ }
2938
+ ]
2939
+ },
2940
+ "message": "This request has been {Status|lower} and can no longer be edited."
2941
+ },
2942
+ {
2943
+ "id": "R9",
2944
+ "kind": "set_on_save",
2945
+ "field": "Status",
2946
+ "modes": [
2947
+ "new"
2948
+ ],
2949
+ "params": {
2950
+ "value": "Submitted"
2951
+ }
2952
+ },
2953
+ {
2954
+ "id": "R10",
2955
+ "kind": "banner",
2956
+ "modes": [
2957
+ "display"
2958
+ ],
2959
+ "when": {
2960
+ "field": "Country.title",
2961
+ "eq": "Iran"
2962
+ },
2963
+ "message": "Sanctioned country: {Country.title}"
2964
+ }
2965
+ ],
2966
+ "ctx": {
2967
+ "mode": "new",
2968
+ "values": {
2969
+ "Title": "Acme",
2970
+ "Country": {
2971
+ "id": 3,
2972
+ "title": "Germany"
2973
+ },
2974
+ "RiskRating": "High",
2975
+ "ContractValue": "18,500",
2976
+ "RequestDate": "2026-10-01",
2977
+ "StartDate": "2026-10-05",
2978
+ "TaxID": "123456789"
2979
+ },
2980
+ "original": {},
2981
+ "user": {
2982
+ "id": 6,
2983
+ "login": "corp\\alice",
2984
+ "title": "Alice",
2985
+ "groups": [
2986
+ "Procurement Members"
2987
+ ]
2988
+ }
2989
+ },
2990
+ "expected": {
2991
+ "visible": true,
2992
+ "readonly": false,
2993
+ "required": true
2994
+ }
2995
+ },
2996
+ {
2997
+ "name": "display readonly",
2998
+ "type": "field_state",
2999
+ "field": {
3000
+ "name": "Title",
3001
+ "label": "Vendor Name",
3002
+ "type": "text",
3003
+ "required": true,
3004
+ "max_length": 20
3005
+ },
3006
+ "rules": [
3007
+ {
3008
+ "id": "R1",
3009
+ "kind": "visible",
3010
+ "field": "Justification",
3011
+ "when": {
3012
+ "field": "RiskRating",
3013
+ "eq": "High"
3014
+ }
3015
+ },
3016
+ {
3017
+ "id": "R2",
3018
+ "kind": "required",
3019
+ "field": "Justification",
3020
+ "when": {
3021
+ "field": "RiskRating",
3022
+ "eq": "High"
3023
+ },
3024
+ "message": "Justification is required for high-risk vendors.",
3025
+ "presentation": "alert"
3026
+ },
3027
+ {
3028
+ "id": "R3",
3029
+ "kind": "required",
3030
+ "field": "SecondApprover",
3031
+ "when": {
3032
+ "field": "ContractValue",
3033
+ "gt": 100000
3034
+ },
3035
+ "message": "A Second Approver is required for contracts over $100,000.",
3036
+ "presentation": "alert"
3037
+ },
3038
+ {
3039
+ "id": "R4",
3040
+ "kind": "compare",
3041
+ "field": "StartDate",
3042
+ "params": {
3043
+ "op": "gte",
3044
+ "other": "RequestDate"
3045
+ },
3046
+ "message": "Start Date cannot be before the Request Date.",
3047
+ "presentation": "alert"
3048
+ },
3049
+ {
3050
+ "id": "R5",
3051
+ "kind": "readonly",
3052
+ "field": "Status",
3053
+ "when": {
3054
+ "not": {
3055
+ "role": "Compliance Reviewers"
3056
+ }
3057
+ }
3058
+ },
3059
+ {
3060
+ "id": "R6",
3061
+ "kind": "length",
3062
+ "field": "TaxID",
3063
+ "params": {
3064
+ "op": "eq",
3065
+ "value": 9,
3066
+ "allow_empty": true
3067
+ },
3068
+ "message": "Tax ID must be exactly 9 digits."
3069
+ },
3070
+ {
3071
+ "id": "R7",
3072
+ "kind": "visible",
3073
+ "field": "Status",
3074
+ "modes": [
3075
+ "edit",
3076
+ "display"
3077
+ ]
3078
+ },
3079
+ {
3080
+ "id": "R8",
3081
+ "kind": "lock_item",
3082
+ "modes": [
3083
+ "edit"
3084
+ ],
3085
+ "when": {
3086
+ "all": [
3087
+ {
3088
+ "field": "Status",
3089
+ "in": [
3090
+ "Approved",
3091
+ "Rejected"
3092
+ ]
3093
+ },
3094
+ {
3095
+ "not": {
3096
+ "role": "Compliance Reviewers"
3097
+ }
3098
+ }
3099
+ ]
3100
+ },
3101
+ "message": "This request has been {Status|lower} and can no longer be edited."
3102
+ },
3103
+ {
3104
+ "id": "R9",
3105
+ "kind": "set_on_save",
3106
+ "field": "Status",
3107
+ "modes": [
3108
+ "new"
3109
+ ],
3110
+ "params": {
3111
+ "value": "Submitted"
3112
+ }
3113
+ },
3114
+ {
3115
+ "id": "R10",
3116
+ "kind": "banner",
3117
+ "modes": [
3118
+ "display"
3119
+ ],
3120
+ "when": {
3121
+ "field": "Country.title",
3122
+ "eq": "Iran"
3123
+ },
3124
+ "message": "Sanctioned country: {Country.title}"
3125
+ }
3126
+ ],
3127
+ "ctx": {
3128
+ "mode": "display",
3129
+ "values": {
3130
+ "Title": "Acme",
3131
+ "Country": {
3132
+ "id": 3,
3133
+ "title": "Germany"
3134
+ },
3135
+ "RiskRating": "Low",
3136
+ "ContractValue": "18,500",
3137
+ "RequestDate": "2026-10-01",
3138
+ "StartDate": "2026-10-05",
3139
+ "TaxID": "123456789"
3140
+ },
3141
+ "original": {},
3142
+ "user": {
3143
+ "id": 7,
3144
+ "login": "corp\\bob",
3145
+ "title": "Bob",
3146
+ "groups": [
3147
+ "Compliance Reviewers"
3148
+ ]
3149
+ }
3150
+ },
3151
+ "expected": {
3152
+ "visible": true,
3153
+ "readonly": true,
3154
+ "required": false
3155
+ }
3156
+ },
3157
+ {
3158
+ "name": "lock for requester",
3159
+ "type": "lock",
3160
+ "rules": [
3161
+ {
3162
+ "id": "R1",
3163
+ "kind": "visible",
3164
+ "field": "Justification",
3165
+ "when": {
3166
+ "field": "RiskRating",
3167
+ "eq": "High"
3168
+ }
3169
+ },
3170
+ {
3171
+ "id": "R2",
3172
+ "kind": "required",
3173
+ "field": "Justification",
3174
+ "when": {
3175
+ "field": "RiskRating",
3176
+ "eq": "High"
3177
+ },
3178
+ "message": "Justification is required for high-risk vendors.",
3179
+ "presentation": "alert"
3180
+ },
3181
+ {
3182
+ "id": "R3",
3183
+ "kind": "required",
3184
+ "field": "SecondApprover",
3185
+ "when": {
3186
+ "field": "ContractValue",
3187
+ "gt": 100000
3188
+ },
3189
+ "message": "A Second Approver is required for contracts over $100,000.",
3190
+ "presentation": "alert"
3191
+ },
3192
+ {
3193
+ "id": "R4",
3194
+ "kind": "compare",
3195
+ "field": "StartDate",
3196
+ "params": {
3197
+ "op": "gte",
3198
+ "other": "RequestDate"
3199
+ },
3200
+ "message": "Start Date cannot be before the Request Date.",
3201
+ "presentation": "alert"
3202
+ },
3203
+ {
3204
+ "id": "R5",
3205
+ "kind": "readonly",
3206
+ "field": "Status",
3207
+ "when": {
3208
+ "not": {
3209
+ "role": "Compliance Reviewers"
3210
+ }
3211
+ }
3212
+ },
3213
+ {
3214
+ "id": "R6",
3215
+ "kind": "length",
3216
+ "field": "TaxID",
3217
+ "params": {
3218
+ "op": "eq",
3219
+ "value": 9,
3220
+ "allow_empty": true
3221
+ },
3222
+ "message": "Tax ID must be exactly 9 digits."
3223
+ },
3224
+ {
3225
+ "id": "R7",
3226
+ "kind": "visible",
3227
+ "field": "Status",
3228
+ "modes": [
3229
+ "edit",
3230
+ "display"
3231
+ ]
3232
+ },
3233
+ {
3234
+ "id": "R8",
3235
+ "kind": "lock_item",
3236
+ "modes": [
3237
+ "edit"
3238
+ ],
3239
+ "when": {
3240
+ "all": [
3241
+ {
3242
+ "field": "Status",
3243
+ "in": [
3244
+ "Approved",
3245
+ "Rejected"
3246
+ ]
3247
+ },
3248
+ {
3249
+ "not": {
3250
+ "role": "Compliance Reviewers"
3251
+ }
3252
+ }
3253
+ ]
3254
+ },
3255
+ "message": "This request has been {Status|lower} and can no longer be edited."
3256
+ },
3257
+ {
3258
+ "id": "R9",
3259
+ "kind": "set_on_save",
3260
+ "field": "Status",
3261
+ "modes": [
3262
+ "new"
3263
+ ],
3264
+ "params": {
3265
+ "value": "Submitted"
3266
+ }
3267
+ },
3268
+ {
3269
+ "id": "R10",
3270
+ "kind": "banner",
3271
+ "modes": [
3272
+ "display"
3273
+ ],
3274
+ "when": {
3275
+ "field": "Country.title",
3276
+ "eq": "Iran"
3277
+ },
3278
+ "message": "Sanctioned country: {Country.title}"
3279
+ }
3280
+ ],
3281
+ "ctx": {
3282
+ "mode": "edit",
3283
+ "values": {
3284
+ "Status": "Approved"
3285
+ },
3286
+ "user": {
3287
+ "id": 6,
3288
+ "login": "corp\\alice",
3289
+ "title": "Alice",
3290
+ "groups": [
3291
+ "Procurement Members"
3292
+ ]
3293
+ }
3294
+ },
3295
+ "expected": "R8",
3296
+ "message": "This request has been approved and can no longer be edited."
3297
+ },
3298
+ {
3299
+ "name": "no lock for reviewer",
3300
+ "type": "lock",
3301
+ "rules": [
3302
+ {
3303
+ "id": "R1",
3304
+ "kind": "visible",
3305
+ "field": "Justification",
3306
+ "when": {
3307
+ "field": "RiskRating",
3308
+ "eq": "High"
3309
+ }
3310
+ },
3311
+ {
3312
+ "id": "R2",
3313
+ "kind": "required",
3314
+ "field": "Justification",
3315
+ "when": {
3316
+ "field": "RiskRating",
3317
+ "eq": "High"
3318
+ },
3319
+ "message": "Justification is required for high-risk vendors.",
3320
+ "presentation": "alert"
3321
+ },
3322
+ {
3323
+ "id": "R3",
3324
+ "kind": "required",
3325
+ "field": "SecondApprover",
3326
+ "when": {
3327
+ "field": "ContractValue",
3328
+ "gt": 100000
3329
+ },
3330
+ "message": "A Second Approver is required for contracts over $100,000.",
3331
+ "presentation": "alert"
3332
+ },
3333
+ {
3334
+ "id": "R4",
3335
+ "kind": "compare",
3336
+ "field": "StartDate",
3337
+ "params": {
3338
+ "op": "gte",
3339
+ "other": "RequestDate"
3340
+ },
3341
+ "message": "Start Date cannot be before the Request Date.",
3342
+ "presentation": "alert"
3343
+ },
3344
+ {
3345
+ "id": "R5",
3346
+ "kind": "readonly",
3347
+ "field": "Status",
3348
+ "when": {
3349
+ "not": {
3350
+ "role": "Compliance Reviewers"
3351
+ }
3352
+ }
3353
+ },
3354
+ {
3355
+ "id": "R6",
3356
+ "kind": "length",
3357
+ "field": "TaxID",
3358
+ "params": {
3359
+ "op": "eq",
3360
+ "value": 9,
3361
+ "allow_empty": true
3362
+ },
3363
+ "message": "Tax ID must be exactly 9 digits."
3364
+ },
3365
+ {
3366
+ "id": "R7",
3367
+ "kind": "visible",
3368
+ "field": "Status",
3369
+ "modes": [
3370
+ "edit",
3371
+ "display"
3372
+ ]
3373
+ },
3374
+ {
3375
+ "id": "R8",
3376
+ "kind": "lock_item",
3377
+ "modes": [
3378
+ "edit"
3379
+ ],
3380
+ "when": {
3381
+ "all": [
3382
+ {
3383
+ "field": "Status",
3384
+ "in": [
3385
+ "Approved",
3386
+ "Rejected"
3387
+ ]
3388
+ },
3389
+ {
3390
+ "not": {
3391
+ "role": "Compliance Reviewers"
3392
+ }
3393
+ }
3394
+ ]
3395
+ },
3396
+ "message": "This request has been {Status|lower} and can no longer be edited."
3397
+ },
3398
+ {
3399
+ "id": "R9",
3400
+ "kind": "set_on_save",
3401
+ "field": "Status",
3402
+ "modes": [
3403
+ "new"
3404
+ ],
3405
+ "params": {
3406
+ "value": "Submitted"
3407
+ }
3408
+ },
3409
+ {
3410
+ "id": "R10",
3411
+ "kind": "banner",
3412
+ "modes": [
3413
+ "display"
3414
+ ],
3415
+ "when": {
3416
+ "field": "Country.title",
3417
+ "eq": "Iran"
3418
+ },
3419
+ "message": "Sanctioned country: {Country.title}"
3420
+ }
3421
+ ],
3422
+ "ctx": {
3423
+ "mode": "edit",
3424
+ "values": {
3425
+ "Status": "Approved"
3426
+ },
3427
+ "user": {
3428
+ "id": 7,
3429
+ "login": "corp\\bob",
3430
+ "title": "Bob",
3431
+ "groups": [
3432
+ "Compliance Reviewers"
3433
+ ]
3434
+ }
3435
+ },
3436
+ "expected": null
3437
+ },
3438
+ {
3439
+ "name": "set on save",
3440
+ "type": "set_on_save",
3441
+ "rules": [
3442
+ {
3443
+ "id": "R1",
3444
+ "kind": "visible",
3445
+ "field": "Justification",
3446
+ "when": {
3447
+ "field": "RiskRating",
3448
+ "eq": "High"
3449
+ }
3450
+ },
3451
+ {
3452
+ "id": "R2",
3453
+ "kind": "required",
3454
+ "field": "Justification",
3455
+ "when": {
3456
+ "field": "RiskRating",
3457
+ "eq": "High"
3458
+ },
3459
+ "message": "Justification is required for high-risk vendors.",
3460
+ "presentation": "alert"
3461
+ },
3462
+ {
3463
+ "id": "R3",
3464
+ "kind": "required",
3465
+ "field": "SecondApprover",
3466
+ "when": {
3467
+ "field": "ContractValue",
3468
+ "gt": 100000
3469
+ },
3470
+ "message": "A Second Approver is required for contracts over $100,000.",
3471
+ "presentation": "alert"
3472
+ },
3473
+ {
3474
+ "id": "R4",
3475
+ "kind": "compare",
3476
+ "field": "StartDate",
3477
+ "params": {
3478
+ "op": "gte",
3479
+ "other": "RequestDate"
3480
+ },
3481
+ "message": "Start Date cannot be before the Request Date.",
3482
+ "presentation": "alert"
3483
+ },
3484
+ {
3485
+ "id": "R5",
3486
+ "kind": "readonly",
3487
+ "field": "Status",
3488
+ "when": {
3489
+ "not": {
3490
+ "role": "Compliance Reviewers"
3491
+ }
3492
+ }
3493
+ },
3494
+ {
3495
+ "id": "R6",
3496
+ "kind": "length",
3497
+ "field": "TaxID",
3498
+ "params": {
3499
+ "op": "eq",
3500
+ "value": 9,
3501
+ "allow_empty": true
3502
+ },
3503
+ "message": "Tax ID must be exactly 9 digits."
3504
+ },
3505
+ {
3506
+ "id": "R7",
3507
+ "kind": "visible",
3508
+ "field": "Status",
3509
+ "modes": [
3510
+ "edit",
3511
+ "display"
3512
+ ]
3513
+ },
3514
+ {
3515
+ "id": "R8",
3516
+ "kind": "lock_item",
3517
+ "modes": [
3518
+ "edit"
3519
+ ],
3520
+ "when": {
3521
+ "all": [
3522
+ {
3523
+ "field": "Status",
3524
+ "in": [
3525
+ "Approved",
3526
+ "Rejected"
3527
+ ]
3528
+ },
3529
+ {
3530
+ "not": {
3531
+ "role": "Compliance Reviewers"
3532
+ }
3533
+ }
3534
+ ]
3535
+ },
3536
+ "message": "This request has been {Status|lower} and can no longer be edited."
3537
+ },
3538
+ {
3539
+ "id": "R9",
3540
+ "kind": "set_on_save",
3541
+ "field": "Status",
3542
+ "modes": [
3543
+ "new"
3544
+ ],
3545
+ "params": {
3546
+ "value": "Submitted"
3547
+ }
3548
+ },
3549
+ {
3550
+ "id": "R10",
3551
+ "kind": "banner",
3552
+ "modes": [
3553
+ "display"
3554
+ ],
3555
+ "when": {
3556
+ "field": "Country.title",
3557
+ "eq": "Iran"
3558
+ },
3559
+ "message": "Sanctioned country: {Country.title}"
3560
+ }
3561
+ ],
3562
+ "ctx": {
3563
+ "mode": "new",
3564
+ "values": {},
3565
+ "user": {
3566
+ "id": 6,
3567
+ "login": "corp\\alice",
3568
+ "title": "Alice",
3569
+ "groups": [
3570
+ "Procurement Members"
3571
+ ]
3572
+ }
3573
+ },
3574
+ "expected": {
3575
+ "Status": "Submitted"
3576
+ }
3577
+ },
3578
+ {
3579
+ "name": "banner",
3580
+ "type": "banners",
3581
+ "rules": [
3582
+ {
3583
+ "id": "R1",
3584
+ "kind": "visible",
3585
+ "field": "Justification",
3586
+ "when": {
3587
+ "field": "RiskRating",
3588
+ "eq": "High"
3589
+ }
3590
+ },
3591
+ {
3592
+ "id": "R2",
3593
+ "kind": "required",
3594
+ "field": "Justification",
3595
+ "when": {
3596
+ "field": "RiskRating",
3597
+ "eq": "High"
3598
+ },
3599
+ "message": "Justification is required for high-risk vendors.",
3600
+ "presentation": "alert"
3601
+ },
3602
+ {
3603
+ "id": "R3",
3604
+ "kind": "required",
3605
+ "field": "SecondApprover",
3606
+ "when": {
3607
+ "field": "ContractValue",
3608
+ "gt": 100000
3609
+ },
3610
+ "message": "A Second Approver is required for contracts over $100,000.",
3611
+ "presentation": "alert"
3612
+ },
3613
+ {
3614
+ "id": "R4",
3615
+ "kind": "compare",
3616
+ "field": "StartDate",
3617
+ "params": {
3618
+ "op": "gte",
3619
+ "other": "RequestDate"
3620
+ },
3621
+ "message": "Start Date cannot be before the Request Date.",
3622
+ "presentation": "alert"
3623
+ },
3624
+ {
3625
+ "id": "R5",
3626
+ "kind": "readonly",
3627
+ "field": "Status",
3628
+ "when": {
3629
+ "not": {
3630
+ "role": "Compliance Reviewers"
3631
+ }
3632
+ }
3633
+ },
3634
+ {
3635
+ "id": "R6",
3636
+ "kind": "length",
3637
+ "field": "TaxID",
3638
+ "params": {
3639
+ "op": "eq",
3640
+ "value": 9,
3641
+ "allow_empty": true
3642
+ },
3643
+ "message": "Tax ID must be exactly 9 digits."
3644
+ },
3645
+ {
3646
+ "id": "R7",
3647
+ "kind": "visible",
3648
+ "field": "Status",
3649
+ "modes": [
3650
+ "edit",
3651
+ "display"
3652
+ ]
3653
+ },
3654
+ {
3655
+ "id": "R8",
3656
+ "kind": "lock_item",
3657
+ "modes": [
3658
+ "edit"
3659
+ ],
3660
+ "when": {
3661
+ "all": [
3662
+ {
3663
+ "field": "Status",
3664
+ "in": [
3665
+ "Approved",
3666
+ "Rejected"
3667
+ ]
3668
+ },
3669
+ {
3670
+ "not": {
3671
+ "role": "Compliance Reviewers"
3672
+ }
3673
+ }
3674
+ ]
3675
+ },
3676
+ "message": "This request has been {Status|lower} and can no longer be edited."
3677
+ },
3678
+ {
3679
+ "id": "R9",
3680
+ "kind": "set_on_save",
3681
+ "field": "Status",
3682
+ "modes": [
3683
+ "new"
3684
+ ],
3685
+ "params": {
3686
+ "value": "Submitted"
3687
+ }
3688
+ },
3689
+ {
3690
+ "id": "R10",
3691
+ "kind": "banner",
3692
+ "modes": [
3693
+ "display"
3694
+ ],
3695
+ "when": {
3696
+ "field": "Country.title",
3697
+ "eq": "Iran"
3698
+ },
3699
+ "message": "Sanctioned country: {Country.title}"
3700
+ }
3701
+ ],
3702
+ "ctx": {
3703
+ "mode": "display",
3704
+ "values": {
3705
+ "Country": {
3706
+ "id": 5,
3707
+ "title": "Iran"
3708
+ }
3709
+ },
3710
+ "user": {
3711
+ "id": 6,
3712
+ "login": "corp\\alice",
3713
+ "title": "Alice",
3714
+ "groups": [
3715
+ "Procurement Members"
3716
+ ]
3717
+ }
3718
+ },
3719
+ "expected": [
3720
+ "Sanctioned country: Iran"
3721
+ ]
3722
+ },
3723
+ {
3724
+ "name": "defaults",
3725
+ "type": "defaults",
3726
+ "fields": [
3727
+ {
3728
+ "name": "Requester",
3729
+ "label": "Requester",
3730
+ "type": "user",
3731
+ "default": "@me"
3732
+ },
3733
+ {
3734
+ "name": "RequestDate",
3735
+ "label": "Request Date",
3736
+ "type": "date",
3737
+ "default": "@today"
3738
+ },
3739
+ {
3740
+ "name": "Kind",
3741
+ "label": "Kind",
3742
+ "type": "choice",
3743
+ "default": "Supplier"
3744
+ }
3745
+ ],
3746
+ "rules": [],
3747
+ "user": {
3748
+ "id": 6,
3749
+ "login": "corp\\alice",
3750
+ "title": "Alice",
3751
+ "groups": [
3752
+ "Procurement Members"
3753
+ ]
3754
+ },
3755
+ "today": "2026-10-28",
3756
+ "expected": {
3757
+ "Requester": {
3758
+ "id": 6,
3759
+ "login": "corp\\alice",
3760
+ "title": "Alice"
3761
+ },
3762
+ "RequestDate": "2026-10-28",
3763
+ "Kind": "Supplier"
3764
+ }
3765
+ },
3766
+ {
3767
+ "name": "template",
3768
+ "type": "fill",
3769
+ "text": "Value {ContractValue} for {Country.title} by {Owner.title}, {Status|upper}",
3770
+ "values": {
3771
+ "ContractValue": 240000.0,
3772
+ "Country": {
3773
+ "id": 1,
3774
+ "title": "Peru"
3775
+ },
3776
+ "Status": "Draft"
3777
+ },
3778
+ "expected": "Value 240000 for Peru by , DRAFT"
3779
+ },
3780
+ {
3781
+ "name": "clear hidden",
3782
+ "type": "clear_hidden",
3783
+ "fields": [
3784
+ {
3785
+ "name": "Screened",
3786
+ "label": "S",
3787
+ "type": "boolean"
3788
+ },
3789
+ {
3790
+ "name": "Result",
3791
+ "label": "R",
3792
+ "type": "choice",
3793
+ "choices": [
3794
+ "Clear"
3795
+ ]
3796
+ }
3797
+ ],
3798
+ "rules": [
3799
+ {
3800
+ "id": "V1",
3801
+ "kind": "visible",
3802
+ "field": "Result",
3803
+ "when": {
3804
+ "field": "Screened",
3805
+ "eq": true
3806
+ },
3807
+ "params": {
3808
+ "clear": true
3809
+ }
3810
+ }
3811
+ ],
3812
+ "ctx": {
3813
+ "mode": "edit",
3814
+ "values": {
3815
+ "Screened": false,
3816
+ "Result": "Clear"
3817
+ },
3818
+ "user": {
3819
+ "groups": []
3820
+ }
3821
+ },
3822
+ "expected": {
3823
+ "Result": null
3824
+ }
3825
+ }
3826
+ ]