Skip to content

Commit

Permalink
feat(cb2-13176): VTM - Add guidance for handling documents issued cen…
Browse files Browse the repository at this point in the history
…trally (#1519)

* feat(cb2-13164): add issue documents centrally

* feat(cb2-13164): issue documents centrally for all relevant templates

* feat(cb2-13164): fix linting

* feat(cb2-13164): change name from issueDocumentsCentrally to issueRequired to match BE

* feat(cb2-13164): fix linting again

* feat(cb2-13164): hide for group 1

* feat(cb2-13164): fix linting again again

* feat(cb2-13164): set issueRequired to false for fail/abandon

* feat(cb2-13176): conditionally show hint text on ADR tests only

* feat(cb2-13176): fix linting

* feat(cb2-13176): remove issue docs centrally from groups 2 3 4 and add to 15 and 16

* feat(cb2-13176): remove issue required from  group 3

* feat(cb2-13176): remove validation for certificate number on groups 3 and 4

* feat(cb2-13176): fix linting

* feat(cb2-13176): use nested shape

* feat(cb2-13176): fix linting

* feat(cb2-13176): ensure reasons for issue is always an array of strings
  • Loading branch information
pbardy2000 authored Jul 26, 2024
1 parent c54e0fa commit e5d5615
Show file tree
Hide file tree
Showing 17 changed files with 664 additions and 410 deletions.
1 change: 1 addition & 0 deletions src/app/forms/models/async-validators.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export enum AsyncValidatorNames {
HideIfEqualsWithCondition = 'hideIfEqualsWithCondition',
PassResultDependantOnCustomDefects = 'passResultDependantOnCustomDefects',
RequiredWhenCarryingDangerousGoods = 'requiredWhenCarryingDangerousGoods',
Custom = 'custom',
}
1 change: 1 addition & 0 deletions src/app/forms/models/validators.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ export enum ValidatorNames {
Tc3TestValidator = 'tc3TestValidator',
DateIsInvalid = 'dateIsInvalid',
MinArrayLengthIfNotEmpty = 'minArrayLengthIfNotEmpty',
IssueRequired = 'issueRequired',
}
44 changes: 24 additions & 20 deletions src/app/forms/services/dynamic-form.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import {
AsyncValidatorFn, FormArray, FormControl, FormGroup,
ValidatorFn, Validators,
AsyncValidatorFn, FormArray, FormControl, FormGroup, ValidatorFn, Validators,
} from '@angular/forms';
import { GlobalError } from '@core/components/global-error/global-error.interface';
import { AsyncValidatorNames } from '@forms/models/async-validators.enum';
Expand All @@ -25,7 +24,7 @@ type CustomFormFields = CustomFormControl | CustomFormArray | CustomFormGroup;
providedIn: 'root',
})
export class DynamicFormService {
constructor(private store: Store<State>) { }
constructor(private store: Store<State>) {}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
validatorMap: Record<ValidatorNames, (args: any) => ValidatorFn> = {
Expand Down Expand Up @@ -55,7 +54,7 @@ export class DynamicFormService {
[ValidatorNames.PastDate]: () => CustomValidators.pastDate,
[ValidatorNames.Pattern]: (args: string) => Validators.pattern(args),
[ValidatorNames.Required]: () => Validators.required,
[ValidatorNames.RequiredIfEquals]: (args: { sibling: string; value: unknown[], customErrorMessage?: string }) =>
[ValidatorNames.RequiredIfEquals]: (args: { sibling: string; value: unknown[]; customErrorMessage?: string }) =>
CustomValidators.requiredIfEquals(args.sibling, args.value, args.customErrorMessage),
[ValidatorNames.requiredIfAllEquals]: (args: { sibling: string; value: unknown[] }) =>
CustomValidators.requiredIfAllEquals(args.sibling, args.value),
Expand All @@ -70,26 +69,27 @@ export class DynamicFormService {
[ValidatorNames.IsMemberOfEnum]: (args: { enum: Record<string, string>; options?: Partial<EnumValidatorOptions> }) =>
CustomValidators.isMemberOfEnum(args.enum, args.options),
[ValidatorNames.UpdateFunctionCode]: () => CustomValidators.updateFunctionCode(),
[ValidatorNames.ShowGroupsWhenEqualTo]: (args: { values: unknown[], groups: string[] }) =>
[ValidatorNames.ShowGroupsWhenEqualTo]: (args: { values: unknown[]; groups: string[] }) =>
CustomValidators.showGroupsWhenEqualTo(args.values, args.groups),
[ValidatorNames.HideGroupsWhenEqualTo]: (args: { values: unknown[], groups: string[] }) =>
[ValidatorNames.HideGroupsWhenEqualTo]: (args: { values: unknown[]; groups: string[] }) =>
CustomValidators.hideGroupsWhenEqualTo(args.values, args.groups),
[ValidatorNames.ShowGroupsWhenIncludes]: (args: { values: unknown[], groups: string[] }) =>
[ValidatorNames.ShowGroupsWhenIncludes]: (args: { values: unknown[]; groups: string[] }) =>
CustomValidators.showGroupsWhenIncludes(args.values, args.groups),
[ValidatorNames.HideGroupsWhenIncludes]: (args: { values: unknown[], groups: string[] }) =>
[ValidatorNames.HideGroupsWhenIncludes]: (args: { values: unknown[]; groups: string[] }) =>
CustomValidators.hideGroupsWhenIncludes(args.values, args.groups),
[ValidatorNames.ShowGroupsWhenExcludes]: (args: { values: unknown[], groups: string[] }) =>
[ValidatorNames.ShowGroupsWhenExcludes]: (args: { values: unknown[]; groups: string[] }) =>
CustomValidators.showGroupsWhenExcludes(args.values, args.groups),
[ValidatorNames.HideGroupsWhenExcludes]: (args: { values: unknown[], groups: string[] }) =>
[ValidatorNames.HideGroupsWhenExcludes]: (args: { values: unknown[]; groups: string[] }) =>
CustomValidators.hideGroupsWhenExcludes(args.values, args.groups),
[ValidatorNames.AddWarningForAdrField]: (warning: string) => CustomValidators.addWarningForAdrField(warning),
[ValidatorNames.IsArray]: (args: Partial<IsArrayValidatorOptions>) => CustomValidators.isArray(args),
[ValidatorNames.Custom]: (...args) => CustomValidators.custom(...args),
[ValidatorNames.Tc3TestValidator]: (args: { inspectionNumber: number }) => CustomValidators.tc3TestValidator(args),
[ValidatorNames.RequiredIfNotHidden]: () => CustomValidators.requiredIfNotHidden(),
[ValidatorNames.DateIsInvalid]: () => CustomValidators.dateIsInvalid,
[ValidatorNames.MinArrayLengthIfNotEmpty]: (args: { minimumLength: number, message: string }) =>
[ValidatorNames.MinArrayLengthIfNotEmpty]: (args: { minimumLength: number; message: string }) =>
CustomValidators.minArrayLengthIfNotEmpty(args.minimumLength, args.message),
[ValidatorNames.IssueRequired]: () => CustomValidators.issueRequired(),
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -102,13 +102,16 @@ export class DynamicFormService {
[AsyncValidatorNames.RequiredIfNotResult]: (args: { testResult: resultOfTestEnum | resultOfTestEnum[] }) =>
CustomAsyncValidators.requiredIfNotResult(this.store, args.testResult),
[AsyncValidatorNames.RequiredIfNotResultAndSiblingEquals]: (args: {
testResult: resultOfTestEnum | resultOfTestEnum[]; sibling: string; value: unknown
testResult: resultOfTestEnum | resultOfTestEnum[];
sibling: string;
value: unknown;
}) => CustomAsyncValidators.requiredIfNotResultAndSiblingEquals(this.store, args.testResult, args.sibling, args.value),
[AsyncValidatorNames.ResultDependantOnCustomDefects]: () => CustomAsyncValidators.resultDependantOnCustomDefects(this.store),
[AsyncValidatorNames.ResultDependantOnRequiredStandards]: () => CustomAsyncValidators.resultDependantOnRequiredStandards(this.store),
[AsyncValidatorNames.UpdateTesterDetails]: () => CustomAsyncValidators.updateTesterDetails(this.store),
[AsyncValidatorNames.UpdateTestStationDetails]: () => CustomAsyncValidators.updateTestStationDetails(this.store),
[AsyncValidatorNames.RequiredWhenCarryingDangerousGoods]: () => CustomAsyncValidators.requiredWhenCarryingDangerousGoods(this.store),
[AsyncValidatorNames.Custom]: (...args) => CustomAsyncValidators.custom(this.store, ...args),
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -221,19 +224,20 @@ export class DynamicFormService {
Object.entries(errors).forEach(([error, data]) => {
// If an anchor link is provided, use that, otherwise determine target element from customId or name
const defaultAnchorLink = meta?.customId ?? meta?.name;
const anchorLink = typeof data === 'object' && data !== null
? data.anchorLink ?? defaultAnchorLink
: defaultAnchorLink;
const anchorLink = typeof data === 'object' && data !== null ? data.anchorLink ?? defaultAnchorLink : defaultAnchorLink;

// If typeof data is an array, assume we're passing the service multiple global errors
const globalErrors = Array.isArray(data) ? data : [{
error: meta?.customErrorMessage ?? ErrorMessageMap[`${error}`](data, meta?.customValidatorErrorName ?? meta?.label),
anchorLink,
}];
const globalErrors = Array.isArray(data)
? data
: [
{
error: meta?.customErrorMessage ?? ErrorMessageMap[`${error}`](data, meta?.customValidatorErrorName ?? meta?.label),
anchorLink,
},
];

validationErrorList.push(...globalErrors);
});
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AdditionalDefectsSection } from './section-templates/additionalDefects/
import { CustomDefectsSection } from './section-templates/customDefects/custom-defects-section.template';
import { DeskBasedEmissionsSection } from './section-templates/emissions/desk-based-emissions-section.template';
import { EmissionsSection } from './section-templates/emissions/emissions-section.template';
import { AdrNotesSection } from './section-templates/notes/adr-notes-section.template';
import { NotesSection } from './section-templates/notes/notes-section.template';
import { reasonForCreationHiddenSection, reasonForCreationSection } from './section-templates/reasonForCreation/reasonForCreation.template';
import { CreateRequiredSectionHgvTrl } from './section-templates/required/contingency-required-hidden-section-hgv-trl.template';
Expand Down Expand Up @@ -34,6 +35,8 @@ import {
ContingencyTestSectionSpecialistGroup3And4,
} from './section-templates/test/contingency/contingency-test-section-specialist-group3And4.template';
import { ContingencyTestSectionSpecialistGroup5 } from './section-templates/test/contingency/contingency-test-section-specialist-group5.template';
import { OldIVAContingencyTestSectionSpecialistGroup1 } from './section-templates/test/contingency/old-contingency-specialist-group1.template';
import { OldIVAContingencyTestSectionSpecialistGroup5 } from './section-templates/test/contingency/old-contingency-specialist-group5.template';
import { DeskBasedTestSectionGroup1Psv } from './section-templates/test/desk-based/desk-based-test-section-group1-PSV.template';
import {
DeskBasedTestSectionGroup1And4HgvTrl as DeskBasedTestSectionGroup1And4And5HgvTrl,
Expand All @@ -58,8 +61,6 @@ import { DeskBasedVehicleSectionGroup5Lgv } from './section-templates/vehicle/de
import { VehicleSectionGroup3 } from './section-templates/vehicle/group-3-light-vehicle-section.template';
import { ContingencyVisitSection } from './section-templates/visit/contingency-visit-section.template';
import { VisitSection } from './section-templates/visit/visit-section.template';
import { OldIVAContingencyTestSectionSpecialistGroup1 } from './section-templates/test/contingency/old-contingency-specialist-group1.template';
import { OldIVAContingencyTestSectionSpecialistGroup5 } from './section-templates/test/contingency/old-contingency-specialist-group5.template';

const groups1and2Template: Record<string, FormNode> = {
required: CreateRequiredSection,
Expand Down Expand Up @@ -280,7 +281,7 @@ export const contingencyTestTemplates: Record<VehicleTypes, Partial<Record<keyof
vehicle: ContingencyVehicleSectionDefaultPsvHgvLight,
test: ContingencyTestSectionGroup7,
visit: ContingencyVisitSection,
notes: NotesSection,
notes: AdrNotesSection,
customDefects: CustomDefectsSection,
defects: defectsHiddenSection,
reasonForCreation: reasonForCreationSection,
Expand Down Expand Up @@ -454,7 +455,7 @@ export const contingencyTestTemplates: Record<VehicleTypes, Partial<Record<keyof
vehicle: ContingencyVehicleSectionDefaultTrl,
test: ContingencyTestSectionGroup7,
visit: ContingencyVisitSection,
notes: NotesSection,
notes: AdrNotesSection,
customDefects: CustomDefectsSection,
defects: defectsHiddenSection,
reasonForCreation: reasonForCreationSection,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { AsyncValidatorNames } from '@forms/models/async-validators.enum';
import { ValidatorNames } from '@forms/models/validators.enum';
import {
CustomFormControl, FormNode, FormNodeEditTypes, FormNodeTypes,
} from '@forms/services/dynamic-form.types';
import { Store, select } from '@ngrx/store';
import { State } from '@store/index';
import { testResultInEdit } from '@store/test-records';
import { map, take, tap } from 'rxjs';

export const AdrNotesSection: FormNode = {
name: 'notesSection',
label: 'Notes',
type: FormNodeTypes.GROUP,
children: [
{
name: 'testTypes',
label: 'Test Types',
type: FormNodeTypes.ARRAY,
children: [
{
name: '0', // it is important here that the name of the node for an ARRAY type should be an index value
type: FormNodeTypes.GROUP,
children: [
{
name: 'additionalNotesRecorded',
label: 'Additional Notes',
type: FormNodeTypes.CONTROL,
value: '',
editType: FormNodeEditTypes.TEXTAREA,
validators: [{ name: ValidatorNames.MaxLength, args: 500 }],
asyncValidators: [
// @TODO abstract into generic custom validator when used in multiple places
{
name: AsyncValidatorNames.Custom,
args: (control: CustomFormControl, store: Store<State>) => {
return store.pipe(
select(testResultInEdit),
take(1),
map((testResult) => testResult?.testTypes.at(0)?.centralDocs?.issueRequired),
tap((issueRequired) => {
control.meta.hint = issueRequired ? 'Enter a reason for issuing documents centrally' : '';
}),
map(() => null),
);
},
},
],
},
],
},
],
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,38 @@ export const ContingencyTestSectionGroup15and16: FormNode = {
{ value: 'pass', label: 'Pass' },
{ value: 'fail', label: 'Fail' },
],
validators: [{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'testExpiryDate', value: 'pass' } }],
validators: [
{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'testExpiryDate', value: 'pass' } },
{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'centralDocs', value: ['pass', 'prs'] } },
],
asyncValidators: [{ name: AsyncValidatorNames.PassResultDependantOnCustomDefects }],
type: FormNodeTypes.CONTROL,
},
{
name: 'centralDocs',
type: FormNodeTypes.GROUP,
children: [
{
name: 'issueRequired',
type: FormNodeTypes.CONTROL,
label: 'Issue documents centrally',
editType: FormNodeEditTypes.RADIO,
value: false,
options: [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' },
],
validators: [{ name: ValidatorNames.HideIfParentSiblingEqual, args: { sibling: 'certificateNumber', value: true } }],
},
{
name: 'reasonsForIssue',
type: FormNodeTypes.CONTROL,
viewType: FormNodeViewTypes.HIDDEN,
editType: FormNodeEditTypes.HIDDEN,
value: [],
},
],
},
{
name: 'reasonForAbandoning',
type: FormNodeTypes.CONTROL,
Expand All @@ -76,7 +104,12 @@ export const ContingencyTestSectionGroup15and16: FormNode = {
label: 'Certificate number',
type: FormNodeTypes.CONTROL,
editType: FormNodeEditTypes.TEXT,
validators: [{ name: ValidatorNames.Required }, { name: ValidatorNames.Alphanumeric }],
validators: [
{ name: ValidatorNames.Required },
{ name: ValidatorNames.Alphanumeric },
// Make required if test result is pass/prs, but issue documents centrally is false
{ name: ValidatorNames.IssueRequired },
],
viewType: FormNodeViewTypes.HIDDEN,
required: true,
value: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,36 @@ export const ContingencyTestSectionGroup7: FormNode = {
{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'certificateNumber', value: 'pass' } },
{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'generateCert', value: 'pass' } },
{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'testExpiryDate', value: 'pass' } },
{ name: ValidatorNames.HideIfNotEqual, args: { sibling: 'centralDocs', value: 'pass' } },
],
asyncValidators: [{ name: AsyncValidatorNames.ResultDependantOnCustomDefects }],
type: FormNodeTypes.CONTROL,
},
{
name: 'centralDocs',
type: FormNodeTypes.GROUP,
children: [
{
name: 'issueRequired',
type: FormNodeTypes.CONTROL,
label: 'Issue documents centrally',
editType: FormNodeEditTypes.RADIO,
value: false,
options: [
{ value: true, label: 'Yes' },
{ value: false, label: 'No' },
],
validators: [{ name: ValidatorNames.HideIfParentSiblingEqual, args: { sibling: 'certificateNumber', value: true } }],
},
{
name: 'reasonsForIssue',
type: FormNodeTypes.CONTROL,
viewType: FormNodeViewTypes.HIDDEN,
editType: FormNodeEditTypes.HIDDEN,
value: [],
},
],
},
{
name: 'testTypeName',
label: 'Description',
Expand Down Expand Up @@ -103,10 +129,8 @@ export const ContingencyTestSectionGroup7: FormNode = {
type: FormNodeTypes.CONTROL,
validators: [
{ name: ValidatorNames.Alphanumeric },
{
name: ValidatorNames.RequiredIfEquals,
args: { sibling: 'testResult', value: ['pass'] },
},
// Make required if test result is pass/prs, but issue documents centrally is false
{ name: ValidatorNames.IssueRequired },
],
required: true,
value: null,
Expand Down
Loading

0 comments on commit e5d5615

Please sign in to comment.