diff --git a/common/src/hedera-modules/vcjs/vcjs.ts b/common/src/hedera-modules/vcjs/vcjs.ts index 7f4a04946a..4f65d14a7b 100644 --- a/common/src/hedera-modules/vcjs/vcjs.ts +++ b/common/src/hedera-modules/vcjs/vcjs.ts @@ -322,8 +322,9 @@ export class VCJS { const validate = await ajv.compileAsync(schema); const valid = validate(vcObject); + const errors = this.enhanceConditionErrors(validate.errors as any[], schema); - return new SchemaValidationResult(valid, 'JSON_SCHEMA_VALIDATION_ERROR', validate.errors as any); + return new SchemaValidationResult(valid, 'JSON_SCHEMA_VALIDATION_ERROR', errors as any); } /** @@ -354,6 +355,8 @@ export class VCJS { * @param schema Schema */ private prepareSchema(schema: any) { + this.stripIfOnly(schema); + const defsObj = schema.$defs; if (!defsObj) { return; @@ -362,12 +365,121 @@ export class VCJS { const defsKeys = Object.keys(defsObj); for (const key of defsKeys) { const nestedSchema = defsObj[key]; + this.stripIfOnly(nestedSchema); const required = nestedSchema.required; if (!required || required.length === 0) { continue; } nestedSchema.required = required.filter((field: any) => !nestedSchema.properties[field] || !nestedSchema.properties[field].readOnly); } + + if (!Array.isArray(schema.allOf)) { + return; + } + const rootProperties = schema.properties || {}; + const conditionalByRef = new Map>(); + for (const condEntry of schema.allOf) { + if (!condEntry?.if) { continue; } + for (const branch of [condEntry.then, condEntry.else]) { + if (!branch?.properties) { continue; } + for (const propKey of Object.keys(branch.properties)) { + const constraint = branch.properties[propKey]; + if (!constraint || typeof constraint !== 'object') { continue; } + const ref = rootProperties[propKey]?.$ref; + if (!ref || !defsObj[ref]) { continue; } + if (!conditionalByRef.has(ref)) { conditionalByRef.set(ref, new Set()); } + const conditionalFields = conditionalByRef.get(ref)!; + if (Array.isArray(constraint.required)) { + for (const fieldName of constraint.required) { conditionalFields.add(fieldName); } + } + if (constraint.properties) { + for (const [fieldName, val] of Object.entries(constraint.properties)) { + if (val === false) { conditionalFields.add(fieldName); } + } + } + } + } + } + for (const [ref, conditionalFields] of conditionalByRef) { + const defsEntry = defsObj[ref]; + if (Array.isArray(defsEntry?.required) && conditionalFields.size) { + defsEntry.required = defsEntry.required.filter((r: string) => !conditionalFields.has(r)); + } + } + } + + private stripIfOnly(schema: any) { + if (!Array.isArray(schema?.allOf)) { + return; + } + schema.allOf = schema.allOf.filter( + (entry: any) => !entry?.if || entry.then !== undefined || entry.else !== undefined + ); + if (schema.allOf.length === 0) { + delete schema.allOf; + } + } + + /** + * Converts the nested JSON Schema `if` node into a readable condition string + */ + private describeIfCondition(node: any): string { + if (!node) { return ''; } + if (Array.isArray(node.anyOf)) { + return node.anyOf.map((b: any) => this.describeIfCondition(b)).filter(Boolean).join(' OR '); + } + if (Array.isArray(node.allOf)) { + return node.allOf.map((b: any) => this.describeIfCondition(b)).filter(Boolean).join(' AND '); + } + if (node.properties) { + return Object.entries(node.properties as Record) + .map(([key, val]) => this.describeIfConditionLeaf(val, key)) + .filter(Boolean) + .join(', '); + } + return ''; + } + + private describeIfConditionLeaf(node: any, leafKey: string): string { + if (!node) { return ''; } + if (node.const !== undefined) { + return `${leafKey} = '${node.const}'`; + } + if (node.properties) { + return Object.entries(node.properties as Record) + .map(([key, val]) => this.describeIfConditionLeaf(val, key)) + .filter(Boolean) + .join(', '); + } + if (Array.isArray(node.anyOf)) { + return node.anyOf.map((b: any) => this.describeIfConditionLeaf(b, leafKey)).filter(Boolean).join(' OR '); + } + if (Array.isArray(node.allOf)) { + return node.allOf.map((b: any) => this.describeIfConditionLeaf(b, leafKey)).filter(Boolean).join(' AND '); + } + return ''; + } + + /** + * Replaces AJV messages with a human-readable description + */ + private enhanceConditionErrors(errors: any[] | null | undefined, schema: any): any[] | null | undefined { + if (!errors?.length || !Array.isArray(schema?.allOf)) { + return errors; + } + return errors.map(error => { + if (error.keyword !== 'false schema') { return error; } + const match = (error.schemaPath as string)?.match(/^#\/allOf\/(\d+)\/(then|else)\//); + if (!match) { return error; } + const condEntry = schema.allOf[parseInt(match[1], 10)]; + if (!condEntry?.if) { return error; } + const fieldName = (error.instancePath as string).split('/').filter(Boolean).pop() || 'field'; + const condition = this.describeIfCondition(condEntry.if) || 'condition not met'; + return { + ...error, + message: `Field '${fieldName}' is not allowed unless: ${condition}`, + }; + }); } /** @@ -396,10 +508,10 @@ export class VCJS { this.prepareSchema(schema); const validate = await ajv.compileAsync(schema); - const valid = validate(subject); + const errors = this.enhanceConditionErrors(validate.errors as any[], schema); - return new SchemaValidationResult(valid, 'JSON_SCHEMA_VALIDATION_ERROR', validate.errors as any); + return new SchemaValidationResult(valid, 'JSON_SCHEMA_VALIDATION_ERROR', errors as any); } /** diff --git a/common/src/xlsx/json-to-xlsx.ts b/common/src/xlsx/json-to-xlsx.ts index 0c7b97e60c..ff1b78a8e3 100644 --- a/common/src/xlsx/json-to-xlsx.ts +++ b/common/src/xlsx/json-to-xlsx.ts @@ -276,7 +276,9 @@ export class JsonToXlsx { schemaCache, enumsCache, row, - subSchemaNames + subSchemaNames, + fieldCache, + [field.name] ); } @@ -446,32 +448,20 @@ export class JsonToXlsx { fieldCache: Map, ) { const baseFormula = JsonToXlsx.buildIfFormula(condition.ifCondition, fieldCache); - const thenFormula = baseFormula; const elseFormula = `NOT(${baseFormula})`; - if (Array.isArray(condition.thenFields)) { - for (const field of condition.thenFields) { - const thenField = fieldCache.get(field.name); - if (!thenField) { - continue; - } - worksheet - .getCell(table.getCol(Dictionary.VISIBILITY), thenField.row) - .setFormulae(thenFormula); + const writeCell = (key: string, formula: string) => { + const rowField = fieldCache.get(key); + if (rowField) { + worksheet.getCell(table.getCol(Dictionary.VISIBILITY), rowField.row).setFormulae(formula); } - } - if (Array.isArray(condition.elseFields)) { - for (const field of condition.elseFields) { - const elseField = fieldCache.get(field.name); - if (!elseField) { - continue; - } - worksheet - .getCell(table.getCol(Dictionary.VISIBILITY), elseField.row) - .setFormulae(elseFormula); - } - } + }; + + for (const field of condition.thenFields || []) { writeCell(field.name, thenFormula); } + for (const field of condition.elseFields || []) { writeCell(field.name, elseFormula); } + for (const t of condition.thenTargets || []) { writeCell(t.fieldPath.join('.'), thenFormula); } + for (const t of condition.elseTargets || []) { writeCell(t.fieldPath.join('.'), elseFormula); } } public static writeSubFields( @@ -481,7 +471,9 @@ export class JsonToXlsx { schemaCache: Map, enumsCache: Map, row: number, - subSchemaNames: Map = new Map() + subSchemaNames: Map = new Map(), + rootFieldCache?: Map, + pathPrefix?: string[] ): number { if (!parent || !parent.isRef || !Array.isArray(parent.fields) || parent.fields.length === 0) { return row; @@ -512,6 +504,13 @@ export class JsonToXlsx { subSchemaNames, parent ); + if (rootFieldCache && pathPrefix) { + const dotPath = [...pathPrefix, field.name].join('.'); + const rowField = fieldCache.get(field.name); + if (rowField) { + rootFieldCache.set(dotPath, rowField); + } + } worksheet .getRow(row) .setOutline(lvl); @@ -522,16 +521,29 @@ export class JsonToXlsx { schemaCache, enumsCache, row, - subSchemaNames + subSchemaNames, + rootFieldCache, + pathPrefix ? [...pathPrefix, field.name] : undefined ); } + // Extend local cache with relative dot-paths from rootFieldCache so nested fieldPath refs resolve. + let condCache = fieldCache; + if (rootFieldCache && pathPrefix) { + const prefix = pathPrefix.join('.') + '.'; + condCache = new Map(fieldCache); + for (const [k, v] of rootFieldCache) { + if (k.startsWith(prefix)) { + condCache.set(k.slice(prefix.length), v); + } + } + } for (const condition of parent.conditions) { JsonToXlsx.writeCondition( worksheet, table, condition, - fieldCache + condCache ); } @@ -620,7 +632,10 @@ export class JsonToXlsx { fieldCache: Map ): string { const toExact = (sub: any): string => { - const f = fieldCache.get(sub.field.name); + const key = (sub.fieldPath?.length > 1) + ? (sub.fieldPath as string[]).join('.') + : sub.field.name; + const f = fieldCache.get(key) ?? fieldCache.get(sub.field.name); if (!f) { throw new Error(`Condition refers to unknown field "${sub.field?.name}".`); } diff --git a/common/src/xlsx/models/schema-condition.ts b/common/src/xlsx/models/schema-condition.ts index ca4a946bd9..dfc2b784a0 100644 --- a/common/src/xlsx/models/schema-condition.ts +++ b/common/src/xlsx/models/schema-condition.ts @@ -1,38 +1,48 @@ import { SchemaCondition, SchemaField } from '@guardian/interfaces'; -type SingleIf = { field: SchemaField; fieldValue: any }; +type SingleIf = { field: SchemaField; fieldValue: any; fieldPath?: string[] }; type GroupIf = { OR: SingleIf[] } | { AND: SingleIf[] }; +function normalizePath(fieldPath?: string[]): string[] | undefined { + return Array.isArray(fieldPath) && fieldPath.length > 1 ? fieldPath : undefined; +} + function sameValue(a: any, b: any): boolean { return JSON.stringify(a) === JSON.stringify(b); } export class XlsxSchemaConditions { - private readonly single?: { field: SchemaField; value: any }; - private readonly group?: { op: 'OR' | 'AND'; items: { field: SchemaField; value: any }[] }; + private readonly single?: { field: SchemaField; value: any; fieldPath?: string[] }; + private readonly group?: { op: 'OR' | 'AND'; items: { field: SchemaField; value: any; fieldPath?: string[] }[] }; public readonly condition: SchemaCondition; - constructor(field: SchemaField, value: any); - constructor(group: { op: 'OR' | 'AND'; items: { field: SchemaField; value: any }[] }); - constructor(arg1: any, value?: any) { + constructor(field: SchemaField, value: any, fieldPath?: string[]); + constructor(group: { op: 'OR' | 'AND'; items: { field: SchemaField; value: any; fieldPath?: string[] }[] }); + constructor(arg1: any, value?: any, fieldPath?: string[]) { if (typeof arg1 === 'object' && value === undefined && arg1?.op && Array.isArray(arg1.items)) { this.group = { op: arg1.op, items: arg1.items }; + const toPredicate = (i: any) => ({ + field: i.field, + fieldValue: i.value, + fieldPath: normalizePath(i.fieldPath) + }); const payload: GroupIf = arg1.op === 'OR' - ? { OR: arg1.items.map((i: any) => ({ field: i.field, fieldValue: i.value })) } - : { AND: arg1.items.map((i: any) => ({ field: i.field, fieldValue: i.value })) }; + ? { OR: arg1.items.map(toPredicate) } + : { AND: arg1.items.map(toPredicate) }; this.condition = { ifCondition: payload as any, thenFields: [], elseFields: [] }; } else { - this.single = { field: arg1 as SchemaField, value }; + this.single = { field: arg1 as SchemaField, value, fieldPath: normalizePath(fieldPath) }; this.condition = { ifCondition: { field: arg1 as SchemaField, - fieldValue: value + fieldValue: value, + fieldPath: normalizePath(fieldPath) } as any, thenFields: [], elseFields: [] @@ -44,7 +54,7 @@ export class XlsxSchemaConditions { return this.condition; } - public equal(otherFieldOrGroup: any, otherValue?: any): boolean { + public equal(otherFieldOrGroup: any, otherValue?: any, otherFieldPath?: string[]): boolean { if (this.group) { if (!(otherFieldOrGroup?.op && Array.isArray(otherFieldOrGroup.items))) { return false @@ -55,16 +65,18 @@ export class XlsxSchemaConditions { if (this.group.items.length !== otherFieldOrGroup.items.length) { return false }; - const norm = (arr: any[]) => + const toKey = (arr: any[]) => arr - .map(i => `${i.field?.name}::${JSON.stringify(i.value)}`) + .map(i => `${i.field?.name}::${JSON.stringify(i.value)}::${JSON.stringify(normalizePath(i.fieldPath))}`) .sort() .join('|'); - return norm(this.group.items) === norm(otherFieldOrGroup.items); + return toKey(this.group.items) === toKey(otherFieldOrGroup.items); } else { const of = otherFieldOrGroup as SchemaField; - return of?.name === this.single.field.name && sameValue(otherValue, this.single.value); + const pathA = JSON.stringify(normalizePath(this.single.fieldPath)); + const pathB = JSON.stringify(normalizePath(otherFieldPath)); + return of?.name === this.single.field.name && sameValue(otherValue, this.single.value) && pathA === pathB; } } @@ -75,4 +87,15 @@ export class XlsxSchemaConditions { this.condition.thenFields.push(field); } } + + public addTarget(field: SchemaField, targetFieldPath: string[], invert: boolean) { + const target = { field, fieldPath: targetFieldPath }; + if (invert) { + if (!this.condition.elseTargets) { this.condition.elseTargets = []; } + this.condition.elseTargets.push(target); + } else { + if (!this.condition.thenTargets) { this.condition.thenTargets = []; } + this.condition.thenTargets.push(target); + } + } } diff --git a/common/src/xlsx/xlsx-to-json.ts b/common/src/xlsx/xlsx-to-json.ts index 7c2c780ecc..05bc28d703 100644 --- a/common/src/xlsx/xlsx-to-json.ts +++ b/common/src/xlsx/xlsx-to-json.ts @@ -407,6 +407,7 @@ export class XlsxToJson { row = table.end.r + 1; const fields: SchemaField[] = []; const allFields = new Map(); + const fieldPaths = new Map(); let parents: SchemaField[] = []; for (; row < range.e.r; row++) { @@ -421,6 +422,7 @@ export class XlsxToJson { allFields.set(field.title, field); parents = parents.slice(0, groupIndex); parents[groupIndex] = field; + fieldPaths.set(field.title, parents.slice(0, groupIndex + 1).map(p => p.name)); if (groupIndex === 0) { XlsxToJson.addFieldByName(worksheet, table, row, xlsxResult, fields, field); } else { @@ -475,6 +477,8 @@ export class XlsxToJson { worksheet, table, fields, + allFields, + fieldPaths, conditionCache, row, xlsxResult @@ -483,7 +487,6 @@ export class XlsxToJson { conditionCache.push(condition); } } - row = table.end.r + 1; const expressions: XlsxExpressions = new XlsxExpressions(); for (; row < range.e.r; row++) { @@ -865,6 +868,8 @@ export class XlsxToJson { worksheet: Worksheet, table: Table, fields: SchemaField[], + allFields: Map, + fieldPaths: Map, conditionCache: XlsxSchemaConditions[], row: number, xlsxResult: XlsxResult @@ -872,15 +877,13 @@ export class XlsxToJson { if (worksheet.empty(table.start.c, table.end.c, row)) { return null; } - if (worksheet.getRow(row).getOutline()) { - return null; - } const key = XlsxToJson.getFieldKey(worksheet, table, row, xlsxResult); - const field = fields.find((f) => f.title === key.path); + const field = allFields.get(key.path) || fields.find((f) => f.title === key.path); + const targetPath = fieldPaths.get(key.path); + const isNested = targetPath && targetPath.length > 1; try { - //visibility if (worksheet.outColumnRange(table.getCol(Dictionary.VISIBILITY))) { return; } @@ -916,40 +919,49 @@ export class XlsxToJson { } if (result.type === 'const') { - field.hidden = field.hidden || !result.value; + if (field) { field.hidden = field.hidden || !result.value; } return; } + const addToCondition = (holder: XlsxSchemaConditions, invert: boolean) => { + if (isNested && field) { + holder.addTarget(field, targetPath, invert); + } else if (field) { + holder.addField(field, invert); + } + }; + if (result.op && Array.isArray(result.items)) { const resolved = result.items.map(it => { - const target = fields.find(f => f.title === it.fieldPath); - if (!target) { + const trigger = allFields.get(it.fieldPath) || fields.find(f => f.title === it.fieldPath); + if (!trigger) { throw new Error(`Invalid target in ${result.op} condition: ${it.fieldPath}`); } - return { field: target, value: it.compareValue }; + return { field: trigger, value: it.compareValue, fieldPath: fieldPaths.get(it.fieldPath) }; }); const conditionKey = { op: result.op, items: resolved }; const existed = conditionCache.find(c => (c as any).equal(conditionKey)); const holder = existed || new XlsxSchemaConditions(conditionKey as any); - holder.addField(field, !!result.invert); + addToCondition(holder, !!result.invert); if (!existed) { return holder; } return null; } else { - const target = fields.find((f) => f.title === result.fieldPath); - if (!target) { - throw new Error('Invalid target'); + const trigger = allFields.get(result.fieldPath) || fields.find((f) => f.title === result.fieldPath); + if (!trigger) { + throw new Error('Invalid trigger field'); } - const condition = conditionCache.find(c => c.equal(target, result.compareValue)); + const triggerPath = fieldPaths.get(result.fieldPath); + const condition = conditionCache.find(c => c.equal(trigger, result.compareValue, triggerPath)); if (condition) { - condition.addField(field, result.invert); + addToCondition(condition, result.invert); return null; } else { - const newCondition = new XlsxSchemaConditions(target, result.compareValue); - newCondition.addField(field, result.invert); + const newCondition = new XlsxSchemaConditions(trigger, result.compareValue, triggerPath); + addToCondition(newCondition, result.invert); return newCondition; } } @@ -1091,6 +1103,15 @@ export class XlsxToJson { } } + if ((node as any).type === 'AssignmentNode') { + const assign = node as any; + const obj = assign.object; + const val = assign.value; + if (obj?.type === 'SymbolNode' && val?.type === 'ConstantNode') { + return { type: 'formulae', fieldPath: obj.name, compareValue: val.value, invert }; + } + } + throw new Error(`Failed to parse formulae: ${formulae}.`); }; diff --git a/frontend/src/app/modules/schema-engine/condition-control.ts b/frontend/src/app/modules/schema-engine/condition-control.ts index e7dbcf6f6c..7f901fd262 100644 --- a/frontend/src/app/modules/schema-engine/condition-control.ts +++ b/frontend/src/app/modules/schema-engine/condition-control.ts @@ -1,4 +1,3 @@ -// condition-control.ts import { UntypedFormArray, UntypedFormControl, @@ -12,6 +11,21 @@ import { FieldControl } from './field-control'; export type IfOperator = 'SINGLE' | 'AND' | 'OR'; +export interface ConditionFieldOption { + key: string; + label: string; + shortLabel?: string; + fieldPath: string[]; + typeKey: string; + required: boolean; + fieldControl?: FieldControl; +} + +export interface ConditionFieldGroup { + label: string; + items: ConditionFieldOption[]; +} + export class ConditionControl { public readonly name: string; @@ -20,6 +34,11 @@ export class ConditionControl { public readonly thenFieldControls: UntypedFormGroup; public readonly elseFieldControls: UntypedFormGroup; + public crossThenTargets: ConditionFieldOption[] = []; + public crossElseTargets: ConditionFieldOption[] = []; + public readonly crossThenCount: UntypedFormControl; + public readonly crossElseCount: UntypedFormControl; + public readonly conditions: UntypedFormArray; public readonly operator: UntypedFormControl; @@ -27,13 +46,15 @@ export class ConditionControl { public changeEvents: any[] | null = null; public fieldChange: Subscription | null = null; - constructor(field?: FieldControl, fieldValue: string = '', operator: IfOperator = 'SINGLE') { + constructor(field?: ConditionFieldOption, fieldValue: string = '', operator: IfOperator = 'SINGLE') { this.name = `condition${Date.now()}${Math.floor(Math.random() * 1000000)}`; this.thenControls = []; this.elseControls = []; this.thenFieldControls = new UntypedFormGroup({}); this.elseFieldControls = new UntypedFormGroup({}); + this.crossThenCount = new UntypedFormControl(0); + this.crossElseCount = new UntypedFormControl(0); this.operator = new UntypedFormControl(operator, Validators.required); this.conditions = new UntypedFormArray([], this.dynamicConditionsValidator()); @@ -45,7 +66,35 @@ export class ConditionControl { } } - public get fieldControl(): FieldControl | undefined { + public addCrossThenTarget(option: ConditionFieldOption): void { + const key = option.fieldPath.join('.'); + if (!this.crossThenTargets.find(t => t.fieldPath.join('.') === key)) { + this.crossThenTargets = [...this.crossThenTargets, option]; + this.crossThenCount.setValue(this.crossThenTargets.length); + } + } + + public removeCrossThenTarget(option: ConditionFieldOption): void { + const key = option.fieldPath.join('.'); + this.crossThenTargets = this.crossThenTargets.filter(t => t.fieldPath.join('.') !== key); + this.crossThenCount.setValue(this.crossThenTargets.length); + } + + public addCrossElseTarget(option: ConditionFieldOption): void { + const key = option.fieldPath.join('.'); + if (!this.crossElseTargets.find(t => t.fieldPath.join('.') === key)) { + this.crossElseTargets = [...this.crossElseTargets, option]; + this.crossElseCount.setValue(this.crossElseTargets.length); + } + } + + public removeCrossElseTarget(option: ConditionFieldOption): void { + const key = option.fieldPath.join('.'); + this.crossElseTargets = this.crossElseTargets.filter(t => t.fieldPath.join('.') !== key); + this.crossElseCount.setValue(this.crossElseTargets.length); + } + + public get fieldControl(): ConditionFieldOption | undefined { const g = this.conditions.at(0) as UntypedFormGroup; return g ? (g.get('field') as UntypedFormControl)?.value : undefined; } @@ -65,7 +114,9 @@ export class ConditionControl { conditions: this.conditions }), thenFieldControls: this.thenFieldControls, - elseFieldControls: this.elseFieldControls + elseFieldControls: this.elseFieldControls, + crossThenCount: this.crossThenCount, + crossElseCount: this.crossElseCount, }, this.countThenElseFieldsValidator()); } @@ -92,10 +143,11 @@ export class ConditionControl { type === 'then' ? this.removeThenControl(control) : this.removeElseControl(control); } - public addCondition(field?: FieldControl, fieldValue: string = '') { + public addCondition(option?: ConditionFieldOption, fieldValue: string = '') { const group = new UntypedFormGroup({ - field: new UntypedFormControl(field, Validators.required), - fieldValue: new UntypedFormControl(fieldValue, Validators.required) + field: new UntypedFormControl(option, Validators.required), + fieldValue: new UntypedFormControl(fieldValue, Validators.required), + fieldPath: new UntypedFormControl(option?.fieldPath ?? null), }); this.conditions.push(group); this.conditions.updateValueAndValidity(); @@ -122,16 +174,25 @@ export class ConditionControl { public normalizeByOperator() { const op = this.operator.value as IfOperator; if (op === 'SINGLE' && this.conditions.length > 1) { - while (this.conditions.length > 1) this.conditions.removeAt(this.conditions.length - 1); + while (this.conditions.length > 1) { + this.conditions.removeAt(this.conditions.length - 1); + } } this.conditions.updateValueAndValidity(); } private countThenElseFieldsValidator(): ValidatorFn { return (group: any): ValidationErrors | null => { - const thenFieldControls = group.controls['thenFieldControls'] as UntypedFormGroup; - const elseFieldControls = group.controls['elseFieldControls'] as UntypedFormGroup; - if (Object.keys(thenFieldControls.controls).length > 0 || Object.keys(elseFieldControls.controls).length > 0) { + const thenFieldControls = group.controls.thenFieldControls as UntypedFormGroup; + const elseFieldControls = group.controls.elseFieldControls as UntypedFormGroup; + const crossThen: number = (group.controls.crossThenCount as UntypedFormControl)?.value || 0; + const crossElse: number = (group.controls.crossElseCount as UntypedFormControl)?.value || 0; + if ( + Object.keys(thenFieldControls.controls).length > 0 || + Object.keys(elseFieldControls.controls).length > 0 || + crossThen > 0 || + crossElse > 0 + ) { return null; } return { noConditionFields: { valid: false } }; diff --git a/frontend/src/app/modules/schema-engine/schema-configuration/schema-configuration.component.html b/frontend/src/app/modules/schema-engine/schema-configuration/schema-configuration.component.html index f3a9692376..d8838539d2 100644 --- a/frontend/src/app/modules/schema-engine/schema-configuration/schema-configuration.component.html +++ b/frontend/src/app/modules/schema-engine/schema-configuration/schema-configuration.component.html @@ -218,19 +218,28 @@ + + {{ group.label }} + @if (getRowField(row)) {
=
} @switch (true) { - @case (isFieldType1(getRowField(row))) { + @case (isFieldType1(getRowOption(row))) {
} - @case (isFieldType2(getRowField(row))) { + @case (isFieldType2(getRowOption(row))) {
} - @case (isFieldType3(getRowField(row))) { + @case (isFieldType3(getRowOption(row))) {
@@ -271,7 +280,7 @@ appendTo="body">
} - @case (isFieldType4(getRowField(row))) { + @case (isFieldType4(getRowOption(row))) {
@@ -282,7 +291,7 @@ appendTo="body">
} - @case (isFieldType5(getRowField(row))) { + @case (isFieldType5(getRowOption(row))) {
@@ -375,6 +384,41 @@
Add THEN Field
+ @if (condition.crossThenTargets.length) { +
+ Sub-schema THEN: + @for (target of condition.crossThenTargets; track target.fieldPath.join('>')) { + + {{ target.label }} + + + } +
+ } +
+ + + {{ group.label }} + + + {{ option.shortLabel || option.label }} + + +

ELSE

@if (condition.elseControls) {
Add ELSE Field
+ @if (condition.crossElseTargets.length) { +
+ Sub-schema ELSE: + @for (target of condition.crossElseTargets; track target.fieldPath.join('>')) { + + {{ target.label }} + + + } +
+ } +
+ + + {{ group.label }} + + + {{ option.shortLabel || option.label }} + + +