From 1ccf165619dfcc97cbfdc59723fa25407df8474d Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Fri, 6 Mar 2026 17:02:50 +0000 Subject: [PATCH 01/37] Adding Enforcement Override Add/Change page --- ...ant-details-enforcement-tab.component.html | 15 +- ...-details-enforcement-tab.component.spec.ts | 8 +- ...ndant-details-enforcement-tab.component.ts | 7 +- ...fines-acc-defendant-details.component.html | 1 + ...es-acc-defendant-details.component.spec.ts | 16 +- .../fines-acc-defendant-details.component.ts | 11 +- ...erride-add-change-field-errors.constant.ts | 22 +++ ...rride-add-change-routing-paths.constant.ts | 10 + ...ride-add-change-routing-titles.constant.ts | 10 + ...nf-override-add-change-form.component.html | 56 ++++++ ...override-add-change-form.component.spec.ts | 84 +++++++++ ...-enf-override-add-change-form.component.ts | 113 ++++++++++++ ...acc-enf-override-add-change.component.html | 17 ++ ...-enf-override-add-change.component.spec.ts | 174 ++++++++++++++++++ ...s-acc-enf-override-add-change.component.ts | 105 +++++++++++ ...rride-add-change-field-errors.interface.ts | 9 + ...verride-add-change-form-state.interface.ts | 5 + ...-enf-override-add-change-form.interface.ts | 6 + ...ride-add-change-routing-paths.interface.ts | 10 + ...es-acc-defendant-routing-paths.constant.ts | 1 + ...s-acc-defendant-routing-titles.constant.ts | 1 + .../fines-acc/routing/fines-acc.routes.ts | 25 +++ ...s-acc-defendant-routing-paths.interface.ts | 1 + .../services/fines-acc-payload.service.ts | 22 ++- .../fetch-enforcers-resolver.ts | 9 + .../fetch-ljas-resolver.ts | 9 + .../fetch-results-with-params-resolver.ts | 11 ++ .../opal-fines-cache-defaults.constant.ts | 1 + .../constants/opal-fines-paths.constant.ts | 2 + .../interfaces/opal-fines-cache.interface.ts | 2 + .../opal-fines-enforcer.interface.ts | 6 + ...opal-fines-enforcers-ref-data.interface.ts | 6 + .../interfaces/opal-fines-paths.interface.ts | 1 + .../opal-fines-results-params.interface.ts | 3 + ...-account-enforcement-override.interface.ts | 5 + ...ines-update-defendant-account.interface.ts | 4 +- .../mocks/opal-fines-results-params.mock.ts | 5 + .../opal-fines.service.spec.ts | 71 ++++++- .../opal-fines-service/opal-fines.service.ts | 46 ++++- 39 files changed, 890 insertions(+), 20 deletions(-) create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts create mode 100644 src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts create mode 100644 src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts create mode 100644 src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts create mode 100644 src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts create mode 100644 src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts create mode 100644 src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts create mode 100644 src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts create mode 100644 src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts create mode 100644 src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html index ddf74622bb..5d08436cb5 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html @@ -196,7 +196,7 @@

Enforcement status

} } } - @if (tabData.last_enforcement_action.result_responses) { + @if (tabData.last_enforcement_action?.result_responses) { @for (param of tabData.last_enforcement_action.result_responses; track param.parameter_name) { @if (param.parameter_name === 'court' && param.response) {
Enforcement status } - @if (tabData.enforcement_override.enforcement_override_result.enforcement_override_result_id) { + @if (tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id) { @if (hasAccountMaintenancePermission) { @@ -333,10 +333,17 @@

Actions

} @if ( hasAccountMaintenancePermission && - !tabData.enforcement_override.enforcement_override_result.enforcement_override_result_id + !tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id ) {

- Add enforcement override + Add enforcement override

}

diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts index 81db86375e..6693352833 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FinesAccDefendantDetailsEnforcementTab } from './fines-acc-defendant-details-enforcement-tab.component'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; -import { beforeEach, describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; describe('FinesAccDefendantDetailsEnforcementTab', () => { let component: FinesAccDefendantDetailsEnforcementTab; @@ -21,4 +21,10 @@ describe('FinesAccDefendantDetailsEnforcementTab', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should handleAddEnforcementOverride when add enforcement override button is clicked', () => { + const eventEmitterSpy = vi.spyOn(component.addEnforcementOverride, 'emit'); + component.handleAddEnforcementOverride(); + expect(eventEmitterSpy).toHaveBeenCalled(); + }); }); diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts index 3820295719..bad6b9ecfc 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from '@angular/core'; import { IFinesAccSummaryTabsContentStyles } from '../interfaces/fines-acc-summary-tabs-content-styles.interface'; import { FINES_ACC_SUMMARY_TABS_CONTENT_STYLES } from '../../constants/fines-acc-summary-tabs-content-styles.constant'; import { @@ -38,4 +38,9 @@ export class FinesAccDefendantDetailsEnforcementTab { @Input() isCompanyAccount: boolean = false; @Input() hasAccountMaintenancePermission: boolean = false; @Input() hasEnterEnforcementPermission: boolean = false; + @Output() addEnforcementOverride = new EventEmitter(); + + handleAddEnforcementOverride(): void { + this.addEnforcementOverride.emit(); + } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html index 045f4d7578..39396c237f 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html @@ -205,6 +205,7 @@

Business Unit:

[isCompanyAccount]="accountData.party_details.organisation_flag" [hasAccountMaintenancePermission]="hasPermission('account-maintenance')" [hasEnterEnforcementPermission]="hasPermission('enter-enforcement')" + (addEnforcementOverride)="navigateToAddEnforcementOverridePage()" > } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts index befe3bd353..feaa9c5f9b 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts @@ -68,7 +68,7 @@ describe('FinesAccDefendantDetailsComponent', () => { getDefendantAccountHistoryAndNotesTabData: vi .fn() .mockName('OpalFines.getDefendantAccountHistoryAndNotesTabData'), - getDefendantAccountEnforcementTabData: vi.fn().mockName('OpalFines.getDefendantAccountEnforcementTabData'), + getDefendantAccountEnforcementStatus: vi.fn().mockName('OpalFines.getDefendantAccountEnforcementStatus'), getDefendantAccountPaymentTermsLatest: vi.fn().mockName('OpalFines.getDefendantAccountPaymentTermsLatest'), getDefendantAccountParty: vi.fn().mockName('OpalFines.getDefendantAccountParty'), getParentOrGuardianAccountParty: vi.fn().mockName('OpalFines.getParentOrGuardianAccountParty'), @@ -87,7 +87,7 @@ describe('FinesAccDefendantDetailsComponent', () => { mockOpalFinesService.getDefendantAccountFixedPenalty.mockReturnValue( of(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_FIXED_PENALTY_MOCK), ); - mockOpalFinesService.getDefendantAccountEnforcementTabData.mockReturnValue( + mockOpalFinesService.getDefendantAccountEnforcementStatus.mockReturnValue( of(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK), ); mockOpalFinesService.getDefendantAccountPaymentTermsLatest.mockReturnValue( @@ -158,6 +158,16 @@ describe('FinesAccDefendantDetailsComponent', () => { }); }); + it('should call router.navigate when navigateToAddEnforcementOverridePage is called', () => { + component.navigateToAddEnforcementOverridePage(); + expect(routerSpy.navigate).toHaveBeenCalledWith( + [`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/override/add`], + { + relativeTo: component['activatedRoute'], + }, + ); + }); + it('should fetch the defendant tab data when fragment is changed to defendant', () => { component['refreshFragment$'].next('defendant'); // Subscribe to trigger the pipe execution @@ -186,7 +196,7 @@ describe('FinesAccDefendantDetailsComponent', () => { component['refreshFragment$'].next('enforcement'); // Subscribe to trigger the pipe execution component.tabEnforcement$.subscribe(); - expect(mockOpalFinesService.getDefendantAccountEnforcementTabData).toHaveBeenCalled(); + expect(mockOpalFinesService.getDefendantAccountEnforcementStatus).toHaveBeenCalled(); expect(mockPayloadService.transformPayload).toHaveBeenCalled(); }); diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts index f6b136cc90..a57def4d7c 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts @@ -237,7 +237,7 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement break; case 'enforcement': this.tabEnforcement$ = this.fetchTabData( - this.opalFinesService.getDefendantAccountEnforcementTabData(account_id), + this.opalFinesService.getDefendantAccountEnforcementStatus(account_id), ); break; case 'impositions': @@ -447,4 +447,13 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement ); } } + + /** + * Navigates to the add enforcement override page or access denied page based on user permissions. + */ + public navigateToAddEnforcementOverridePage(): void { + this['router'].navigate([`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/override/add`], { + relativeTo: this.activatedRoute, + }); + } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts new file mode 100644 index 0000000000..71cb05a243 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts @@ -0,0 +1,22 @@ +import { IFinesAccEnfOverrideAddChangeFieldErrors } from '../interfaces/fines-acc-enf-override-add-change-field-errors.interface'; + +export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS: IFinesAccEnfOverrideAddChangeFieldErrors = { + fenf_account_enforcement_action: { + required: { + message: `Select an enforcement action`, + priority: 1, + }, + }, + fenf_account_enforcement_enforcer: { + required: { + message: `Select an enforcer`, + priority: 2, + }, + }, + fenf_account_enforcement_lja: { + required: { + message: `Select a Local Justice Area`, + priority: 2, + }, + }, +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts new file mode 100644 index 0000000000..5b8093e1c5 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts @@ -0,0 +1,10 @@ +import { IFinesAccEnfOverrideAddChangeRoutingPaths } from '../interfaces/fines-acc-enf-override-add-change-routing-paths.interface'; + +export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS: IFinesAccEnfOverrideAddChangeRoutingPaths = { + root: 'override', + children: { + add: 'add', + remove: 'remove', + change: 'change', + }, +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts new file mode 100644 index 0000000000..8903f1e7b7 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts @@ -0,0 +1,10 @@ +import { IFinesAccEnfOverrideAddChangeRoutingPaths } from '../interfaces/fines-acc-enf-override-add-change-routing-paths.interface'; + +export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES: IFinesAccEnfOverrideAddChangeRoutingPaths = { + root: 'Enforcement Override', + children: { + add: 'Add enforcement override', + remove: 'Remove enforcement override', + change: 'Change enforcement override', + }, +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html new file mode 100644 index 0000000000..8f9d613d1b --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html @@ -0,0 +1,56 @@ + +
+ + @if (form.controls['fenf_account_enforcement_enforcer'].enabled) { + + } + @if (form.controls['fenf_account_enforcement_lja'].enabled) { + + } + +
+
+ + Add override + +
+
+ +
+
+
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts new file mode 100644 index 0000000000..09adf48097 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts @@ -0,0 +1,84 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FinesAccEnfOverrideAddChangeFormComponent } from './fines-acc-enf-override-add-change-form.component'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { ActivatedRoute } from '@angular/router'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { of } from 'rxjs'; + +describe('FinesAccEnfOverrideAddChangeFormComponent', () => { + let component: FinesAccEnfOverrideAddChangeFormComponent; + let fixture: ComponentFixture; + let mockOpalFines: Pick; + + beforeEach(async () => { + mockOpalFines = { + getResult: vi.fn().mockReturnValue(of({ requires_enforcer: false, requires_lja: false })), + }; + + await TestBed.configureTestingModule({ + imports: [FinesAccEnfOverrideAddChangeFormComponent], + providers: [ + { provide: OpalFines, useValue: mockOpalFines }, + { provide: ActivatedRoute, useValue: { snapshot: { params: {}, data: {} } } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(FinesAccEnfOverrideAddChangeFormComponent); + component = fixture.componentInstance; + component.enforcementActionOptions = [{ value: 'R1', name: 'Result One (R1)' }]; + component.enforcerOptions = [{ value: 'E1', name: 'Enforcer One (E1)' }]; + component.localJusticeAreaOptions = [{ value: 'L1', name: 'LJA One (L1)' }]; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize form controls and disable dependent fields', () => { + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.disabled).toBe(true); + expect(ljaControl?.disabled).toBe(true); + }); + + it('should call handleChangeEnforcementAction on enforcement action change', () => { + const spy = vi.spyOn(component, 'handleChangeEnforcementAction'); + component.form.get('fenf_account_enforcement_action')?.setValue('R1'); + expect(spy).toHaveBeenCalledWith('R1'); + }); + + it('should enable only enforcer control when result requires enforcer', () => { + mockOpalFines.getResult = vi.fn().mockReturnValue(of({ requires_enforcer: true, requires_lja: false })); + component.handleChangeEnforcementAction('R1'); + + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.enabled).toBe(true); + expect(ljaControl?.disabled).toBe(true); + }); + + it('should enable only LJA control when result requires LJA', () => { + mockOpalFines.getResult = vi.fn().mockReturnValue(of({ requires_enforcer: false, requires_lja: true })); + component.handleChangeEnforcementAction('R1'); + + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.disabled).toBe(true); + expect(ljaControl?.enabled).toBe(true); + }); + + it('should enable both controls when result requires enforcer and LJA', () => { + mockOpalFines.getResult = vi.fn().mockReturnValue(of({ requires_enforcer: true, requires_lja: true })); + component.handleChangeEnforcementAction('R1'); + + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.enabled).toBe(true); + expect(ljaControl?.enabled).toBe(true); + }); +}); diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts new file mode 100644 index 0000000000..35cbd4d222 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts @@ -0,0 +1,113 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, inject } from '@angular/core'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { AbstractFormBaseComponent } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base'; +import { + IAbstractFormBaseFieldErrors, + IAbstractFormBaseForm, +} from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; +import { IAbstractFormControlErrorMessage } from '@hmcts/opal-frontend-common/components/abstract/interfaces'; +import { GovukButtonComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-button'; +import { GovukCancelLinkComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-cancel-link'; +import { GovukErrorSummaryComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-error-summary'; +import { IGovUkSelectOptions } from '@hmcts/opal-frontend-common/components/govuk/govuk-select/interfaces'; +import { FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS } from '../constants/fines-acc-enf-override-add-change-field-errors.constant'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { AlphagovAccessibleAutocompleteComponent } from '@hmcts/opal-frontend-common/components/alphagov/alphagov-accessible-autocomplete'; +import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; +import { IFinesAccEnfOverrideAddChangeFormState } from '../interfaces/fines-acc-enf-override-add-change-form-state.interface'; + +@Component({ + selector: 'app-fines-enf-override-add-change-form', + imports: [ + FormsModule, + ReactiveFormsModule, + GovukButtonComponent, + GovukCancelLinkComponent, + GovukErrorSummaryComponent, + AlphagovAccessibleAutocompleteComponent, + ], + templateUrl: './fines-acc-enf-override-add-change-form.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, +}) +export class FinesAccEnfOverrideAddChangeFormComponent extends AbstractFormBaseComponent implements OnInit, OnDestroy { + private finesService = inject(OpalFines); + protected override fieldErrors: IAbstractFormBaseFieldErrors = { + ...FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS, + }; + protected override formSubmit = new EventEmitter>(); + public override formControlErrorMessages: IAbstractFormControlErrorMessage = {}; + public defendantAccRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS; + @Input({ required: true }) enforcementActionOptions!: IGovUkSelectOptions[]; + @Input({ required: true }) enforcerOptions!: IGovUkSelectOptions[]; + @Input({ required: true }) localJusticeAreaOptions!: IGovUkSelectOptions[]; + + /** + * Sets up the enforcement override add/change form with the necessary form controls. + */ + private setupEnforcementOverrideAddChangeForm(): void { + this.form = new FormGroup({ + fenf_account_enforcement_action: new FormControl(null, Validators.required), + fenf_account_enforcement_enforcer: new FormControl(null, Validators.required), + fenf_account_enforcement_lja: new FormControl(null, Validators.required), + }); + this.disableFormControl('fenf_account_enforcement_enforcer'); + this.disableFormControl('fenf_account_enforcement_lja'); + } + + /** + * Sets up event listeners for changing form values + */ + private setupFormValueChangeListeners(): void { + this.form.get('fenf_account_enforcement_action')?.valueChanges.subscribe((change: string | null): void => { + this.handleChangeEnforcementAction(change ?? ''); + }); + } + + private getEnforcementActionResult(id: string): void { + this.finesService.getResult(id).subscribe((result) => { + console.log(result); + this.disableFormControl('fenf_account_enforcement_lja'); + this.disableFormControl('fenf_account_enforcement_enforcer'); + if (result.requires_enforcer) { + this.enableFormControl('fenf_account_enforcement_enforcer'); + } + if (result.requires_lja) { + this.enableFormControl('fenf_account_enforcement_lja'); + } + this.form.updateValueAndValidity(); + }); + } + + /** + * Enables a form control + * @param controlName Name of the form field to enable + */ + private enableFormControl(controlName: string): void { + this.form.get(controlName)?.enable(); + } + + /** + * Disables a form control and resets its value. + * @param controlName Name of the form field to disable + */ + private disableFormControl(controlName: string): void { + this.form.get(controlName)?.reset(); + this.form.get(controlName)?.disable(); + } + + public override ngOnInit(): void { + this.setupEnforcementOverrideAddChangeForm(); + this.setupFormValueChangeListeners(); + super.ngOnInit(); + } + + /** + * Handles the change enforcement action event emitted from the form when a new enforcement action is selected. + * @param id The result ID for the enforcement action that has been selected in the form + * @returns void + */ + public handleChangeEnforcementAction(id: string): void { + this.getEnforcementActionResult(id); + } +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html new file mode 100644 index 0000000000..a1a2a8f72d --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html @@ -0,0 +1,17 @@ +
+ + +
+
+ +
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts new file mode 100644 index 0000000000..dfc931eb49 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts @@ -0,0 +1,174 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { FinesAccEnfOverrideAddChangeComponent } from './fines-acc-enf-override-add-change.component'; +import { FinesAccountStore } from '@app/flows/fines/fines-acc/stores/fines-acc.store'; +import { FinesAccPayloadService } from '@app/flows/fines/fines-acc/services/fines-acc-payload.service'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service'; +import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { of, throwError } from 'rxjs'; +import { FinesAccountStoreType } from '../types/fines-account-store.type'; +import { signal } from '@angular/core'; + +describe('FinesAccEnfOverrideAddChangeComponent', () => { + let component: FinesAccEnfOverrideAddChangeComponent; + let fixture: ComponentFixture; + let mockRoute: ActivatedRoute; + let mockAccountStore: Pick< + FinesAccountStoreType, + 'getAccountNumber' | 'party_name' | 'account_id' | 'base_version' | 'business_unit_id' | 'setSuccessMessage' + >; + let mockPayloadService: Pick; + let mockOpalFinesService: Pick< + OpalFines, + 'patchDefendantAccount' | 'getEnforcerPrettyName' | 'getLocalJusticeAreaPrettyName' | 'getResultPrettyName' + >; + let mockUtilsService: Pick; + + beforeEach(async () => { + mockRoute = { + snapshot: { + data: { + pageHeading: 'Add enforcement override', + enforcersRefData: { + refData: [ + { enforcer_id: 'E1', enforcer_code: 'EC1', name: 'Enforcer One' }, + { enforcer_id: 'E2', enforcer_code: 'EC2', name: 'Enforcer Two' }, + ], + }, + localJusticeAreasRefData: { + refData: [ + { local_justice_area_id: 'L1', name: 'LJA One' }, + { local_justice_area_id: 'L2', name: 'LJA Two' }, + ], + }, + resultsRefData: { + refData: [ + { result_id: 'R1', result_title: 'Result One' }, + { result_id: 'R2', result_title: 'Result Two' }, + ], + }, + }, + }, + } as unknown as ActivatedRoute; + + mockAccountStore = { + getAccountNumber: signal('123456'), + party_name: signal('Test Person'), + account_id: signal(1001), + base_version: signal('1'), + business_unit_id: signal('2002'), + setSuccessMessage: vi.fn(), + }; + + mockPayloadService = { + buildEnforcementOverrideFormPayload: vi.fn().mockReturnValue({ enforcement_override: {} }), + }; + + mockOpalFinesService = { + patchDefendantAccount: vi.fn().mockReturnValue(of({})), + getEnforcerPrettyName: vi.fn( + (enforcer: { name: string; enforcer_code: string | number }) => `${enforcer.name} (${enforcer.enforcer_code})`, + ), + getLocalJusticeAreaPrettyName: vi.fn( + (lja: { name: string; local_justice_area_id: string | number }) => `${lja.name} (${lja.local_justice_area_id})`, + ), + getResultPrettyName: vi.fn( + (result: { result_title: string; result_id: string | number }) => + `${result.result_title} (${result.result_id})`, + ), + }; + + mockUtilsService = { + scrollToTop: vi.fn(), + }; + + await TestBed.configureTestingModule({ + imports: [FinesAccEnfOverrideAddChangeComponent], + providers: [ + { provide: ActivatedRoute, useValue: mockRoute }, + { provide: FinesAccountStore, useValue: mockAccountStore }, + { provide: FinesAccPayloadService, useValue: mockPayloadService }, + { provide: OpalFines, useValue: mockOpalFinesService }, + { provide: UtilsService, useValue: mockUtilsService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(FinesAccEnfOverrideAddChangeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should map route data into option lists', () => { + expect(component.pageTitle).toBe('Add enforcement override'); + expect(component.enforcerOptions).toEqual([ + { value: 'E1', name: 'Enforcer One (EC1)' }, + { value: 'E2', name: 'Enforcer Two (EC2)' }, + ]); + expect(component.localJusticeAreaOptions).toEqual([ + { value: 'L1', name: 'LJA One (L1)' }, + { value: 'L2', name: 'LJA Two (L2)' }, + ]); + expect(component.enforcementActionOptions).toEqual([ + { value: 'R1', name: 'Result One (R1)' }, + { value: 'R2', name: 'Result Two (R2)' }, + ]); + }); + + it('should submit form and navigate on success', () => { + const routerNavigateSpy = vi.spyOn(component as never, 'routerNavigate'); + const form = { + formData: { + fenf_account_enforcement_action: 'R1', + fenf_account_enforcement_enforcer: 'E1', + fenf_account_enforcement_lja: 'L1', + }, + nestedFlow: false, + }; + + component.handleFinesEnfOverrideAddChangeSubmit(form); + + expect(mockPayloadService.buildEnforcementOverrideFormPayload).toHaveBeenCalledWith(form.formData); + expect(mockOpalFinesService.patchDefendantAccount).toHaveBeenCalledWith( + 1001, + { enforcement_override: {} }, + '1', + '2002', + ); + expect(routerNavigateSpy).toHaveBeenCalledWith(FINES_ACC_DEFENDANT_ROUTING_PATHS.children.details); + }); + + it('should scroll to top on submit error', () => { + mockOpalFinesService.patchDefendantAccount = vi.fn().mockReturnValue(throwError(() => new Error('fail'))); + const routerNavigateSpy = vi.spyOn(component as never, 'routerNavigate'); + const form = { + formData: { + fenf_account_enforcement_action: 'R1', + fenf_account_enforcement_enforcer: 'E1', + fenf_account_enforcement_lja: 'L1', + }, + nestedFlow: false, + }; + + component.handleFinesEnfOverrideAddChangeSubmit(form); + + expect(mockUtilsService.scrollToTop).toHaveBeenCalled(); + expect(routerNavigateSpy).not.toHaveBeenCalled(); + }); + + it('should update stateUnsavedChanges when handleUnsavedChanges is called', () => { + component.handleUnsavedChanges(true); + expect((component as unknown as { stateUnsavedChanges: boolean }).stateUnsavedChanges).toBe(true); + }); + + it('should complete ngUnsubscribe on destroy', () => { + const subject = (component as unknown as { ngUnsubscribe: { isStopped: boolean } }).ngUnsubscribe; + component.ngOnDestroy(); + expect(subject.isStopped).toBe(true); + }); +}); diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts new file mode 100644 index 0000000000..efc03351bb --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts @@ -0,0 +1,105 @@ +import { ChangeDetectionStrategy, Component, inject, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { IOpalFinesResultsRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-ref-data.interface'; +import { FinesAccEnfOverrideAddChangeFormComponent } from './fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component'; +import { FinesAccountStore } from '@app/flows/fines/fines-acc/stores/fines-acc.store'; +import { IGovUkSelectOptions } from '@hmcts/opal-frontend-common/components/govuk/govuk-select/interfaces'; +import { GovukHeadingWithCaptionComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-heading-with-caption'; +import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; +import { IOpalFinesLocalJusticeAreaRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; +import { AbstractFormParentBaseComponent } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-parent-base'; +import { catchError, EMPTY, Subject, takeUntil } from 'rxjs'; +import { FinesAccPayloadService } from '@app/flows/fines/fines-acc/services/fines-acc-payload.service'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; +import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service'; +import { IFinesAccEnfOverrideAddChangeFormState } from './interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; + +@Component({ + selector: 'app-fines-acc-enf-override-add-change', + templateUrl: './fines-acc-enf-override-add-change.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [FinesAccEnfOverrideAddChangeFormComponent, GovukHeadingWithCaptionComponent], +}) +export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBaseComponent implements OnDestroy { + private readonly ngUnsubscribe = new Subject(); + private route = inject(ActivatedRoute); + private finesAccStore = inject(FinesAccountStore); + private finesAccPayloadService = inject(FinesAccPayloadService); + private opalFinesService = inject(OpalFines); + private utilsService = inject(UtilsService); + private finesDefendantRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS; + public accountNumber = this.finesAccStore.getAccountNumber(); + public partyName = this.finesAccStore.party_name(); + public pageTitle = this.route.snapshot.data['pageHeading'] as string; + public enforcerOptions: IGovUkSelectOptions[] = this.setEnforcerOptions(); + public localJusticeAreaOptions: IGovUkSelectOptions[] = this.setLocalJusticeAreaOptions(); + public enforcementActionOptions: IGovUkSelectOptions[] = this.setEnforcementActionOptions(); + + private setEnforcerOptions(): IGovUkSelectOptions[] { + return (this.route.snapshot.data['enforcersRefData'] as IOpalFinesEnforcersRefData).refData.map((enforcer) => ({ + value: enforcer.enforcer_id, + name: this.opalFinesService.getEnforcerPrettyName(enforcer), + })); + } + + private setLocalJusticeAreaOptions(): IGovUkSelectOptions[] { + return (this.route.snapshot.data['localJusticeAreasRefData'] as IOpalFinesLocalJusticeAreaRefData).refData.map( + (lja) => ({ + value: lja.local_justice_area_id, + name: this.opalFinesService.getLocalJusticeAreaPrettyName(lja), + }), + ); + } + + private setEnforcementActionOptions(): IGovUkSelectOptions[] { + return (this.route.snapshot.data['resultsRefData'] as IOpalFinesResultsRefData).refData.map((result) => ({ + value: result.result_id, + name: this.opalFinesService.getResultPrettyName(result), + })); + } + + /** + * Handles the form submission for adding/removing/changing an enforcement override. + * @param form - The form data containing the enforcement override details. + */ + public handleFinesEnfOverrideAddChangeSubmit( + form: IAbstractFormBaseForm, + ): void { + const payload = this.finesAccPayloadService.buildEnforcementOverrideFormPayload(form.formData); + this.opalFinesService + .patchDefendantAccount( + this.finesAccStore.account_id()!, + payload, + this.finesAccStore.base_version()!, + this.finesAccStore.business_unit_id()!, + ) + .pipe( + catchError(() => { + this.utilsService.scrollToTop(); + return EMPTY; + }), + takeUntil(this.ngUnsubscribe), + ) + .subscribe(() => { + this.finesAccStore.setSuccessMessage('Enforcement override added'); + this.routerNavigate(this.finesDefendantRoutingPaths.children.details); + }); + } + + /** + * Handles unsaved changes coming from the child component + * + * @param unsavedChanges boolean value from child component + */ + public handleUnsavedChanges(unsavedChanges: boolean): void { + this.stateUnsavedChanges = unsavedChanges; + } + + public ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts new file mode 100644 index 0000000000..e0bef04bea --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts @@ -0,0 +1,9 @@ +import { + IAbstractFormBaseFieldError, + IAbstractFormBaseFieldErrors, +} from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; +import { IFinesAccEnfOverrideAddChangeFormState } from './fines-acc-enf-override-add-change-form-state.interface'; + +export type IFinesAccEnfOverrideAddChangeFieldErrors = IAbstractFormBaseFieldErrors & { + [K in keyof IFinesAccEnfOverrideAddChangeFormState]: IAbstractFormBaseFieldError; +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts new file mode 100644 index 0000000000..ecda5df1b5 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts @@ -0,0 +1,5 @@ +export interface IFinesAccEnfOverrideAddChangeFormState { + fenf_account_enforcement_action: string | null; + fenf_account_enforcement_enforcer: string | null; + fenf_account_enforcement_lja: string | null; +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts new file mode 100644 index 0000000000..f7fe8d145c --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts @@ -0,0 +1,6 @@ +import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; +import { IFinesAccEnfOverrideAddChangeFormState } from './fines-acc-enf-override-add-change-form-state.interface'; + +export interface IFinesAccEnfOverrideAddChangeForm extends IAbstractFormBaseForm { + formData: IFinesAccEnfOverrideAddChangeFormState; +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts new file mode 100644 index 0000000000..cd87acedfa --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts @@ -0,0 +1,10 @@ +import { IChildRoutingPaths } from '@hmcts/opal-frontend-common/pages/routing/interfaces'; + +export interface IFinesAccEnfOverrideAddChangeRoutingPaths extends IChildRoutingPaths { + root: string; + children: { + add: string; + remove: string; + change: string; + }; +} diff --git a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts index 8c6b36df90..0c380e98ba 100644 --- a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts +++ b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts @@ -10,5 +10,6 @@ export const FINES_ACC_DEFENDANT_ROUTING_PATHS: IFinesAccDefendantRoutingPaths = 'payment-terms': 'payment-terms', party: 'party', 'payment-card': 'payment-card', + enforcement: 'enforcement', }, }; diff --git a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts index f1d8b7bd04..c7ddb586d4 100644 --- a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts +++ b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts @@ -10,5 +10,6 @@ export const FINES_ACC_DEFENDANT_ROUTING_TITLES: IFinesAccDefendantRoutingPaths 'payment-terms': 'Amend payment terms', party: 'Amend party details', 'payment-card': 'Request payment card', + enforcement: 'Enforcement', }, }; diff --git a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts index acf3c220ec..fce6a1f672 100644 --- a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts +++ b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts @@ -16,6 +16,12 @@ import { FINES_ACC_MINOR_CREDITOR_ROUTING_TITLES } from './constants/fines-acc-m import { defendantAccountPartyResolver } from './resolvers/defendant-account-party.resolver'; import { defendantAccountPaymentTermsLatestResolver } from './resolvers/defendant-account-payment-terms-latest.resolver'; import { minorCreditorAccountHeadingResolver } from './resolvers/defendant-minor-creditor-heading.resolver'; +import { fetchResultsWithParamsResolver } from '../../routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver'; +import { IOpalFinesResultsParams } from '../../services/opal-fines-service/interfaces/opal-fines-results-params.interface'; +import { fetchLocalJusticeAreasResolver } from '../../routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver'; +import { fetchEnforcersResolver } from '../../routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver'; +import { FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES } from '../fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant'; +import { FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS } from '../fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant'; const accRootPermissionIds = FINES_PERMISSIONS; @@ -168,6 +174,25 @@ export const routing: Routes = [ defendantAccountPaymentTermsData: defendantAccountPaymentTermsLatestResolver, }, }, + { + path: `${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/${FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS.root}/${FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS.children.add}`, + loadComponent: () => + import('../fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component').then( + (c) => c.FinesAccEnfOverrideAddChangeComponent, + ), + data: { + resultsParams: { enforcement_override: true } as IOpalFinesResultsParams, + title: FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES.children.add, + pageHeading: FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES.children.add, + }, + resolve: { + title: TitleResolver, + resultsRefData: fetchResultsWithParamsResolver, + localJusticeAreasRefData: fetchLocalJusticeAreasResolver, + enforcersRefData: fetchEnforcersResolver, + }, + canDeactivate: [canDeactivateGuard], + }, ], }, { diff --git a/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts b/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts index cc514a485e..04763cdef9 100644 --- a/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts +++ b/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts @@ -10,5 +10,6 @@ export interface IFinesAccDefendantRoutingPaths extends IChildRoutingPaths { 'payment-terms': string; party: string; 'payment-card': string; + enforcement: string; }; } diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts index 5daed96efd..b209eecbd5 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts @@ -25,6 +25,7 @@ import { IOpalFinesAmendPaymentTermsPayload } from '@services/fines/opal-fines-s import { buildPaymentTermsAmendPayloadUtil } from './utils/fines-acc-payload-build-payment-terms-amend.utils'; import { buildAccountPartyFromFormState } from './utils/fines-acc-payload-build-defendant-data.utils'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; +import { IFinesAccEnfOverrideAddChangeFormState } from '../fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface'; @Injectable({ providedIn: 'root', @@ -153,7 +154,7 @@ export class FinesAccPayloadService { } /** - * Transforms the given IFinesAccAddCommentsFormState and account version into an update payload + * Transforms the given IFinesAccAddCommentsFormState into an update payload * for the defendant account API. * * @param formState - The form state containing the comment and note data @@ -170,6 +171,25 @@ export class FinesAccPayloadService { }; } + /** + * Transforms the given IFinesAccEnfOverrideAddChangeFormState into an update payload + * for the defendant account API. + * + * @param formState - The form state containing the enforcement override data + * @returns The transformed payload for updating the defendant account + */ + public buildEnforcementOverrideFormPayload( + formState: IFinesAccEnfOverrideAddChangeFormState, + ): IOpalFinesUpdateDefendantAccountPayload { + return { + enforcement_override: { + enf_override_result_id: formState.fenf_account_enforcement_action || null, + enf_override_enforcer_id: formState.fenf_account_enforcement_enforcer || null, + enf_override_tfo_lja_id: formState.fenf_account_enforcement_lja || null, + }, + }; + } + /** * Transforms the given finesMacPayload object by applying the transformations diff --git a/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts new file mode 100644 index 0000000000..016c37caea --- /dev/null +++ b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts @@ -0,0 +1,9 @@ +import { inject } from '@angular/core'; +import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; +import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; +import { ResolveFn } from '@angular/router'; + +export const fetchEnforcersResolver: ResolveFn = () => { + const opalFinesService = inject(OpalFines); + return opalFinesService.getEnforcers(); +}; diff --git a/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts new file mode 100644 index 0000000000..4313c2cf36 --- /dev/null +++ b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts @@ -0,0 +1,9 @@ +import { ResolveFn } from '@angular/router'; +import { inject } from '@angular/core'; +import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; +import { IOpalFinesLocalJusticeAreaRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; + +export const fetchLocalJusticeAreasResolver: ResolveFn = () => { + const opalFinesService = inject(OpalFines); + return opalFinesService.getLocalJusticeAreas(); +}; diff --git a/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts new file mode 100644 index 0000000000..3c8a6fab73 --- /dev/null +++ b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts @@ -0,0 +1,11 @@ +import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router'; +import { inject } from '@angular/core'; +import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; +import { IOpalFinesResultsRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-results-ref-data.interface'; +import { IOpalFinesResultsParams } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface'; + +export const fetchResultsWithParamsResolver: ResolveFn = (route: ActivatedRouteSnapshot) => { + const resultsParams = route.data['resultsParams'] as IOpalFinesResultsParams; + const opalFinesService = inject(OpalFines); + return opalFinesService.getResults([], resultsParams); +}; diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts index d8c9a83538..7979852a31 100644 --- a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts @@ -19,4 +19,5 @@ export const OPAL_FINES_CACHE_DEFAULTS: IOpalFinesCache = { defendantAccountHistoryAndNotesCache$: null, defendantAccountPaymentTermsLatestCache$: null, defendantAccountFixedPenaltyCache$: null, + enforcersCache$: null, }; diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts index c8031d3674..3623667939 100644 --- a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts @@ -14,6 +14,7 @@ const defendantAccounts = 'defendant-accounts'; const notes = 'notes/add'; const minorCreditorAccounts = 'minor-creditor-accounts'; const searchAccounts = '/search'; +const enforcers = 'enforcers'; export const OPAL_FINES_PATHS: IOpalFinesPaths = { businessUnitRefData: `${baseUrl}${businessUnit}`, @@ -30,4 +31,5 @@ export const OPAL_FINES_PATHS: IOpalFinesPaths = { searchDefendantAccounts: `${baseUrl}${defendantAccounts}${searchAccounts}`, searchMinorCreditorAccounts: `${baseUrl}${minorCreditorAccounts}${searchAccounts}`, minorCreditorAccounts: `${baseUrl}${minorCreditorAccounts}`, + enforcersRefData: `${baseUrl}${enforcers}`, }; diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts index f6fc28668b..2baf9e6bbe 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts @@ -15,6 +15,7 @@ import { IOpalFinesAccountDefendantDetailsHistoryAndNotesTabRefData } from './op import { IOpalFinesAccountDefendantDetailsPaymentTermsLatest } from './opal-fines-account-defendant-details-payment-terms-latest.interface'; import { IOpalFinesAccountDefendantDetailsFixedPenaltyTabRefData } from './opal-fines-account-defendant-details-fixed-penalty-tab-ref-data.interface'; import { IOpalFinesResultRefData } from './opal-fines-result-ref-data.interface'; +import { IOpalFinesEnforcersRefData } from './opal-fines-enforcers-ref-data.interface'; export interface IOpalFinesCache { courtRefDataCache$: { [key: string]: Observable }; @@ -35,4 +36,5 @@ export interface IOpalFinesCache { defendantAccountHistoryAndNotesCache$: Observable | null; defendantAccountPaymentTermsLatestCache$: Observable | null; defendantAccountFixedPenaltyCache$: Observable | null; + enforcersCache$: Observable | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts new file mode 100644 index 0000000000..99cb0a036b --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts @@ -0,0 +1,6 @@ +export interface IOpalFinesEnforcer { + enforcer_id: number; + enforcer_code: number; + name: string; + name_cy: string | null; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts new file mode 100644 index 0000000000..954988349a --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts @@ -0,0 +1,6 @@ +import { IOpalFinesEnforcer } from './opal-fines-enforcer.interface'; + +export interface IOpalFinesEnforcersRefData { + count: number; + refData: IOpalFinesEnforcer[]; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts index 6b2add8d7c..79db4a2ced 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts @@ -13,4 +13,5 @@ export interface IOpalFinesPaths { searchDefendantAccounts: string; searchMinorCreditorAccounts: string; minorCreditorAccounts: string; + enforcersRefData: string; } diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts new file mode 100644 index 0000000000..b00eef01d5 --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts @@ -0,0 +1,3 @@ +export interface IOpalFinesResultsParams { + enforcement_override: boolean; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts new file mode 100644 index 0000000000..dad8f2b9f9 --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts @@ -0,0 +1,5 @@ +export interface IOpalFinesUpdateDefendantAccountEnforcementOverride { + enf_override_result_id: string | null; + enf_override_enforcer_id: string | null; + enf_override_tfo_lja_id: string | null; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts index 14ad4ce15a..c0ae20b350 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts @@ -1,8 +1,10 @@ import { IOpalFinesUpdateDefendantAccountCommentsNotes } from './opal-fines-update-defendant-account-comments-notes.interface'; +import { IOpalFinesUpdateDefendantAccountEnforcementOverride } from './opal-fines-update-defendant-account-enforcement-override.interface'; /** * Interface for the payload to update a defendant account *Subject to change */ export interface IOpalFinesUpdateDefendantAccountPayload { - comment_and_notes: IOpalFinesUpdateDefendantAccountCommentsNotes; + comment_and_notes?: IOpalFinesUpdateDefendantAccountCommentsNotes; + enforcement_override?: IOpalFinesUpdateDefendantAccountEnforcementOverride; } diff --git a/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts new file mode 100644 index 0000000000..cf0aae1c7e --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts @@ -0,0 +1,5 @@ +import { IOpalFinesResultsParams } from '../interfaces/opal-fines-results-params.interface'; + +export const OPAL_FINES_RESULTS_PARAMS_MOCK: IOpalFinesResultsParams = { + enforcement_override: true, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index b38cc9c8b1..bb5019bdd9 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -53,6 +53,8 @@ import { of } from 'rxjs'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_FIXED_PENALTY_MOCK } from './mocks/opal-fines-account-defendant-details-fixed-penalty.mock'; import { IOpalFinesResultRefData } from './interfaces/opal-fines-result-ref-data.interface'; import { FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK } from '../../fines-acc/fines-acc-minor-creditor-details/mocks/fines-acc-minor-creditor-details-header.mock'; +import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; +import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; describe('OpalFines', () => { @@ -240,6 +242,71 @@ describe('OpalFines', () => { expect(result).toEqual(`${localJusticeArea.name} (${localJusticeArea.lja_code})`); }); + it('should send a GET request to enforcers ref data API', () => { + const mockEnforcers: IOpalFinesEnforcersRefData = { + count: 1, + refData: [ + { + enforcer_id: 101, + enforcer_code: 202, + name: 'Enforcer One', + name_cy: null, + }, + ], + }; + const expectedUrl = `${OPAL_FINES_PATHS.enforcersRefData}`; + + service.getEnforcers().subscribe((response) => { + expect(response).toEqual(mockEnforcers); + }); + + const req = httpMock.expectOne(expectedUrl); + expect(req.request.method).toBe('GET'); + req.flush(mockEnforcers); + }); + + it('should return cached response for enforcers ref data', () => { + const mockEnforcers: IOpalFinesEnforcersRefData = { + count: 1, + refData: [ + { + enforcer_id: 101, + enforcer_code: 202, + name: 'Enforcer One', + name_cy: null, + }, + ], + }; + const expectedUrl = `${OPAL_FINES_PATHS.enforcersRefData}`; + + service.getEnforcers().subscribe((response) => { + expect(response).toEqual(mockEnforcers); + }); + + const req = httpMock.expectOne(expectedUrl); + expect(req.request.method).toBe('GET'); + req.flush(mockEnforcers); + + service.getEnforcers().subscribe((response) => { + expect(response).toEqual(mockEnforcers); + }); + + httpMock.expectNone(expectedUrl); + }); + + it('should return the enforcer name and code in a pretty format', () => { + const enforcer: IOpalFinesEnforcer = { + enforcer_id: 101, + enforcer_code: 202, + name: 'Enforcer One', + name_cy: null, + }; + + const result = service.getEnforcerPrettyName(enforcer); + + expect(result).toEqual(`${enforcer.name} (${enforcer.enforcer_code})`); + }); + it('should return the item value for a given configuration item name', () => { const businessUnit = OPAL_FINES_BUSINESS_UNIT_REF_DATA_MOCK.refData[0]; const expectedValue = 'Item1'; @@ -807,12 +874,12 @@ describe('OpalFines', () => { req.flush(expectedResponse); }); - it('should getDefendantAccountEnforcementTabData', () => { + it('should getDefendantAccountEnforcementStatus', () => { const account_id: number = 77; const apiUrl = `${OPAL_FINES_PATHS.defendantAccounts}/${account_id}/enforcement-status`; const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK; - service.getDefendantAccountEnforcementTabData(account_id).subscribe((response) => { + service.getDefendantAccountEnforcementStatus(account_id).subscribe((response) => { response.version = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK.version; expect(response).toEqual(expectedResponse); }); diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts index 6ef0507124..81842727c2 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts @@ -50,6 +50,9 @@ import { IOpalFinesAccountDefendantDetailsFixedPenaltyTabRefData } from './inter import { IOpalFinesResultRefData } from './interfaces/opal-fines-result-ref-data.interface'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../../fines-acc/fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; import { IOpalFinesAccountRequestPaymentCardResponse } from './interfaces/opal-fines-account-request-payment-card-response.interface'; +import { IOpalFinesResultsParams } from './interfaces/opal-fines-results-params.interface'; +import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; +import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; @Injectable({ providedIn: 'root', @@ -272,12 +275,18 @@ export class OpalFines { /** * Retrieves the Opal fines results based on the provided result IDs. * @param result_ids - An array of result IDs. + * @param params - The parameters for fetching the results. * @returns An Observable that emits the Opal fines results reference data. */ - public getResults(result_ids: string[]): Observable { + public getResults( + result_ids: string[], + params: IOpalFinesResultsParams | null = null, + ): Observable { if (!this.cache.resultsCache$) { this.cache.resultsCache$ = this.http - .get(OPAL_FINES_PATHS.resultsRefData, { params: { result_ids } }) + .get(OPAL_FINES_PATHS.resultsRefData, { + params: { result_ids, ...params }, + }) .pipe(shareReplay(1)); } @@ -563,6 +572,31 @@ export class OpalFines { return this.cache.prosecutorDataCache$[business_unit]; } + /** + * Retrieves the prosecutor data for a specific business unit. + * If the prosecutor data is not already cached, it makes an HTTP request to fetch the data and caches it for future use. + * @param business_unit - The business unit for which to retrieve the prosecutor data. + * @returns An Observable that emits the prosecutor data for the specified business unit. + */ + public getEnforcers(): Observable { + if (!this.cache.enforcersCache$) { + this.cache.enforcersCache$ = this.http + .get(OPAL_FINES_PATHS.enforcersRefData) + .pipe(shareReplay(1)); + } + + return this.cache.enforcersCache$; + } + + /** + * Returns the pretty name of an enforcer. + * @param enforcer - The enforcer object. + * @returns The pretty name of the enforcer. + */ + public getEnforcerPrettyName(enforcer: IOpalFinesEnforcer): string { + return `${enforcer.name} (${enforcer.enforcer_code})`; + } + /** * Retrieves the defendant account details at a glance for a specific tab. * If the account details for the specified tab are not already cached, it makes an HTTP request to fetch the data and caches it for future use. @@ -683,13 +717,13 @@ export class OpalFines { } /** - * Retrieves the defendant account details enforcement tab data. - * If the account details for the specified tab are not already cached, it makes an HTTP request to fetch the data and caches it for future use. + * Retrieves the defendant account details enforcement status. + * If the account details for the enforcement status are not already cached, it makes an HTTP request to fetch the data and caches it for future use. * * @param account_id - The ID of the defendant account. - * @returns An Observable that emits the account details at a glance for the specified tab. + * @returns An Observable of the defendants enforcement status. */ - public getDefendantAccountEnforcementTabData( + public getDefendantAccountEnforcementStatus( account_id: number | null, ): Observable { if (!this.cache.defendantAccountEnforcementCache$) { From 4a5c2fe042a1898fb104f5078977a141cc42f362 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 9 Mar 2026 10:38:25 +0000 Subject: [PATCH 02/37] updating method for fetching title from route data. --- .../fines-acc-enf-override-add-change.component.spec.ts | 2 +- .../fines-acc-enf-override-add-change.component.ts | 2 +- src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts index dfc931eb49..b7aec31925 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts @@ -30,7 +30,7 @@ describe('FinesAccEnfOverrideAddChangeComponent', () => { mockRoute = { snapshot: { data: { - pageHeading: 'Add enforcement override', + title: 'Add enforcement override', enforcersRefData: { refData: [ { enforcer_id: 'E1', enforcer_code: 'EC1', name: 'Enforcer One' }, diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts index efc03351bb..44fd4a5b6c 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts @@ -33,7 +33,7 @@ export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBas private finesDefendantRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS; public accountNumber = this.finesAccStore.getAccountNumber(); public partyName = this.finesAccStore.party_name(); - public pageTitle = this.route.snapshot.data['pageHeading'] as string; + public pageTitle = this.route.snapshot.data['title'] as string; public enforcerOptions: IGovUkSelectOptions[] = this.setEnforcerOptions(); public localJusticeAreaOptions: IGovUkSelectOptions[] = this.setLocalJusticeAreaOptions(); public enforcementActionOptions: IGovUkSelectOptions[] = this.setEnforcementActionOptions(); diff --git a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts index fce6a1f672..1a47ca9ed0 100644 --- a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts +++ b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts @@ -183,10 +183,9 @@ export const routing: Routes = [ data: { resultsParams: { enforcement_override: true } as IOpalFinesResultsParams, title: FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES.children.add, - pageHeading: FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES.children.add, }, resolve: { - title: TitleResolver, + titleResolver: TitleResolver, resultsRefData: fetchResultsWithParamsResolver, localJusticeAreasRefData: fetchLocalJusticeAreasResolver, enforcersRefData: fetchEnforcersResolver, From 07ac9e4f381f230280f588c1bfa70a2abeb2d071 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 9 Mar 2026 13:28:32 +0000 Subject: [PATCH 03/37] Adding route permission guard. --- ...ant-details-enforcement-tab.component.html | 8 +++-- ...-enf-override-add-change-form.component.ts | 36 +++++++++++-------- .../fines-acc/routing/fines-acc.routes.ts | 4 ++- ...ails-enforcement-tab-ref-data.interface.ts | 4 +-- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html index 5d08436cb5..9007489ca1 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html @@ -196,7 +196,7 @@

Enforcement status

} } } - @if (tabData.last_enforcement_action?.result_responses) { + @if (tabData.last_enforcement_action.result_responses) { @for (param of tabData.last_enforcement_action.result_responses; track param.parameter_name) { @if (param.parameter_name === 'court' && param.response) {
Enforcement status } - @if (tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id) { + @if ( + tabData.enforcement_override && + tabData.enforcement_override.enforcement_override_result && + tabData.enforcement_override.enforcement_override_result.enforcement_override_result_id + ) { @if (hasAccountMaintenancePermission) { diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts index 35cbd4d222..b39d79d767 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts @@ -15,6 +15,7 @@ import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fin import { AlphagovAccessibleAutocompleteComponent } from '@hmcts/opal-frontend-common/components/alphagov/alphagov-accessible-autocomplete'; import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; import { IFinesAccEnfOverrideAddChangeFormState } from '../interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { takeUntil } from 'rxjs'; @Component({ selector: 'app-fines-enf-override-add-change-form', @@ -59,24 +60,29 @@ export class FinesAccEnfOverrideAddChangeFormComponent extends AbstractFormBaseC * Sets up event listeners for changing form values */ private setupFormValueChangeListeners(): void { - this.form.get('fenf_account_enforcement_action')?.valueChanges.subscribe((change: string | null): void => { - this.handleChangeEnforcementAction(change ?? ''); - }); + this.form + .get('fenf_account_enforcement_action') + ?.valueChanges.pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((change: string | null): void => { + this.handleChangeEnforcementAction(change ?? ''); + }); } private getEnforcementActionResult(id: string): void { - this.finesService.getResult(id).subscribe((result) => { - console.log(result); - this.disableFormControl('fenf_account_enforcement_lja'); - this.disableFormControl('fenf_account_enforcement_enforcer'); - if (result.requires_enforcer) { - this.enableFormControl('fenf_account_enforcement_enforcer'); - } - if (result.requires_lja) { - this.enableFormControl('fenf_account_enforcement_lja'); - } - this.form.updateValueAndValidity(); - }); + this.finesService + .getResult(id) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((result) => { + this.disableFormControl('fenf_account_enforcement_lja'); + this.disableFormControl('fenf_account_enforcement_enforcer'); + if (result.requires_enforcer) { + this.enableFormControl('fenf_account_enforcement_enforcer'); + } + if (result.requires_lja) { + this.enableFormControl('fenf_account_enforcement_lja'); + } + this.form.updateValueAndValidity(); + }); } /** diff --git a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts index 1a47ca9ed0..e8420f4f79 100644 --- a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts +++ b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts @@ -180,9 +180,12 @@ export const routing: Routes = [ import('../fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component').then( (c) => c.FinesAccEnfOverrideAddChangeComponent, ), + canActivate: [routePermissionsGuard, finesAccStateGuard], + canDeactivate: [canDeactivateGuard], data: { resultsParams: { enforcement_override: true } as IOpalFinesResultsParams, title: FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES.children.add, + routePermissionId: [accRootPermissionIds['account-maintenance']], }, resolve: { titleResolver: TitleResolver, @@ -190,7 +193,6 @@ export const routing: Routes = [ localJusticeAreasRefData: fetchLocalJusticeAreasResolver, enforcersRefData: fetchEnforcersResolver, }, - canDeactivate: [canDeactivateGuard], }, ], }, diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts index 7fea806704..d3b3d9a64b 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts @@ -23,7 +23,7 @@ export interface IOpalFinesAccountDefendantDetailsEnforcementTabRefData { lja_id: number; lja_name: string; }; - }; + } | null; enforcement_overview: { collection_order: IOpalFinesAccountDefendantDetailsEnforcementTabRefDataEnforcementOverviewCollectionOrder | null; days_in_default: number | null; @@ -51,5 +51,5 @@ export interface IOpalFinesAccountDefendantDetailsEnforcementTabRefData { }>; warrant_number: string; } | null; - next_enforcement_action_data: string; + next_enforcement_action_data: string | null; } From 235346cf1a39f442d0413daf6edd83de073fccff Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Wed, 11 Mar 2026 15:25:26 +0000 Subject: [PATCH 04/37] Adding method docs. --- ...-details-enforcement-tab.component.spec.ts | 2 ++ ...ndant-details-enforcement-tab.component.ts | 11 ++++++++- ...nf-override-add-change-form.component.html | 6 +++++ ...-enf-override-add-change-form.component.ts | 20 ++++++++++++++++ ...acc-enf-override-add-change.component.html | 11 +++------ ...s-acc-enf-override-add-change.component.ts | 24 +++++++++++++++---- 6 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts index 6693352833..055ebf37aa 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts @@ -24,6 +24,8 @@ describe('FinesAccDefendantDetailsEnforcementTab', () => { it('should handleAddEnforcementOverride when add enforcement override button is clicked', () => { const eventEmitterSpy = vi.spyOn(component.addEnforcementOverride, 'emit'); + component.hasAccountMaintenancePermission = true; + component.tabData.enforcement_override = null; // Ensure there is no existing enforcement override result component.handleAddEnforcementOverride(); expect(eventEmitterSpy).toHaveBeenCalled(); }); diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts index bad6b9ecfc..ab43aed025 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts @@ -40,7 +40,16 @@ export class FinesAccDefendantDetailsEnforcementTab { @Input() hasEnterEnforcementPermission: boolean = false; @Output() addEnforcementOverride = new EventEmitter(); + /** + * Emits an event to add an enforcement override if the user has the necessary permissions and there is no existing enforcement override result. + * @returns void + */ handleAddEnforcementOverride(): void { - this.addEnforcementOverride.emit(); + if ( + this.hasAccountMaintenancePermission && + !this.tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id + ) { + this.addEnforcementOverride.emit(); + } } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html index 8f9d613d1b..7f010a8924 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html @@ -1,3 +1,9 @@ + + - - -
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts index 44fd4a5b6c..65849c8ea0 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts @@ -4,7 +4,6 @@ import { IOpalFinesResultsRefData } from '@app/flows/fines/services/opal-fines-s import { FinesAccEnfOverrideAddChangeFormComponent } from './fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component'; import { FinesAccountStore } from '@app/flows/fines/fines-acc/stores/fines-acc.store'; import { IGovUkSelectOptions } from '@hmcts/opal-frontend-common/components/govuk/govuk-select/interfaces'; -import { GovukHeadingWithCaptionComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-heading-with-caption'; import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; import { IOpalFinesLocalJusticeAreaRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; import { AbstractFormParentBaseComponent } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-parent-base'; @@ -21,7 +20,7 @@ import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/ab templateUrl: './fines-acc-enf-override-add-change.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [FinesAccEnfOverrideAddChangeFormComponent, GovukHeadingWithCaptionComponent], + imports: [FinesAccEnfOverrideAddChangeFormComponent], }) export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBaseComponent implements OnDestroy { private readonly ngUnsubscribe = new Subject(); @@ -31,13 +30,17 @@ export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBas private opalFinesService = inject(OpalFines); private utilsService = inject(UtilsService); private finesDefendantRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS; - public accountNumber = this.finesAccStore.getAccountNumber(); - public partyName = this.finesAccStore.party_name(); - public pageTitle = this.route.snapshot.data['title'] as string; + public accountNumber = this.finesAccStore.getAccountNumber() ?? ''; + public partyName = this.finesAccStore.party_name() ?? ''; + public pageTitle: string = this.route.snapshot.data['title'] ?? ''; public enforcerOptions: IGovUkSelectOptions[] = this.setEnforcerOptions(); public localJusticeAreaOptions: IGovUkSelectOptions[] = this.setLocalJusticeAreaOptions(); public enforcementActionOptions: IGovUkSelectOptions[] = this.setEnforcementActionOptions(); + /** + * Sets the options for the enforcer select dropdown. + * @returns An array of IGovUkSelectOptions representing the enforcers. + */ private setEnforcerOptions(): IGovUkSelectOptions[] { return (this.route.snapshot.data['enforcersRefData'] as IOpalFinesEnforcersRefData).refData.map((enforcer) => ({ value: enforcer.enforcer_id, @@ -45,6 +48,10 @@ export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBas })); } + /** + * Sets the options for the local justice area select dropdown. + * @returns An array of IGovUkSelectOptions representing the local justice areas. + */ private setLocalJusticeAreaOptions(): IGovUkSelectOptions[] { return (this.route.snapshot.data['localJusticeAreasRefData'] as IOpalFinesLocalJusticeAreaRefData).refData.map( (lja) => ({ @@ -54,6 +61,10 @@ export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBas ); } + /** + * Sets the options for the enforcement action select dropdown. + * @returns An array of IGovUkSelectOptions representing the enforcement actions. + */ private setEnforcementActionOptions(): IGovUkSelectOptions[] { return (this.route.snapshot.data['resultsRefData'] as IOpalFinesResultsRefData).refData.map((result) => ({ value: result.result_id, @@ -98,6 +109,9 @@ export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBas this.stateUnsavedChanges = unsavedChanges; } + /** + * Unsubscribes from all active subscriptions to prevent memory leaks when the component is destroyed. + */ public ngOnDestroy(): void { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); From f2e9155ed940c0cc4ac759cf97804a7e3ec34e01 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Fri, 20 Mar 2026 14:01:42 +0000 Subject: [PATCH 05/37] Updating payload for add enf override patch. --- .../fines-acc-defendant-details.component.ts | 22 +++++++++------- ...-enf-override-add-change.component.spec.ts | 8 +++++- ...s-acc-enf-override-add-change.component.ts | 2 +- .../fines-acc-payload.service.spec.ts | 2 ++ .../services/fines-acc-payload.service.ts | 26 ++++++++++++++++--- ...account-patch-payload-defaults.constant.ts | 6 +++++ ...-account-enforcement-override.interface.ts | 12 ++++++--- ...ines-update-defendant-account.interface.ts | 4 +-- .../opal-fines.service.spec.ts | 4 +++ 9 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts index a57def4d7c..c8d7f1b673 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; -import { distinctUntilChanged, EMPTY, map, merge, Observable, Subject, takeUntil, tap } from 'rxjs'; +import { distinctUntilChanged, EMPTY, map, merge, Observable, of, Subject, switchMap, takeUntil, tap } from 'rxjs'; // Services import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; import { PermissionsService } from '@hmcts/opal-frontend-common/services/permissions-service'; @@ -222,16 +222,18 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement case 'payment-terms': this.tabPaymentTerms$ = this.fetchTabData( this.opalFinesService.getDefendantAccountPaymentTermsLatest(account_id).pipe( - tap((data) => { - if (data.last_enforcement) { - this.opalFinesService - .getResult(data.last_enforcement) - .pipe(takeUntil(this.destroy$)) - .subscribe((result) => { + switchMap( + (data): Observable => + (data.last_enforcement + ? this.opalFinesService.getResult(data.last_enforcement) + : of(null) + ).pipe( + tap((result: IOpalFinesResultRefData | null) => { this.lastEnforcement = result; - }); - } - }), + }), + map((): IOpalFinesAccountDefendantDetailsPaymentTermsLatest => data), + ), + ), ), ); break; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts index b7aec31925..dcc5c001ed 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts @@ -140,7 +140,13 @@ describe('FinesAccEnfOverrideAddChangeComponent', () => { '1', '2002', ); - expect(routerNavigateSpy).toHaveBeenCalledWith(FINES_ACC_DEFENDANT_ROUTING_PATHS.children.details); + expect(routerNavigateSpy).toHaveBeenCalledWith( + FINES_ACC_DEFENDANT_ROUTING_PATHS.children.details, + false, + undefined, + null, + 'enforcement', + ); }); it('should scroll to top on submit error', () => { diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts index 65849c8ea0..4f4ba7e2eb 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts @@ -96,7 +96,7 @@ export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBas ) .subscribe(() => { this.finesAccStore.setSuccessMessage('Enforcement override added'); - this.routerNavigate(this.finesDefendantRoutingPaths.children.details); + this.routerNavigate(this.finesDefendantRoutingPaths.children.details, false, undefined, null, 'enforcement'); }); } diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts index 69988df8bf..74a28b2b30 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts @@ -545,6 +545,7 @@ describe('FinesAccPayloadService', () => { free_text_note_2: 'Updated note 2', free_text_note_3: 'Updated note 3', }, + enforcement_override: null, }); }); @@ -565,6 +566,7 @@ describe('FinesAccPayloadService', () => { free_text_note_2: null, free_text_note_3: null, }, + enforcement_override: null, }); }); diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts index b209eecbd5..06959f87ef 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts @@ -26,6 +26,7 @@ import { buildPaymentTermsAmendPayloadUtil } from './utils/fines-acc-payload-bui import { buildAccountPartyFromFormState } from './utils/fines-acc-payload-build-defendant-data.utils'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; import { IFinesAccEnfOverrideAddChangeFormState } from '../fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from '../../services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant'; @Injectable({ providedIn: 'root', @@ -162,6 +163,7 @@ export class FinesAccPayloadService { */ public buildCommentsFormPayload(formState: IFinesAccAddCommentsFormState): IOpalFinesUpdateDefendantAccountPayload { return { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: formState.facc_add_comment || null, free_text_note_1: formState.facc_add_free_text_1 || null, @@ -181,11 +183,29 @@ export class FinesAccPayloadService { public buildEnforcementOverrideFormPayload( formState: IFinesAccEnfOverrideAddChangeFormState, ): IOpalFinesUpdateDefendantAccountPayload { + const { + fenf_account_enforcement_action: enforcement_override_result_id, + fenf_account_enforcement_enforcer: enforcer_id, + fenf_account_enforcement_lja: lja_id, + } = formState; return { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, enforcement_override: { - enf_override_result_id: formState.fenf_account_enforcement_action || null, - enf_override_enforcer_id: formState.fenf_account_enforcement_enforcer || null, - enf_override_tfo_lja_id: formState.fenf_account_enforcement_lja || null, + enforcement_override_result: enforcement_override_result_id + ? { + enforcement_override_result_id, + } + : null, + enforcer: enforcer_id + ? { + enforcer_id, + } + : null, + lja: lja_id + ? { + lja_id, + } + : null, }, }; } diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts new file mode 100644 index 0000000000..b9fb0ce6fd --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts @@ -0,0 +1,6 @@ +import { IOpalFinesUpdateDefendantAccountPayload } from '../interfaces/opal-fines-update-defendant-account.interface'; + +export const OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS: IOpalFinesUpdateDefendantAccountPayload = { + comment_and_notes: null, + enforcement_override: null, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts index dad8f2b9f9..86d105cc25 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts @@ -1,5 +1,11 @@ export interface IOpalFinesUpdateDefendantAccountEnforcementOverride { - enf_override_result_id: string | null; - enf_override_enforcer_id: string | null; - enf_override_tfo_lja_id: string | null; + enforcement_override_result: { + enforcement_override_result_id: string; + } | null; + enforcer: { + enforcer_id: string; + } | null; + lja: { + lja_id: string; + } | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts index c0ae20b350..09d92d1f52 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts @@ -5,6 +5,6 @@ import { IOpalFinesUpdateDefendantAccountEnforcementOverride } from './opal-fine * Interface for the payload to update a defendant account *Subject to change */ export interface IOpalFinesUpdateDefendantAccountPayload { - comment_and_notes?: IOpalFinesUpdateDefendantAccountCommentsNotes; - enforcement_override?: IOpalFinesUpdateDefendantAccountEnforcementOverride; + comment_and_notes: IOpalFinesUpdateDefendantAccountCommentsNotes | null; + enforcement_override: IOpalFinesUpdateDefendantAccountEnforcementOverride | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index efdf46608d..d6fc032ea5 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -56,6 +56,8 @@ import { FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK } from '../../fines-acc/fi import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { FINES_ACC_DEFENDANT_DETAILS_TABS } from '../../fines-acc/fines-acc-defendant-details/constants/fines-acc-defendant-details-tabs.constant'; +import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from './constants/opal-fines-defendant-account-patch-payload-defaults.constant'; describe('OpalFines', () => { let service: OpalFines; @@ -1216,6 +1218,7 @@ describe('OpalFines', () => { it('should return a mock response for patching defendant account', () => { const accountId = 123456; const updatePayload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: 'Updated comment', free_text_note_1: 'Updated note 1', @@ -1237,6 +1240,7 @@ describe('OpalFines', () => { it('should handle different payload values in mock response for patching defendant account', () => { const accountId = 789012; const updatePayload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 5, comment_and_notes: { account_comment: 'Different comment', From d8c1bef0497387fbd4b634f67b06e739919079e2 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 23 Mar 2026 09:44:19 +0000 Subject: [PATCH 06/37] Adding access modifier to method --- .../fines-acc-defendant-details-enforcement-tab.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts index ab43aed025..a61cfb7475 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts @@ -44,7 +44,7 @@ export class FinesAccDefendantDetailsEnforcementTab { * Emits an event to add an enforcement override if the user has the necessary permissions and there is no existing enforcement override result. * @returns void */ - handleAddEnforcementOverride(): void { + public handleAddEnforcementOverride(): void { if ( this.hasAccountMaintenancePermission && !this.tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id From e60dbf177e2db0e70ef3903eee9bafe04964916b Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 23 Mar 2026 13:02:17 +0000 Subject: [PATCH 07/37] Updating link and mock data. --- ...ant-details-enforcement-tab.component.html | 7 +--- ...-details-enforcement-tab.component.spec.ts | 4 ++- ...ndant-details-enforcement-tab.component.ts | 3 +- .../mocks/opal-fines-enforcer.mock.ts | 8 +++++ .../opal-fines.service.spec.ts | 32 +++---------------- 5 files changed, 18 insertions(+), 36 deletions(-) create mode 100644 src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html index 9007489ca1..0e4e06973f 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html @@ -340,12 +340,7 @@

Actions

!tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id ) {

- Add enforcement override

diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts index 055ebf37aa..bcace27490 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts @@ -24,9 +24,11 @@ describe('FinesAccDefendantDetailsEnforcementTab', () => { it('should handleAddEnforcementOverride when add enforcement override button is clicked', () => { const eventEmitterSpy = vi.spyOn(component.addEnforcementOverride, 'emit'); + const event = { preventDefault: vi.fn() } as unknown as Event; component.hasAccountMaintenancePermission = true; component.tabData.enforcement_override = null; // Ensure there is no existing enforcement override result - component.handleAddEnforcementOverride(); + component.handleAddEnforcementOverride(event); + expect(event.preventDefault).toHaveBeenCalled(); expect(eventEmitterSpy).toHaveBeenCalled(); }); }); diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts index a61cfb7475..19ec837685 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts @@ -44,7 +44,8 @@ export class FinesAccDefendantDetailsEnforcementTab { * Emits an event to add an enforcement override if the user has the necessary permissions and there is no existing enforcement override result. * @returns void */ - public handleAddEnforcementOverride(): void { + public handleAddEnforcementOverride(event: Event): void { + event.preventDefault(); if ( this.hasAccountMaintenancePermission && !this.tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id diff --git a/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts new file mode 100644 index 0000000000..2bdd27ad3b --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts @@ -0,0 +1,8 @@ +import { IOpalFinesEnforcer } from '../interfaces/opal-fines-enforcer.interface'; + +export const OPAL_FINES_ENFORCER_MOCK: IOpalFinesEnforcer = { + enforcer_id: 101, + enforcer_code: 202, + name: 'Enforcer One', + name_cy: null, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index d6fc032ea5..8124861d17 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -58,6 +58,7 @@ import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { FINES_ACC_DEFENDANT_DETAILS_TABS } from '../../fines-acc/fines-acc-defendant-details/constants/fines-acc-defendant-details-tabs.constant'; import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from './constants/opal-fines-defendant-account-patch-payload-defaults.constant'; +import { OPAL_FINES_ENFORCER_MOCK } from './mocks/opal-fines-enforcer.mock'; describe('OpalFines', () => { let service: OpalFines; @@ -282,14 +283,7 @@ describe('OpalFines', () => { it('should send a GET request to enforcers ref data API', () => { const mockEnforcers: IOpalFinesEnforcersRefData = { count: 1, - refData: [ - { - enforcer_id: 101, - enforcer_code: 202, - name: 'Enforcer One', - name_cy: null, - }, - ], + refData: [OPAL_FINES_ENFORCER_MOCK], }; const expectedUrl = `${OPAL_FINES_PATHS.enforcersRefData}`; @@ -305,14 +299,7 @@ describe('OpalFines', () => { it('should return cached response for enforcers ref data', () => { const mockEnforcers: IOpalFinesEnforcersRefData = { count: 1, - refData: [ - { - enforcer_id: 101, - enforcer_code: 202, - name: 'Enforcer One', - name_cy: null, - }, - ], + refData: [OPAL_FINES_ENFORCER_MOCK], }; const expectedUrl = `${OPAL_FINES_PATHS.enforcersRefData}`; @@ -332,12 +319,7 @@ describe('OpalFines', () => { }); it('should return the enforcer name and code in a pretty format', () => { - const enforcer: IOpalFinesEnforcer = { - enforcer_id: 101, - enforcer_code: 202, - name: 'Enforcer One', - name_cy: null, - }; + const enforcer: IOpalFinesEnforcer = OPAL_FINES_ENFORCER_MOCK; const result = service.getEnforcerPrettyName(enforcer); @@ -928,9 +910,6 @@ describe('OpalFines', () => { }); it('should getDefendantAccountImpositionsTabData', () => { - // const account_id: number = 77; - // const business_unit_id: string = '12'; - // const business_unit_user_id: string | null = '12'; const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_IMPOSITIONS_TAB_REF_DATA_MOCK; service.getDefendantAccountImpositionsTabData().subscribe((response) => { @@ -974,9 +953,6 @@ describe('OpalFines', () => { }); it('should getDefendantAccountHistoryAndNotesTabData', () => { - // const account_id: number = 77; - // const business_unit_id: string = '12'; - // const business_unit_user_id: string | null = '12'; const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_HISTORY_AND_NOTES_TAB_REF_DATA_MOCK; service.getDefendantAccountHistoryAndNotesTabData().subscribe((response) => { From ebabd2f6f4e0d067a1e38e86570cd182c3b86ecc Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 23 Mar 2026 13:03:34 +0000 Subject: [PATCH 08/37] Fixing lint issue --- .../fines/services/opal-fines-service/opal-fines.service.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index 8124861d17..13f8299858 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -56,7 +56,6 @@ import { FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK } from '../../fines-acc/fi import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { FINES_ACC_DEFENDANT_DETAILS_TABS } from '../../fines-acc/fines-acc-defendant-details/constants/fines-acc-defendant-details-tabs.constant'; import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from './constants/opal-fines-defendant-account-patch-payload-defaults.constant'; import { OPAL_FINES_ENFORCER_MOCK } from './mocks/opal-fines-enforcer.mock'; From 260ac3cca708e36c854f5ffde6c537ee5ef7ad0c Mon Sep 17 00:00:00 2001 From: Alfred-Gillingham2 Date: Thu, 26 Mar 2026 13:56:35 +0000 Subject: [PATCH 09/37] WIP - Enforcement Override --- ...addEnforcementOverrideParentGuardian.cy.ts | 202 ++++++++++++++++++ .../add_enforcement_override_elements.ts | 12 ++ 2 files changed, 214 insertions(+) create mode 100644 cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts create mode 100644 cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts new file mode 100644 index 0000000000..c51353528d --- /dev/null +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts @@ -0,0 +1,202 @@ +import { mount } from 'cypress/angular'; +import { of } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { FinesAccEnfOverrideAddChangeComponent } from 'src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component'; +import { FinesAccEnfOverrideAddChangeFormComponent } from 'src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component'; +import { FinesAccountStore } from 'src/app/flows/fines/fines-acc/stores/fines-acc.store'; +import { FinesAccPayloadService } from 'src/app/flows/fines/fines-acc/services/fines-acc-payload.service'; +import { OpalFines } from 'src/app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service'; +import { DOM_ELEMENTS } from './constants/add_enforcement_override_elements'; + +const mockRoute: ActivatedRoute = { + snapshot: { + data: { + title: 'Add enforcement override', + enforcersRefData: { + refData: [ + { enforcer_id: 'E1', enforcer_code: 'EC1', name: 'Enforcer One' }, + { enforcer_id: 'E2', enforcer_code: 'EC2', name: 'Enforcer Two' }, + ], + }, + localJusticeAreasRefData: { + refData: [ + { local_justice_area_id: 'L1', name: 'LJA One' }, + { local_justice_area_id: 'L2', name: 'LJA Two' }, + ], + }, + resultsRefData: { + refData: [ + { result_id: 'R1', result_title: 'Result One', requires_enforcer: true }, + { result_id: 'R2', result_title: 'Result Two' }, + ], + }, + }, + }, +} as any; + +const mockStore = { + party_name: () => 'Test Person', + getAccountNumber: () => '123456', + account_id: () => 1001, + base_version: () => '1', + business_unit_id: () => '2002', + setSuccessMessage: () => {}, +}; + +const mockPayloadService = { + buildEnforcementOverrideFormPayload: () => ({ enforcement_override: {} }), +}; + +const mockOpalFinesService = { + getEnforcerPrettyName: (e: { name: string; enforcer_code: string }) => `${e.name} (${e.enforcer_code})`, + + getLocalJusticeAreaPrettyName: (lja: { name: string; local_justice_area_id: string }) => + `${lja.name} (${lja.local_justice_area_id})`, + + getResultPrettyName: (r: { result_title: string; result_id: string }) => `${r.result_title} (${r.result_id})`, + + getResult: () => of({ requires_enforcer: false, requires_lja: false }), + patchDefendantAccount: () => of({}), +}; + +const mockUtilsService = { + scrollToTop: () => {}, +}; + +const setup = () => { + return mount(FinesAccEnfOverrideAddChangeComponent, { + providers: [ + { provide: ActivatedRoute, useValue: mockRoute }, + { provide: FinesAccountStore, useValue: mockStore }, + { provide: FinesAccPayloadService, useValue: mockPayloadService }, + { provide: OpalFines, useValue: mockOpalFinesService }, + { provide: UtilsService, useValue: mockUtilsService }, + ], + }); +}; + +describe('Add Enforcement Override', () => { + it('AC1a, AC1b. Should render the form with title', () => { + setup(); + + cy.get(DOM_ELEMENTS.title).should('contain.text', 'Test Person - 123456'); + cy.get(DOM_ELEMENTS.title).should('contain.text', 'Add enforcement override'); + }); + + it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', () => { + setup(); + + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).click(); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('ABDC').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('AEOC').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTU').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('CLAMPO').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('CWN').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('DW').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('FSN').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('MAN').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('NBWT').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('REGF').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('SUMA').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(DOM_ELEMENTS.addOverrideButton).should('exist'); + cy.get(DOM_ELEMENTS.cancelLink).should('exist'); + }); + + it('AC2. Enforcer dropdown for valid override', () => { + setup(); + + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('AB'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('ABDC').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('AEOC').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTU').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('CLAMPO').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('CWN').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('DW').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('FSN').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('MAN').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('NBWT').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('REGF').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('SUMA').should('not.exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('not.exist'); + + cy.get(DOM_ELEMENTS.dropdownOptions).contains('ABDC').click(); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'ABDC'); + + cy.get(DOM_ELEMENTS.enforcerDropdown).should('exist'); + cy.get(DOM_ELEMENTS.enforcerDropdown).click(); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('').should('exist'); + }); + + it('AC3. LJA dropdown for valid override', () => { + setup(); + + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('TFO'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').click(); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'TFOOUT'); + cy.get(DOM_ELEMENTS.localJusticeAreaDropdown).should('exist'); + cy.get(DOM_ELEMENTS.localJusticeAreaDropdown).click(); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('').should('exist'); + }); + + it('AC4a. Error when no enforcement override is selected', () => { + setup(); + + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); + cy.get(DOM_ELEMENTS.addOverrideButton).click(); + cy.get(DOM_ELEMENTS.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select an enforcement override'); + }); + + it('AC4b. Error when no enforcer is selected', () => { + setup(); + + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('BW'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTU').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').click(); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'BWTD'); + cy.get(DOM_ELEMENTS.enforcerDropdown).should('exist'); + + cy.get(DOM_ELEMENTS.addOverrideButton).click(); + cy.get(DOM_ELEMENTS.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select an enforcer'); + }); + + it('AC4c. Error when no LJA is selected', () => { + setup(); + + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('TFO'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('exist'); + cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').click(); + cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'TFOOUT'); + cy.get(DOM_ELEMENTS.localJusticeAreaDropdown).should('exist'); + + cy.get(DOM_ELEMENTS.addOverrideButton).click(); + cy.get(DOM_ELEMENTS.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select a Local Justice Area'); + }); + + // TODO: AC5 Validation Passes + // TODO: AC6 Cancel link behaviour and route guard +}); diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts new file mode 100644 index 0000000000..f1f51ec529 --- /dev/null +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts @@ -0,0 +1,12 @@ +export const DOM_ELEMENTS = { + // Page Elements + title: 'h1.govuk-heading-l', + subtitle: '.govuk-fieldset__legend--m', + enfOverrideDropdown: '[name="fenf_account_enforcement_action-autocomplete"]', + enforcerDropdown: '[name="fenf_account_enforcement_enforcer-autocomplete"]', + localJusticeAreaDropdown: '[name="fenf_account_enforcement_lja-autocomplete"]', + dropdownOptions: '.autocomplete__option', + addOverrideButton: '#submitForm', + cancelLink: '.govuk-link', + errorSummary: '.govuk-error-summary__title', +}; From 7db5bf7f2461ec8a52f9030e0dae25cf74996b51 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Thu, 26 Mar 2026 15:48:25 +0000 Subject: [PATCH 10/37] Updating error message text. --- .../fines-acc-enf-override-add-change-field-errors.constant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts index 71cb05a243..eaf803e257 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts @@ -3,7 +3,7 @@ import { IFinesAccEnfOverrideAddChangeFieldErrors } from '../interfaces/fines-ac export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS: IFinesAccEnfOverrideAddChangeFieldErrors = { fenf_account_enforcement_action: { required: { - message: `Select an enforcement action`, + message: `Select an enforcement override`, priority: 1, }, }, From 07b518376fe5832995a4eaf75b2f7f554bbd8d2e Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Thu, 26 Mar 2026 16:57:30 +0000 Subject: [PATCH 11/37] Switching to enforcement tab on account summary when cancelling add enforcement override form. --- .../fines-acc-enf-override-add-change-form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html index 7f010a8924..fb6dfd0c14 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html @@ -55,7 +55,7 @@
From 31344a151005ab23a7fbcb2e0d8a9520cad4cdc0 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 14:32:52 +0000 Subject: [PATCH 12/37] Update shared locators for enforcement --- .../AccountEnquiriesEnforcement.cy.ts | 2 +- ...quiry.enforcement-override-add.locators.ts | 21 +++++++++ .../account.enquiry.enforcement.locators.ts | 46 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts create mode 100644 cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts diff --git a/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts b/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts index 6915e9be30..0b7d7c6344 100644 --- a/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts +++ b/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts @@ -6,7 +6,7 @@ import { } from '../../CommonIntercepts/CommonUserState.mocks'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; -import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENFORCEMENT_STATUS_TAB } from './constants/account_enquiry_enforcement_status_elements'; +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENFORCEMENT_STATUS_TAB } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; import { interceptDefendantHeader, interceptEnforcementStatus } from './intercept/defendantAccountIntercepts'; import { interceptAuthenticatedUser, interceptUserState } from 'cypress/component/CommonIntercepts/CommonIntercepts'; import { IComponentProperties } from './setup/setupComponent.interface'; diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts new file mode 100644 index 0000000000..9384617e2a --- /dev/null +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts @@ -0,0 +1,21 @@ +/** + * @file account.enquiry.enforcement-override-add.locators.ts + * @description + * Selector map for the **Account Enquiry – Add enforcement override** form. + * Covers page headings, autocomplete controls, actions, and validation summary. + * + * @remarks + * - Use these selectors in Cypress tests instead of local component constants. + * - The exported key names are preserved to keep migration of existing specs mechanical. + */ +export const DOM_ELEMENTS = { + title: 'h1.govuk-heading-l', + subtitle: '.govuk-fieldset__legend--m', + enfOverrideDropdown: '[name="fenf_account_enforcement_action-autocomplete"]', + enforcerDropdown: '[name="fenf_account_enforcement_enforcer-autocomplete"]', + localJusticeAreaDropdown: '[name="fenf_account_enforcement_lja-autocomplete"]', + dropdownOptions: '.autocomplete__option', + addOverrideButton: '#submitForm', + cancelLink: '.govuk-link', + errorSummary: '.govuk-error-summary__title', +} as const; diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts new file mode 100644 index 0000000000..5520db58ef --- /dev/null +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts @@ -0,0 +1,46 @@ +/** + * @file account.enquiry.enforcement.locators.ts + * @description + * Selector map for the **Account Enquiry – Enforcement** tab. + * Covers page structure, summary cards, action links, and enforcement detail rows. + * + * @remarks + * - Use these selectors in Cypress component and e2e tests to avoid local duplication. + * - This map preserves the legacy selector keys used by the existing enforcement spec. + */ +export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { + headingWithCaption: 'opal-lib-govuk-heading-with-caption', + headingName: 'h1.govuk-heading-l', + pageHeader: 'opal-lib-custom-page-header', + headerLabel: '[opal-lib-custom-account-information-item-label]', + headerValue: '[opal-lib-custom-account-information-item-value]', + + addNoteButton: 'button[id$="addAccountNote"]', + + summaryMetricBar: 'opal-lib-custom-summary-metric-bar', + accountInfo: 'opal-lib-custom-account-information', + parentGuardianTag: '#status', + + tabName: '[subnavitemid="enforcement-tab"] > .moj-sub-navigation__link', + + enforcementStatusLink: '.govuk-link', + detailsLink: '.govuk-details__summary-text', + + tableTitle: '.govuk-summary-card__title', + collectionOrderStatus: '#enforcementOverviewDetailsCollection_order_statusKey', + daysInDefault: '#enforcementOverviewDetailsDays_in_defaultKey', + enforcementCourt: '#enforcementOverviewDetailsEnforcement_courtKey', + enforcementAction: '#lastEnforcementActionDetailsEnforcement_actionKey', + reason: '#lastEnforcementActionDetailsReasonKey', + lastEnfEnforcer: '#lastEnforcementActionDetailsEnforcerKey', + warrantNumber: '#lastEnforcementActionDetailsWarrant_numberKey', + dateAdded: '#lastEnforcementActionDetailsDate_addedKey', + enforcementOverride: '#enforcementOverrideDetailsEnforcement_overrideKey', + enfOverrideEnforcer: '#enforcementOverrideDetailsEnforcerKey', + localJusticeArea: '#enforcementOverrideDetailsLocal_justice_areaKey', + + detailsDaysInDefault: '[id="enforcementActionDetailsDays in defaultKey"]', + detailsReason: '#enforcementActionDetailsReasonKey', + + actionsColumnHeader: '.govuk-grid-column-one-third > .govuk-\\!-margin-bottom-2', +} as const; From 9433c2ad54faf5a26753a0a91cefdd0808068d2e Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 16:01:57 +0000 Subject: [PATCH 13/37] fix: correct constant name for account enquiry enforcement status elements --- .../constants/account_enquiry_enforcement_status_elements.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts b/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts index 3f39b58cdc..80fbc9f467 100644 --- a/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts +++ b/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts @@ -1,6 +1,6 @@ // Stable selectors / visible text hooks for the Defendant Details page. -export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { +export const accACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { headingWithCaption: 'opal-lib-govuk-heading-with-caption', headingName: 'h1.govuk-heading-l', pageHeader: 'opal-lib-custom-page-header', From 3231b3ed0c4bc14d497806c741becbfd06e4da31 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 16:02:13 +0000 Subject: [PATCH 14/37] feat: add intercept functions for enforcement override results and enforcers --- .../CommonIntercepts/CommonIntercept.mocks.ts | 1023 +++++++++++++++++ .../CommonIntercepts/CommonIntercepts.ts | 49 +- .../intercept/defendantAccountIntercepts.ts | 9 + 3 files changed, 1080 insertions(+), 1 deletion(-) diff --git a/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts b/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts index 2dff882693..465309e1ec 100644 --- a/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts +++ b/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts @@ -1,3 +1,4 @@ +import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; import { IOpalFinesCourtRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-court-ref-data.interface'; import { IOpalFinesLocalJusticeAreaRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; import { IOpalFinesMajorCreditorRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-major-creditor-ref-data.interface'; @@ -448,6 +449,204 @@ export const OPAL_FINES_RESULT_REF_DATA_MOCK: IOpalFinesResultRefData[] = [ requires_lja: false, manual_enforcement: false, }, + { + result_id: 'ABDC', + result_title: 'Application made for Benefit Deductions', + result_title_cy: 'Cais am dynnu arian o fudd-daliadau', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: true, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, LIVE UC or AMEND UC +400.00" },{ "name":"enforcer", "prompt":"Job Center Plus code (Enforcer code)", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'AEOC', + result_title: 'Attachment of Earnings Order - with Collection Order', + result_title_cy: 'Gorchymyn atafaelu enillion - cyfrif dirwy', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: true, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, include employer name" }]', + }, + { + result_id: 'BWTD', + result_title: 'Bail Warrant - dated', + result_title_cy: 'Gwarant Mechniaeth - Gyda dyddiad', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: true, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"hearingdate", "prompt":"Adjourned to", "type":"date", "min":1, "language_dependent":false, "hint":"For example, 31/01/2023" },{ "name":"courtcode", "prompt":"Court code", "type":"integer", "min":1, "max":999, "language_dependent":false },{ "name":"enforcer", "prompt":"Warrant enforcer (Enforcer code)", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'BWTU', + result_title: 'Bail Warrant - undated', + result_title_cy: 'Gwarant Mechniaeth - Heb Ddyddiad', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"baildirection", "prompt":"Date and time (Bail direction)", "type":"text", "min":0, "max":500, "language_dependent":false, "hint":"As ordered in court. For example, 31/01/2023 and 14:30" },{ "name":"courtdetails", "prompt":"Court details", "type":"text", "min":1, "max":100, "language_dependent":false },{ "name":"enforcer", "prompt":"Enforcer code", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'CLAMPO', + result_title: 'Clamping Order', + result_title_cy: '', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, vehicle details held on account" },{ "name":"effectivedate", "prompt":"Effective from date", "type":"date", "language_dependent":true, "hint":"For example, 31/01/2023" },{ "name":"enforcer", "prompt":"Clamping contractor code (Enforcer code)", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'CWN', + result_title: 'Committal Warrant Notice', + result_title_cy: 'Rhybudd o Warant Traddodi', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: true, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, failed to comply with suspended committal" },{ "name":"hearingdate", "prompt":"Adjourned to", "type":"date", "min":1, "language_dependent":false, "hint":"For example, 31/01/2023" },{ "name":"courtcode", "prompt":"Court code", "type":"integer", "min":1, "max":999, "language_dependent":false }]', + }, { result_id: 'DW', result_title: 'Warrant of Control', @@ -481,8 +680,328 @@ export const OPAL_FINES_RESULT_REF_DATA_MOCK: IOpalFinesResultRefData[] = [ result_parameters: '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"enforcer", "prompt":"Enforcer code", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', }, + { + result_id: 'MAN', + result_title: 'Manual Enforcement', + result_title_cy: '', + result_type: 'Result', + active: false, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: undefined, + }, + { + result_id: 'NBWT', + result_title: 'No bail warrant', + result_title_cy: 'Gwarant dim mechniaeth', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"courtdetails", "prompt":"Court details", "type":"text", "min":0, "max":100, "language_dependent":false },{ "name":"enforcer", "prompt":"Enforcer code", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'REGF', + result_title: 'Registration of Fine', + result_title_cy: 'Rhybudd Cofrestru', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: true, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: true, + prevent_payment_card: false, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" }]', + }, + { + result_id: 'SUMA', + result_title: 'Enforcement Summons (Automatic enforcement only)', + result_title_cy: 'Gwŷs Gorfodi', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: true, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" }]', + }, + { + result_id: 'TFOOUT', + result_title: 'Transfer of Fine Order to a Court in England or Wales', + result_title_cy: 'Trosglwyddo Gorchymyn Dirwy i Lys yng Nghymru neu Lloegr', + result_type: 'Action', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: false, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: true, + manual_enforcement: undefined, + result_parameters: undefined, + }, + { + result_id: 'FSN', + result_title: 'Further Steps Notice', + result_title_cy: 'Rhybudd o gamau pellach', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: false, + further_enforcement_warn: false, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: false, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" }]', + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + generates_warrant: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + }, ]; +export const OPAL_FINES_ENF_OVERRIDE_RESULT_REF_DATA_MOCK: IOpalFinesResultsRefData = { + count: 13, + refData: [ + { + result_id: 'ABDC', + result_title: 'Application made for Benefit Deductions', + result_title_cy: 'Cais am dynnu arian o fudd-daliadau', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + enforcement_override_result: true, + }, + { + result_id: 'AEOC', + result_title: 'Attachment of Earnings Order - with Collection Order', + result_title_cy: 'Gorchymyn atafaelu enillion - cyfrif dirwy', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'BWTD', + result_title: 'Bail Warrant - dated', + result_title_cy: 'Gwarant Mechniaeth - Gyda dyddiad', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'BWTU', + result_title: 'Bail Warrant - undated', + result_title_cy: 'Gwarant Mechniaeth - Heb Ddyddiad', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'CLAMPO', + result_title: 'Clamping Order', + result_title_cy: null, + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'CWN', + result_title: 'Committal Warrant Notice', + result_title_cy: 'Rhybudd o Warant Traddodi', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'DW', + result_title: 'Warrant of Control', + result_title_cy: 'Gwarant Atafaelu', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'MAN', + result_title: 'Manual Enforcement', + result_title_cy: null, + active: false, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'NBWT', + result_title: 'No bail warrant', + result_title_cy: 'Gwarant dim mechniaeth', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'REGF', + result_title: 'Registration of Fine', + result_title_cy: 'Rhybudd Cofrestru', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'SUMA', + result_title: 'Enforcement Summons (Automatic enforcement only)', + result_title_cy: 'Gwŷs Gorfodi', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'TFOOUT', + result_title: 'Transfer of Fine Order to a Court in England or Wales', + result_title_cy: 'Trosglwyddo Gorchymyn Dirwy i Lys yng Nghymru neu Lloegr', + active: true, + result_type: 'Action', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'FSN', + result_title: 'Further Steps Notice', + result_title_cy: 'Rhybudd o gamau pellach', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + ], +}; + export const OPAL_FINES_MAJOR_CREDITOR_REF_DATA_MOCK: IOpalFinesMajorCreditorRefData = { count: 4, refData: [ @@ -659,3 +1178,507 @@ export const OPAL_FINES_PROSECUTOR_REF_DATA_MOCK: IOpalFinesProsecutorRefData = }, ], }; + +export const OPAL_FINES_ENFORCER_REF_DATA_MOCK: IOpalFinesEnforcersRefData = { + count: 83, + refData: [ + { + enforcer_id: 780000000002, + enforcer_code: 3, + name: '00000', + name_cy: null, + }, + { + enforcer_id: 650000000001, + enforcer_code: 840, + name: 'ATCMTest', + name_cy: null, + }, + { + enforcer_id: 470000000001, + enforcer_code: 101, + name: 'BR.Enfone', + name_cy: null, + }, + { + enforcer_id: 470000000003, + enforcer_code: 103, + name: 'BR.Enfthree', + name_cy: null, + }, + { + enforcer_id: 470000000002, + enforcer_code: 102, + name: 'BR.Enftwo', + name_cy: null, + }, + { + enforcer_id: 50000000001, + enforcer_code: 101, + name: 'Cam.Enfone', + name_cy: null, + }, + { + enforcer_id: 50000000003, + enforcer_code: 103, + name: 'Cam.Enfthree', + name_cy: null, + }, + { + enforcer_id: 50000000002, + enforcer_code: 102, + name: 'Cam.Enftwo', + name_cy: null, + }, + { + enforcer_id: 1300000000002, + enforcer_code: 1, + name: 'CIVILIAN ENFORCEMENT OFFICERS', + name_cy: null, + }, + { + enforcer_id: 600000000001, + enforcer_code: 101, + name: 'Dyfed Enforcer 1', + name_cy: null, + }, + { + enforcer_id: 1060000000041, + enforcer_code: 801, + name: 'EnfAE01', + name_cy: 'EnfAE01', + }, + { + enforcer_id: 1060000000043, + enforcer_code: 803, + name: 'EnfAE02', + name_cy: 'EnfAE02', + }, + { + enforcer_id: 1060000000045, + enforcer_code: 802, + name: 'EnfAE03', + name_cy: 'EnfAE03', + }, + { + enforcer_id: 1060000000047, + enforcer_code: 804, + name: 'EnfAE04', + name_cy: 'EnfAE04', + }, + { + enforcer_id: 1060000000049, + enforcer_code: 805, + name: 'EnfAE05', + name_cy: 'EnfAE05', + }, + { + enforcer_id: 780000000001, + enforcer_code: 2, + name: 'Enfone Add3', + name_cy: null, + }, + { + enforcer_id: 600000000021, + enforcer_code: 901, + name: 'Enforcement(Dy) - ABDC', + name_cy: null, + }, + { + enforcer_id: 600000000022, + enforcer_code: 902, + name: 'Enforcement(Dy) - BWTD', + name_cy: null, + }, + { + enforcer_id: 600000000023, + enforcer_code: 903, + name: 'Enforcement(Dy) - BWTU', + name_cy: null, + }, + { + enforcer_id: 600000000024, + enforcer_code: 904, + name: 'Enforcement(Dy) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 600000000025, + enforcer_code: 905, + name: 'Enforcement(Dy) - DW', + name_cy: null, + }, + { + enforcer_id: 600000000026, + enforcer_code: 906, + name: 'Enforcement(Dy) - NBWT', + name_cy: null, + }, + { + enforcer_id: 890000000041, + enforcer_code: 901, + name: 'Enforcement(Gw) - ABDC', + name_cy: null, + }, + { + enforcer_id: 890000000042, + enforcer_code: 902, + name: 'Enforcement(Gw) - BWTD', + name_cy: null, + }, + { + enforcer_id: 890000000043, + enforcer_code: 903, + name: 'Enforcement(Gw) - BWTU', + name_cy: null, + }, + { + enforcer_id: 890000000044, + enforcer_code: 904, + name: 'Enforcement(Gw) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 890000000045, + enforcer_code: 905, + name: 'Enforcement(Gw) - DW', + name_cy: null, + }, + { + enforcer_id: 890000000046, + enforcer_code: 906, + name: 'Enforcement(Gw) - NBWT', + name_cy: null, + }, + { + enforcer_id: 1060000000021, + enforcer_code: 901, + name: 'Enforcement(Nw) - ABDC', + name_cy: null, + }, + { + enforcer_id: 1060000000022, + enforcer_code: 902, + name: 'Enforcement(Nw) - BWTD', + name_cy: null, + }, + { + enforcer_id: 1060000000023, + enforcer_code: 903, + name: 'Enforcement(Nw) - BWTU', + name_cy: null, + }, + { + enforcer_id: 1060000000024, + enforcer_code: 904, + name: 'Enforcement(Nw) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 1060000000025, + enforcer_code: 905, + name: 'Enforcement(Nw) - DW', + name_cy: null, + }, + { + enforcer_id: 1060000000026, + enforcer_code: 906, + name: 'Enforcement(Nw) - NBWT', + name_cy: null, + }, + { + enforcer_id: 360000000041, + enforcer_code: 901, + name: 'Enforcement(SW-SMG) - ABDC', + name_cy: null, + }, + { + enforcer_id: 360000000042, + enforcer_code: 902, + name: 'Enforcement(SW-SMG) - BWTD', + name_cy: null, + }, + { + enforcer_id: 360000000043, + enforcer_code: 903, + name: 'Enforcement(SW-SMG) - BWTU', + name_cy: null, + }, + { + enforcer_id: 360000000044, + enforcer_code: 904, + name: 'Enforcement(SW-SMG) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 360000000045, + enforcer_code: 905, + name: 'Enforcement(SW-SMG) - DW', + name_cy: null, + }, + { + enforcer_id: 360000000046, + enforcer_code: 906, + name: 'Enforcement(SW-SMG) - NBWT', + name_cy: null, + }, + { + enforcer_id: 730000000021, + enforcer_code: 104, + name: 'EnforcerFour', + name_cy: null, + }, + { + enforcer_id: 800000000001, + enforcer_code: 101, + name: 'EnforcerOne', + name_cy: null, + }, + { + enforcer_id: 730000000001, + enforcer_code: 101, + name: 'EnforcerOne', + name_cy: null, + }, + { + enforcer_id: 800000000003, + enforcer_code: 103, + name: 'EnforcerThree', + name_cy: null, + }, + { + enforcer_id: 730000000003, + enforcer_code: 103, + name: 'EnforcerThree', + name_cy: null, + }, + { + enforcer_id: 800000000002, + enforcer_code: 102, + name: 'EnforcerTwo', + name_cy: null, + }, + { + enforcer_id: 730000000002, + enforcer_code: 102, + name: 'EnforcerTwo', + name_cy: null, + }, + { + enforcer_id: 820000000001, + enforcer_code: 101, + name: 'Gm.Enfone', + name_cy: null, + }, + { + enforcer_id: 820000000004, + enforcer_code: 1, + name: 'Gm.Enfone Add2', + name_cy: null, + }, + { + enforcer_id: 820000000003, + enforcer_code: 103, + name: 'Gm.Enfthree', + name_cy: null, + }, + { + enforcer_id: 820000000002, + enforcer_code: 102, + name: 'Gm.Enftwo', + name_cy: null, + }, + { + enforcer_id: 820000000005, + enforcer_code: 2, + name: 'Gm.Enftwo Add3', + name_cy: null, + }, + { + enforcer_id: 890000000001, + enforcer_code: 101, + name: 'Gwent.Enfone', + name_cy: null, + }, + { + enforcer_id: 890000000003, + enforcer_code: 103, + name: 'Gwent.Enfthree', + name_cy: null, + }, + { + enforcer_id: 890000000002, + enforcer_code: 102, + name: 'Gwent.Enftwo', + name_cy: null, + }, + { + enforcer_id: 300000000001, + enforcer_code: 101, + name: 'GY.Enfone', + name_cy: null, + }, + { + enforcer_id: 300000000003, + enforcer_code: 103, + name: 'GY.Enfthree', + name_cy: null, + }, + { + enforcer_id: 300000000002, + enforcer_code: 102, + name: 'GY.Enftwo', + name_cy: null, + }, + { + enforcer_id: 260000000042, + enforcer_code: 105, + name: 'Job Centre Hemel Hempstead', + name_cy: null, + }, + { + enforcer_id: 260000000041, + enforcer_code: 104, + name: 'Job Centre Watford', + name_cy: null, + }, + { + enforcer_id: 260000000021, + enforcer_code: 101, + name: 'NE.Enfone', + name_cy: null, + }, + { + enforcer_id: 260000000023, + enforcer_code: 103, + name: 'NE.Enfthree', + name_cy: null, + }, + { + enforcer_id: 260000000022, + enforcer_code: 102, + name: 'NE.Enftwo', + name_cy: null, + }, + { + enforcer_id: 1060000000001, + enforcer_code: 101, + name: 'North Wales Enforcer 1', + name_cy: null, + }, + { + enforcer_id: 1060000000002, + enforcer_code: 102, + name: 'North Wales Job Centre 1', + name_cy: null, + }, + { + enforcer_id: 890000000021, + enforcer_code: 1, + name: 'NW.Enfone Add2', + name_cy: null, + }, + { + enforcer_id: 890000000022, + enforcer_code: 2, + name: 'NW.Enftwo Add3', + name_cy: null, + }, + { + enforcer_id: 570000000001, + enforcer_code: 101, + name: 'PD.EnfOne', + name_cy: null, + }, + { + enforcer_id: 570000000003, + enforcer_code: 103, + name: 'PD.EnfThree', + name_cy: null, + }, + { + enforcer_id: 570000000002, + enforcer_code: 102, + name: 'PD.EnfTwo', + name_cy: null, + }, + { + enforcer_id: 1030000000001, + enforcer_code: 101, + name: 'PE Enfone', + name_cy: null, + }, + { + enforcer_id: 1030000000003, + enforcer_code: 103, + name: 'PE.Enfthree', + name_cy: null, + }, + { + enforcer_id: 1030000000002, + enforcer_code: 102, + name: 'PE.Enftwo', + name_cy: null, + }, + { + enforcer_id: 260000000043, + enforcer_code: 106, + name: 'Police Hertfordshire', + name_cy: null, + }, + { + enforcer_id: 360000000001, + enforcer_code: 101, + name: 'SouthWales.Enfone', + name_cy: null, + }, + { + enforcer_id: 360000000003, + enforcer_code: 103, + name: 'SouthWales.Enfthree', + name_cy: null, + }, + { + enforcer_id: 360000000002, + enforcer_code: 102, + name: 'SouthWales.Enftwo', + name_cy: null, + }, + { + enforcer_id: 360000000021, + enforcer_code: 104, + name: 'South Wales Job Centre', + name_cy: null, + }, + { + enforcer_id: 770000000001, + enforcer_code: 1, + name: 'The Bailiffs', + name_cy: null, + }, + { + enforcer_id: 770000000003, + enforcer_code: 3, + name: 'The DWP', + name_cy: null, + }, + { + enforcer_id: 260000000001, + enforcer_code: 123, + name: 'The enforcers name', + name_cy: null, + }, + { + enforcer_id: 770000000002, + enforcer_code: 2, + name: 'The police', + name_cy: null, + }, + { + enforcer_id: 780000000021, + enforcer_code: 101, + name: 'Warrant enforcer', + name_cy: null, + }, + ], +}; diff --git a/cypress/component/CommonIntercepts/CommonIntercepts.ts b/cypress/component/CommonIntercepts/CommonIntercepts.ts index 4122205586..c232b385f6 100644 --- a/cypress/component/CommonIntercepts/CommonIntercepts.ts +++ b/cypress/component/CommonIntercepts/CommonIntercepts.ts @@ -8,6 +8,8 @@ import { OPAL_OFFENCE_BY_ID_MOCK, OPAL_FINES_OFFENCES_REF_DATA_MOCK, OPAL_FINES_RESULT_REF_DATA_MOCK, + OPAL_FINES_ENFORCER_REF_DATA_MOCK, + OPAL_FINES_ENF_OVERRIDE_RESULT_REF_DATA_MOCK, } from './CommonIntercept.mocks'; /** @@ -75,7 +77,7 @@ export function interceptOffencesById(offenceId: number) { * Intercepts the GET request to the `/sso/authenticated` endpoint and mocks the response * to indicate that the user is authenticated. * - * @returns Cypress.Chainable - The Cypress chainable object for further chaining. + * @returns Cypress.Chainable<> - The Cypress chainable object for further chaining. * * @example * // Usage in a Cypress test @@ -216,3 +218,48 @@ export function interceptResultByCode(resultCode: string) { ) .as('getResultByCode'); } + +/** + * + * @returns {Cypress.Chainable} A Cypress chainable that represents the intercepted request, + * aliased as 'getEnforcementOverrideResults' for later reference in tests. + * + * @example + * ```typescript + * interceptEnforcementOverrideResults(); + * cy.wait('@getEnforcementOverrideResults'); + * ``` + */ + +export function interceptEnforcementOverrideResults() { + const overrideResults = OPAL_FINES_ENF_OVERRIDE_RESULT_REF_DATA_MOCK; + return cy + .intercept('GET', `/opal-fines-service/results?enforcement_override=true`, { + statusCode: 200, + body: { count: overrideResults.refData.length, refData: overrideResults.refData }, + }) + .as('getEnforcementOverrideResults'); +} + +/** + * Intercepts GET requests to the enforcers endpoint and returns mock enforcer data. + * + * @returns {Cypress.Chainable} A Cypress chainable that represents the intercepted request, + * aliased as 'getEnforcersByBU' for later reference in tests. + * + * @example + * ```typescript + * interceptEnforcers(); + * cy.get('[data-testid="enforcer-select"]').click(); + * cy.wait('@getEnforcersByBU'); + * ``` + */ +export function interceptEnforcers() { + const enforcers = OPAL_FINES_ENFORCER_REF_DATA_MOCK; + return cy + .intercept('GET', `/opal-fines-service/enforcers`, { + statusCode: 200, + body: enforcers, + }) + .as('getEnforcersByBU'); +} diff --git a/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts b/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts index d1a1026c9d..c845b86d30 100644 --- a/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts +++ b/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts @@ -237,3 +237,12 @@ export function interceptEnforcementStatus( }) .as('getEnforcementStatus'); } + +export function interceptPatchDefendantAccount() { + return cy + .intercept('PATCH', `/opal-fines-service/defendant-accounts/*`, { + statusCode: 200, + body: {}, + }) + .as('patchDefendantAccount'); +} From 93674c8242fa6b8118a2eabcd65547b92bed0878 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 16:02:22 +0000 Subject: [PATCH 15/37] feat: add locator for "Add enforcement override" link in account enquiry enforcement status elements --- .../account-enquiry/account.enquiry.enforcement.locators.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts index 5520db58ef..5fdb0145ce 100644 --- a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts @@ -24,6 +24,7 @@ export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { tabName: '[subnavitemid="enforcement-tab"] > .moj-sub-navigation__link', enforcementStatusLink: '.govuk-link', + addEnforcementOverrideLink: '.govuk-link:contains("Add enforcement override")', detailsLink: '.govuk-details__summary-text', tableTitle: '.govuk-summary-card__title', From 531e4261b921734977cb943f0859b8486c28d48d Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 16:02:45 +0000 Subject: [PATCH 16/37] feat: refactor enforcement override tests to use shared locators and common setup - wip --- ...addEnforcementOverrideParentGuardian.cy.ts | 300 +++++++++--------- 1 file changed, 142 insertions(+), 158 deletions(-) diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts index c51353528d..6f70105577 100644 --- a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts @@ -1,159 +1,143 @@ -import { mount } from 'cypress/angular'; -import { of } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; -import { FinesAccEnfOverrideAddChangeComponent } from 'src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component'; -import { FinesAccEnfOverrideAddChangeFormComponent } from 'src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component'; -import { FinesAccountStore } from 'src/app/flows/fines/fines-acc/stores/fines-acc.store'; -import { FinesAccPayloadService } from 'src/app/flows/fines/fines-acc/services/fines-acc-payload.service'; -import { OpalFines } from 'src/app/flows/fines/services/opal-fines-service/opal-fines.service'; -import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service'; -import { DOM_ELEMENTS } from './constants/add_enforcement_override_elements'; - -const mockRoute: ActivatedRoute = { - snapshot: { - data: { - title: 'Add enforcement override', - enforcersRefData: { - refData: [ - { enforcer_id: 'E1', enforcer_code: 'EC1', name: 'Enforcer One' }, - { enforcer_id: 'E2', enforcer_code: 'EC2', name: 'Enforcer Two' }, - ], - }, - localJusticeAreasRefData: { - refData: [ - { local_justice_area_id: 'L1', name: 'LJA One' }, - { local_justice_area_id: 'L2', name: 'LJA Two' }, - ], - }, - resultsRefData: { - refData: [ - { result_id: 'R1', result_title: 'Result One', requires_enforcer: true }, - { result_id: 'R2', result_title: 'Result Two' }, - ], - }, - }, - }, -} as any; - -const mockStore = { - party_name: () => 'Test Person', - getAccountNumber: () => '123456', - account_id: () => 1001, - base_version: () => '1', - business_unit_id: () => '2002', - setSuccessMessage: () => {}, -}; - -const mockPayloadService = { - buildEnforcementOverrideFormPayload: () => ({ enforcement_override: {} }), -}; - -const mockOpalFinesService = { - getEnforcerPrettyName: (e: { name: string; enforcer_code: string }) => `${e.name} (${e.enforcer_code})`, - - getLocalJusticeAreaPrettyName: (lja: { name: string; local_justice_area_id: string }) => - `${lja.name} (${lja.local_justice_area_id})`, - - getResultPrettyName: (r: { result_title: string; result_id: string }) => `${r.result_title} (${r.result_id})`, - - getResult: () => of({ requires_enforcer: false, requires_lja: false }), - patchDefendantAccount: () => of({}), -}; - -const mockUtilsService = { - scrollToTop: () => {}, -}; - -const setup = () => { - return mount(FinesAccEnfOverrideAddChangeComponent, { - providers: [ - { provide: ActivatedRoute, useValue: mockRoute }, - { provide: FinesAccountStore, useValue: mockStore }, - { provide: FinesAccPayloadService, useValue: mockPayloadService }, - { provide: OpalFines, useValue: mockOpalFinesService }, - { provide: UtilsService, useValue: mockUtilsService }, - ], - }); +import { DOM_ELEMENTS as ENF_OVR } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators'; +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; +import { setupAccountEnquiryComponent } from '../accountEnquiry/setup/SetupComponent'; +import { IComponentProperties } from '../accountEnquiry/setup/setupComponent.interface'; +import { + interceptAuthenticatedUser, + interceptUserState, + interceptResultByCode, + interceptEnforcementOverrideResults, + interceptLocalJusticeAreas, + interceptEnforcers, +} from 'cypress/component/CommonIntercepts/CommonIntercepts'; +import { USER_STATE_MOCK_PERMISSION_BU77 } from 'cypress/component/CommonIntercepts/CommonUserState.mocks'; +import { + interceptDefendantHeader, + interceptEnforcementStatus, + interceptPatchDefendantAccount, +} from '../accountEnquiry/intercept/defendantAccountIntercepts'; +import { createDefendantHeaderMockWithName } from '../accountEnquiry/mocks/defendant_details_mock'; +import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@app/flows/fines/services/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; + +const componentProperties: IComponentProperties = { + accountId: '77', + fragments: 'enforcement', + interceptedRoutes: [ + '/access-denied', + '../note/add', + '../debtor/individual/amend', + '../debtor/parentGuardian/amend', + '../enforcement/amend', + '../enforcement/amend-denied', + // Add more routes here as needed + ], }; +function commonSetup() { + let headerMock = structuredClone(createDefendantHeaderMockWithName('Robert', 'Thomson')); + headerMock.debtor_type = 'individual'; + let enforcementMock = structuredClone(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK); + enforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: null, + enforcement_override_result_name: null, + }; + + const accountId = headerMock.defendant_account_party_id; + interceptAuthenticatedUser(); + interceptUserState(USER_STATE_MOCK_PERMISSION_BU77); + interceptDefendantHeader(accountId, headerMock, '123'); + interceptEnforcementStatus(accountId, enforcementMock, '123'); + + interceptEnforcementOverrideResults(); + interceptLocalJusticeAreas(); + interceptEnforcers(); + + ['ABDC', 'AEOC', 'BWTD', 'BWTU', 'CLAMPO', 'CWN', 'DW', 'MAN', 'NBWT', 'REGF', 'SUMA', 'TFOOUT'].forEach((code) => + interceptResultByCode(code), + ); + interceptPatchDefendantAccount(); + + setupAccountEnquiryComponent({ ...componentProperties, accountId: accountId }); + cy.get(ENF.addEnforcementOverrideLink).click(); +} describe('Add Enforcement Override', () => { it('AC1a, AC1b. Should render the form with title', () => { - setup(); + commonSetup(); - cy.get(DOM_ELEMENTS.title).should('contain.text', 'Test Person - 123456'); - cy.get(DOM_ELEMENTS.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.title).should('contain.text', 'Mr Robert THOMSON - 177A'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); }); it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', () => { - setup(); - - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).click(); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('ABDC').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('AEOC').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTU').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('CLAMPO').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('CWN').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('DW').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('FSN').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('MAN').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('NBWT').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('REGF').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('SUMA').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('exist'); - - cy.get(DOM_ELEMENTS.addOverrideButton).should('exist'); - cy.get(DOM_ELEMENTS.cancelLink).should('exist'); + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(ENF_OVR.addOverrideButton).should('exist'); + cy.get(ENF_OVR.cancelLink).should('exist'); }); it('AC2. Enforcer dropdown for valid override', () => { - setup(); - - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('AB'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('ABDC').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('AEOC').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTU').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('CLAMPO').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('CWN').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('DW').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('FSN').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('MAN').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('NBWT').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('REGF').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('SUMA').should('not.exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('not.exist'); - - cy.get(DOM_ELEMENTS.dropdownOptions).contains('ABDC').click(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'ABDC'); - - cy.get(DOM_ELEMENTS.enforcerDropdown).should('exist'); - cy.get(DOM_ELEMENTS.enforcerDropdown).click(); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('').should('exist'); + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('AB'); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('not.exist'); + + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'ABDC'); + + cy.get(ENF_OVR.enforcerDropdown).should('exist'); + cy.get(ENF_OVR.enforcerDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('The DWP (3)').should('exist'); }); it('AC3. LJA dropdown for valid override', () => { - setup(); + commonSetup(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('TFO'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').click(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'TFOOUT'); - cy.get(DOM_ELEMENTS.localJusticeAreaDropdown).should('exist'); - cy.get(DOM_ELEMENTS.localJusticeAreaDropdown).click(); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").should('exist'); }); it('AC4a. Error when no enforcement override is selected', () => { - setup(); + commonSetup(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); - cy.get(DOM_ELEMENTS.addOverrideButton).click(); - cy.get(DOM_ELEMENTS.errorSummary) + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) .should('exist') .contains('There is a problem') .next() @@ -161,36 +145,36 @@ describe('Add Enforcement Override', () => { }); it('AC4b. Error when no enforcer is selected', () => { - setup(); - - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('BW'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTU').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('BWTD').click(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'BWTD'); - cy.get(DOM_ELEMENTS.enforcerDropdown).should('exist'); - - cy.get(DOM_ELEMENTS.addOverrideButton).click(); - cy.get(DOM_ELEMENTS.errorSummary) + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('BW'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'BWTD'); + cy.get(ENF_OVR.enforcerDropdown).should('exist'); + + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) .should('exist') .contains('There is a problem') .next() .should('contain.text', 'Select an enforcer'); }); - it('AC4c. Error when no LJA is selected', () => { - setup(); + it.only('AC4c. Error when no LJA is selected', () => { + commonSetup(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('exist'); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).click().type('TFO'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').should('exist'); - cy.get(DOM_ELEMENTS.dropdownOptions).contains('TFOOUT').click(); - cy.get(DOM_ELEMENTS.enfOverrideDropdown).should('have.value', 'TFOOUT'); - cy.get(DOM_ELEMENTS.localJusticeAreaDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); - cy.get(DOM_ELEMENTS.addOverrideButton).click(); - cy.get(DOM_ELEMENTS.errorSummary) + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) .should('exist') .contains('There is a problem') .next() From e39eb1d66e45721a0c68d02c99b3c99e492cae3e Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 16:59:39 +0000 Subject: [PATCH 17/37] feat: add locators for enforcement override value and local justice area value --- .../account-enquiry/account.enquiry.enforcement.locators.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts index 5fdb0145ce..eb331d2197 100644 --- a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts @@ -37,8 +37,10 @@ export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { warrantNumber: '#lastEnforcementActionDetailsWarrant_numberKey', dateAdded: '#lastEnforcementActionDetailsDate_addedKey', enforcementOverride: '#enforcementOverrideDetailsEnforcement_overrideKey', + enforcementOverrideValue: '#enforcementOverrideDetailsEnforcement_overrideValue', enfOverrideEnforcer: '#enforcementOverrideDetailsEnforcerKey', localJusticeArea: '#enforcementOverrideDetailsLocal_justice_areaKey', + localJusticeAreaValue: '#enforcementOverrideDetailsLocal_justice_areaValue', detailsDaysInDefault: '[id="enforcementActionDetailsDays in defaultKey"]', detailsReason: '#enforcementActionDetailsReasonKey', From 622059103e1986bbe3037b443f4c34b64b476cdd Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Thu, 26 Mar 2026 16:59:45 +0000 Subject: [PATCH 18/37] feat: enhance enforcement override functionality with validation and cancel behavior --- ...addEnforcementOverrideParentGuardian.cy.ts | 133 +++++++++++++++++- 1 file changed, 130 insertions(+), 3 deletions(-) diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts index 6f70105577..9cb54aaba9 100644 --- a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts @@ -2,6 +2,7 @@ import { DOM_ELEMENTS as ENF_OVR } from '../../../shared/selectors/account-enqui import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; import { setupAccountEnquiryComponent } from '../accountEnquiry/setup/SetupComponent'; import { IComponentProperties } from '../accountEnquiry/setup/setupComponent.interface'; +import { DOM_ELEMENTS as VERSION_CONTROL } from '../accountEnquiry/constants/global_version_control_elements'; import { interceptAuthenticatedUser, interceptUserState, @@ -12,12 +13,15 @@ import { } from 'cypress/component/CommonIntercepts/CommonIntercepts'; import { USER_STATE_MOCK_PERMISSION_BU77 } from 'cypress/component/CommonIntercepts/CommonUserState.mocks'; import { + interceptAtAGlance, interceptDefendantHeader, interceptEnforcementStatus, interceptPatchDefendantAccount, } from '../accountEnquiry/intercept/defendantAccountIntercepts'; import { createDefendantHeaderMockWithName } from '../accountEnquiry/mocks/defendant_details_mock'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@app/flows/fines/services/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; +import { defendantAccountAtAGlanceResolver } from '@app/flows/fines/fines-acc/routing/resolvers/defendant-account-at-a-glance.resolver'; +import { OPAL_FINES_ACCOUNT_DEFENDANT_AT_A_GLANCE_MOCK } from '../accountEnquiry/mocks/defendant_details_at_glance_mock'; const componentProperties: IComponentProperties = { accountId: '77', @@ -44,6 +48,7 @@ function commonSetup() { const accountId = headerMock.defendant_account_party_id; interceptAuthenticatedUser(); interceptUserState(USER_STATE_MOCK_PERMISSION_BU77); + interceptAtAGlance(77, OPAL_FINES_ACCOUNT_DEFENDANT_AT_A_GLANCE_MOCK, 'W/"123456"'); interceptDefendantHeader(accountId, headerMock, '123'); interceptEnforcementStatus(accountId, enforcementMock, '123'); @@ -58,6 +63,8 @@ function commonSetup() { setupAccountEnquiryComponent({ ...componentProperties, accountId: accountId }); cy.get(ENF.addEnforcementOverrideLink).click(); + + return { accountId }; } describe('Add Enforcement Override', () => { @@ -163,7 +170,7 @@ describe('Add Enforcement Override', () => { .should('contain.text', 'Select an enforcer'); }); - it.only('AC4c. Error when no LJA is selected', () => { + it('AC4c. Error when no LJA is selected', () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -181,6 +188,126 @@ describe('Add Enforcement Override', () => { .should('contain.text', 'Select a Local Justice Area'); }); - // TODO: AC5 Validation Passes - // TODO: AC6 Cancel link behaviour and route guard + it('AC5. Valid submission returns to Enforcement tab with success banner and new override panel', () => { + const { accountId } = commonSetup(); + const updatedEnforcementMock = structuredClone(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK); + + updatedEnforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: 'TFOOUT', + enforcement_override_result_name: 'Transfer of Fine Order to a Court in England or Wales', + }; + updatedEnforcementMock.enforcement_override!.enforcer = { + enforcer_id: 0, + enforcer_name: '', + }; + updatedEnforcementMock.enforcement_override!.lja = { + lja_id: 4165, + lja_name: "Bedfordshire Magistrates' Court", + }; + + interceptEnforcementStatus(accountId, updatedEnforcementMock, '124'); + + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").click(); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('contain.value', "Bedfordshire Magistrates' Court (4165)"); + + cy.get(ENF_OVR.addOverrideButton).click(); + + cy.wait('@patchDefendantAccount') + .its('request.body.enforcement_override') + .should('deep.equal', { + enforcement_override_result: { + enforcement_override_result_id: 'TFOOUT', + }, + enforcer: null, + lja: { + lja_id: 4165, + }, + }); + + cy.wait('@getEnforcementStatus'); + + cy.get(ENF.tabName).should('exist').and('contain.text', 'Enforcement'); + + cy.get(VERSION_CONTROL.successBanner).should('exist'); + cy.get(VERSION_CONTROL.successBannerText).should('contain', 'Enforcement override added'); + + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverride).should('exist').and('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverrideValue).and( + 'contain.text', + 'Transfer of Fine Order to a Court in England or Wales(TFOOUT)', + ); + cy.get(ENF.localJusticeArea).should('exist').and('contain.text', 'Local Justice Area (LJA)'); + cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); + }); + + it('AC6a. Cancel without changes returns away from the add override page without confirmation', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + + cy.window().then((win) => { + cy.stub(win, 'confirm').as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('not.have.been.called'); + cy.get(ENF_OVR.title).should('not.exist'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }); + + it('AC6b. Cancel after selecting a value shows confirmation before navigating away', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return true; + }) + .as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF_OVR.title).should('not.exist'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }); + + it('AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return false; + }) + .as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + }); }); From 7f51d567f2d9d90271d3f8e8f1539420e0234925 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 14:45:03 +0000 Subject: [PATCH 19/37] fix: simplify condition for displaying last enforcement action --- .../fines-acc-defendant-details-enforcement-tab.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html index 0e4e06973f..9a07f61d29 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html @@ -86,7 +86,7 @@

Enforcement status

- @if (tabData.last_enforcement_action !== null) { + @if (tabData.last_enforcement_action) {
Date: Fri, 27 Mar 2026 14:45:31 +0000 Subject: [PATCH 20/37] feat: add enforcement override functionality for Parent/Guardian and Company --- .../addEnforcementOverride.cy.ts | 385 ++++++++++++++++++ ...addEnforcementOverrideParentGuardian.cy.ts | 313 -------------- 2 files changed, 385 insertions(+), 313 deletions(-) create mode 100644 cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts delete mode 100644 cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts new file mode 100644 index 0000000000..481f4a6ca4 --- /dev/null +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts @@ -0,0 +1,385 @@ +import { DOM_ELEMENTS as ENF_OVR } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators'; +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; +import { setupAccountEnquiryComponent } from '../accountEnquiry/setup/SetupComponent'; +import { IComponentProperties } from '../accountEnquiry/setup/setupComponent.interface'; +import { DOM_ELEMENTS as VERSION_CONTROL } from '../accountEnquiry/constants/global_version_control_elements'; +import { + interceptAuthenticatedUser, + interceptUserState, + interceptResultByCode, + interceptEnforcementOverrideResults, + interceptLocalJusticeAreas, + interceptEnforcers, +} from 'cypress/component/CommonIntercepts/CommonIntercepts'; +import { USER_STATE_MOCK_PERMISSION_BU77 } from 'cypress/component/CommonIntercepts/CommonUserState.mocks'; +import { + interceptDefendantHeader, + interceptEnforcementStatus, + interceptPatchDefendantAccount, +} from '../accountEnquiry/intercept/defendantAccountIntercepts'; +import { + createDefendantHeaderMockWithName, + DEFENDANT_HEADER_MOCK, +} from '../accountEnquiry/mocks/defendant_details_mock'; +import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@app/flows/fines/services/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; + +const componentProperties: IComponentProperties = { + accountId: '77', + fragments: 'enforcement', + interceptedRoutes: [ + '/access-denied', + '../note/add', + '../debtor/individual/amend', + '../debtor/parentGuardian/amend', + '../enforcement/amend', + '../enforcement/amend-denied', + // Add more routes here as needed + ], +}; +function setupAddEnforcementOverride( + headerMock = structuredClone(createDefendantHeaderMockWithName('Robert', 'Thomson')), +) { + let enforcementMock = structuredClone(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK); + enforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: null, + enforcement_override_result_name: null, + }; + + const accountId = headerMock.defendant_account_party_id; + interceptAuthenticatedUser(); + interceptUserState(USER_STATE_MOCK_PERMISSION_BU77); + interceptDefendantHeader(accountId, headerMock, '123'); + interceptEnforcementStatus(accountId, enforcementMock, '123'); + + interceptEnforcementOverrideResults(); + interceptLocalJusticeAreas(); + interceptEnforcers(); + + ['ABDC', 'AEOC', 'BWTD', 'BWTU', 'CLAMPO', 'CWN', 'DW', 'MAN', 'NBWT', 'REGF', 'SUMA', 'TFOOUT'].forEach((code) => + interceptResultByCode(code), + ); + interceptPatchDefendantAccount(); + + setupAccountEnquiryComponent({ ...componentProperties, accountId: accountId }); + cy.get(ENF.addEnforcementOverrideLink).click(); + + return { accountId }; +} + +function commonSetup() { + const headerMock = structuredClone(createDefendantHeaderMockWithName('Robert', 'Thomson')); + headerMock.debtor_type = 'individual'; + + return setupAddEnforcementOverride(headerMock); +} + +function companySetup() { + const headerMock = structuredClone(DEFENDANT_HEADER_MOCK); + headerMock.party_details.organisation_flag = true; + headerMock.party_details.organisation_details = { + organisation_name: 'Test Org Ltd', + organisation_aliases: [], + }; + headerMock.party_details.individual_details = null; + headerMock.debtor_type = 'company'; + + return setupAddEnforcementOverride(headerMock); +} + +describe( + 'Add Enforcement Override - Parent/Guardian', + { tags: ['@JIRA-STORY:PO-1866', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, + () => { + it('AC1a, AC1b. Should render the form with title', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Mr Robert THOMSON - 177A'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + }); + + it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(ENF_OVR.addOverrideButton).should('exist'); + cy.get(ENF_OVR.cancelLink).should('exist'); + }); + + it('Should support forward keyboard navigation across the add enforcement override form', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('be.visible').focus(); + cy.get(ENF_OVR.enfOverrideDropdown).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get(ENF_OVR.addOverrideButton).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + }); + + it('AC2. Enforcer dropdown for valid override', () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('AB'); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('not.exist'); + + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'ABDC'); + + cy.get(ENF_OVR.enforcerDropdown).should('exist'); + cy.get(ENF_OVR.enforcerDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('The DWP (3)').should('exist'); + }); + + it('AC3. LJA dropdown for valid override', () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").should('exist'); + }); + + it('AC4a. Error when no enforcement override is selected', () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select an enforcement override'); + }); + + it('AC4b. Error when no enforcer is selected', () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('BW'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'BWTD'); + cy.get(ENF_OVR.enforcerDropdown).should('exist'); + + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select an enforcer'); + }); + + it('AC4c. Error when no LJA is selected', () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); + + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select a Local Justice Area'); + }); + + it('AC5. Valid submission returns to Enforcement tab with success banner and new override panel', () => { + const { accountId } = commonSetup(); + const updatedEnforcementMock = structuredClone( + OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK, + ); + + updatedEnforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: 'TFOOUT', + enforcement_override_result_name: 'Transfer of Fine Order to a Court in England or Wales', + }; + updatedEnforcementMock.enforcement_override!.enforcer = { + enforcer_id: 0, + enforcer_name: '', + }; + updatedEnforcementMock.enforcement_override!.lja = { + lja_id: 4165, + lja_name: "Bedfordshire Magistrates' Court", + }; + + interceptEnforcementStatus(accountId, updatedEnforcementMock, '124'); + + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").click(); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('contain.value', "Bedfordshire Magistrates' Court (4165)"); + + cy.get(ENF_OVR.addOverrideButton).click(); + + cy.wait('@patchDefendantAccount') + .its('request.body.enforcement_override') + .should('deep.equal', { + enforcement_override_result: { + enforcement_override_result_id: 'TFOOUT', + }, + enforcer: null, + lja: { + lja_id: 4165, + }, + }); + + cy.wait('@getEnforcementStatus'); + + cy.get(ENF.tabName).should('exist').and('contain.text', 'Enforcement'); + + cy.get(VERSION_CONTROL.successBanner).should('exist'); + cy.get(VERSION_CONTROL.successBannerText).should('contain', 'Enforcement override added'); + + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverride).should('exist').and('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverrideValue).and( + 'contain.text', + 'Transfer of Fine Order to a Court in England or Wales(TFOOUT)', + ); + cy.get(ENF.localJusticeArea).should('exist').and('contain.text', 'Local Justice Area (LJA)'); + cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); + }); + + it('AC6a. Cancel without changes returns away from the add override page without confirmation', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + + cy.window().then((win) => { + cy.stub(win, 'confirm').as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('not.have.been.called'); + cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }); + + it('AC6b. Cancel after selecting a value shows confirmation before navigating away', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return true; + }) + .as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }); + + it('AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return false; + }) + .as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + }); + }, +); + +describe( + 'Add Enforcement Override - Company', + { tags: ['@JIRA-STORY:PO-1867', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, + () => { + it('AC1a, AC1b. Should render the form with company title', () => { + companySetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Test Org Ltd - 177A'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + }); + + it( + 'Should support forward keyboard navigation across the company add enforcement override form', + { tags: ['@JIRA-LABEL:accessibility'] }, + () => { + companySetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('be.visible').focus(); + cy.get(ENF_OVR.enfOverrideDropdown).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get(ENF_OVR.addOverrideButton).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + }, + ); + }, +); diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts deleted file mode 100644 index 9cb54aaba9..0000000000 --- a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverrideParentGuardian.cy.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { DOM_ELEMENTS as ENF_OVR } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators'; -import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; -import { setupAccountEnquiryComponent } from '../accountEnquiry/setup/SetupComponent'; -import { IComponentProperties } from '../accountEnquiry/setup/setupComponent.interface'; -import { DOM_ELEMENTS as VERSION_CONTROL } from '../accountEnquiry/constants/global_version_control_elements'; -import { - interceptAuthenticatedUser, - interceptUserState, - interceptResultByCode, - interceptEnforcementOverrideResults, - interceptLocalJusticeAreas, - interceptEnforcers, -} from 'cypress/component/CommonIntercepts/CommonIntercepts'; -import { USER_STATE_MOCK_PERMISSION_BU77 } from 'cypress/component/CommonIntercepts/CommonUserState.mocks'; -import { - interceptAtAGlance, - interceptDefendantHeader, - interceptEnforcementStatus, - interceptPatchDefendantAccount, -} from '../accountEnquiry/intercept/defendantAccountIntercepts'; -import { createDefendantHeaderMockWithName } from '../accountEnquiry/mocks/defendant_details_mock'; -import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@app/flows/fines/services/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; -import { defendantAccountAtAGlanceResolver } from '@app/flows/fines/fines-acc/routing/resolvers/defendant-account-at-a-glance.resolver'; -import { OPAL_FINES_ACCOUNT_DEFENDANT_AT_A_GLANCE_MOCK } from '../accountEnquiry/mocks/defendant_details_at_glance_mock'; - -const componentProperties: IComponentProperties = { - accountId: '77', - fragments: 'enforcement', - interceptedRoutes: [ - '/access-denied', - '../note/add', - '../debtor/individual/amend', - '../debtor/parentGuardian/amend', - '../enforcement/amend', - '../enforcement/amend-denied', - // Add more routes here as needed - ], -}; -function commonSetup() { - let headerMock = structuredClone(createDefendantHeaderMockWithName('Robert', 'Thomson')); - headerMock.debtor_type = 'individual'; - let enforcementMock = structuredClone(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK); - enforcementMock.enforcement_override!.enforcement_override_result = { - enforcement_override_result_id: null, - enforcement_override_result_name: null, - }; - - const accountId = headerMock.defendant_account_party_id; - interceptAuthenticatedUser(); - interceptUserState(USER_STATE_MOCK_PERMISSION_BU77); - interceptAtAGlance(77, OPAL_FINES_ACCOUNT_DEFENDANT_AT_A_GLANCE_MOCK, 'W/"123456"'); - interceptDefendantHeader(accountId, headerMock, '123'); - interceptEnforcementStatus(accountId, enforcementMock, '123'); - - interceptEnforcementOverrideResults(); - interceptLocalJusticeAreas(); - interceptEnforcers(); - - ['ABDC', 'AEOC', 'BWTD', 'BWTU', 'CLAMPO', 'CWN', 'DW', 'MAN', 'NBWT', 'REGF', 'SUMA', 'TFOOUT'].forEach((code) => - interceptResultByCode(code), - ); - interceptPatchDefendantAccount(); - - setupAccountEnquiryComponent({ ...componentProperties, accountId: accountId }); - cy.get(ENF.addEnforcementOverrideLink).click(); - - return { accountId }; -} - -describe('Add Enforcement Override', () => { - it('AC1a, AC1b. Should render the form with title', () => { - commonSetup(); - - cy.get(ENF_OVR.title).should('contain.text', 'Mr Robert THOMSON - 177A'); - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - }); - - it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.enfOverrideDropdown).click(); - cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('DW').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); - - cy.get(ENF_OVR.addOverrideButton).should('exist'); - cy.get(ENF_OVR.cancelLink).should('exist'); - }); - - it('AC2. Enforcer dropdown for valid override', () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('AB'); - cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('DW').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('not.exist'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('not.exist'); - - cy.get(ENF_OVR.dropdownOptions).contains('ABDC').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'ABDC'); - - cy.get(ENF_OVR.enforcerDropdown).should('exist'); - cy.get(ENF_OVR.enforcerDropdown).click(); - cy.get(ENF_OVR.dropdownOptions).contains('The DWP (3)').should('exist'); - }); - - it('AC3. LJA dropdown for valid override', () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); - - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); - cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); - cy.get(ENF_OVR.localJusticeAreaDropdown).click(); - cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").should('exist'); - }); - - it('AC4a. Error when no enforcement override is selected', () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.addOverrideButton).click(); - cy.get(ENF_OVR.errorSummary) - .should('exist') - .contains('There is a problem') - .next() - .should('contain.text', 'Select an enforcement override'); - }); - - it('AC4b. Error when no enforcer is selected', () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('BW'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTD').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'BWTD'); - cy.get(ENF_OVR.enforcerDropdown).should('exist'); - - cy.get(ENF_OVR.addOverrideButton).click(); - cy.get(ENF_OVR.errorSummary) - .should('exist') - .contains('There is a problem') - .next() - .should('contain.text', 'Select an enforcer'); - }); - - it('AC4c. Error when no LJA is selected', () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); - cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); - - cy.get(ENF_OVR.addOverrideButton).click(); - cy.get(ENF_OVR.errorSummary) - .should('exist') - .contains('There is a problem') - .next() - .should('contain.text', 'Select a Local Justice Area'); - }); - - it('AC5. Valid submission returns to Enforcement tab with success banner and new override panel', () => { - const { accountId } = commonSetup(); - const updatedEnforcementMock = structuredClone(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK); - - updatedEnforcementMock.enforcement_override!.enforcement_override_result = { - enforcement_override_result_id: 'TFOOUT', - enforcement_override_result_name: 'Transfer of Fine Order to a Court in England or Wales', - }; - updatedEnforcementMock.enforcement_override!.enforcer = { - enforcer_id: 0, - enforcer_name: '', - }; - updatedEnforcementMock.enforcement_override!.lja = { - lja_id: 4165, - lja_name: "Bedfordshire Magistrates' Court", - }; - - interceptEnforcementStatus(accountId, updatedEnforcementMock, '124'); - - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); - - cy.get(ENF_OVR.localJusticeAreaDropdown).click(); - cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").click(); - cy.get(ENF_OVR.localJusticeAreaDropdown).should('contain.value', "Bedfordshire Magistrates' Court (4165)"); - - cy.get(ENF_OVR.addOverrideButton).click(); - - cy.wait('@patchDefendantAccount') - .its('request.body.enforcement_override') - .should('deep.equal', { - enforcement_override_result: { - enforcement_override_result_id: 'TFOOUT', - }, - enforcer: null, - lja: { - lja_id: 4165, - }, - }); - - cy.wait('@getEnforcementStatus'); - - cy.get(ENF.tabName).should('exist').and('contain.text', 'Enforcement'); - - cy.get(VERSION_CONTROL.successBanner).should('exist'); - cy.get(VERSION_CONTROL.successBannerText).should('contain', 'Enforcement override added'); - - cy.get(ENF.tableTitle).should('contain.text', 'Enforcement override'); - cy.get(ENF.enforcementOverride).should('exist').and('contain.text', 'Enforcement override'); - cy.get(ENF.enforcementOverrideValue).and( - 'contain.text', - 'Transfer of Fine Order to a Court in England or Wales(TFOOUT)', - ); - cy.get(ENF.localJusticeArea).should('exist').and('contain.text', 'Local Justice Area (LJA)'); - cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); - }); - - it('AC6a. Cancel without changes returns away from the add override page without confirmation', () => { - commonSetup(); - - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - - cy.window().then((win) => { - cy.stub(win, 'confirm').as('confirm'); - }); - - cy.contains('a.govuk-link', /^Cancel$/i).click(); - - cy.get('@confirm').should('not.have.been.called'); - cy.get(ENF_OVR.title).should('not.exist'); - cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); - }); - - it('AC6b. Cancel after selecting a value shows confirmation before navigating away', () => { - commonSetup(); - - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); - - cy.window().then((win) => { - cy.stub(win, 'confirm') - .callsFake((message: string) => { - expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); - return true; - }) - .as('confirm'); - }); - - cy.contains('a.govuk-link', /^Cancel$/i).click(); - - cy.get('@confirm').should('have.been.calledOnce'); - cy.get(ENF_OVR.title).should('not.exist'); - cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); - }); - - it('AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', () => { - commonSetup(); - - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); - - cy.window().then((win) => { - cy.stub(win, 'confirm') - .callsFake((message: string) => { - expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); - return false; - }) - .as('confirm'); - }); - - cy.contains('a.govuk-link', /^Cancel$/i).click(); - - cy.get('@confirm').should('have.been.calledOnce'); - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); - }); -}); From b8cc77d8fe2934a016d139233da76b1059694af8 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 14:45:49 +0000 Subject: [PATCH 21/37] feat: implement enforcement tab navigation and add enforcement override form actions --- .../details.enforcement.actions.ts | 49 +++++++++++++++++++ .../account-details/details.nav.actions.ts | 29 +++++++++++ .../opal/flows/account-enquiry.flow.ts | 22 +++++++++ .../searchForAccount/account-enquiry.steps.ts | 16 ++++++ 4 files changed, 116 insertions(+) diff --git a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts index e69de29bb2..35af170f8c 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts @@ -0,0 +1,49 @@ +/** + * @file details.enforcement.actions.ts + * @description Actions for the Account Details "Enforcement" tab and add override form. + */ +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; +import { DOM_ELEMENTS as ENF_OVR } from '../../../../../shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators'; +import { createScopedLogger } from '../../../../../support/utils/log.helper'; + +const log = createScopedLogger('AccountDetailsEnforcementActions'); + +/** + * Actions for the Account Details enforcement tab and add override form. + */ +export class AccountDetailsEnforcementActions { + private static readonly DEFAULT_TIMEOUT = 15_000; + + /** + * Asserts the Enforcement tab content is visible. + */ + public assertEnforcementTabVisible(): void { + log('assert', 'Enforcement tab is visible'); + cy.contains(ENF.tableTitle, 'Enforcement overview', { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'be.visible', + ); + } + + /** + * Opens the add enforcement override form from the Enforcement tab. + */ + public openAddEnforcementOverrideForm(): void { + log('navigate', 'Opening add enforcement override form'); + cy.get(ENF.addEnforcementOverrideLink, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .click(); + } + + /** + * Asserts the add enforcement override form is visible. + */ + public assertAddEnforcementOverrideFormVisible(): void { + log('assert', 'Add enforcement override form is visible'); + cy.get(ENF_OVR.title, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .and('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'be.visible', + ); + } +} diff --git a/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts index 0e68ccd31c..cfe04ceba6 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts @@ -103,6 +103,19 @@ export class AccountDetailsNavActions { cy.get(N.subNav.paymentTermsTab, { timeout: 10_000 }).should('be.visible').click(); } + /** + * Navigates to the "Enforcement" tab within the Account Details shell. + * + * @description + * Clicks the “Enforcement” sub-navigation tab and prepares for assertions + * or further content checks within the tab panel. + */ + goToEnforcementTab(): void { + log('navigate', 'Navigating to "Enforcement" tab'); + + cy.get(N.subNav.enforcementTab, { timeout: 10_000 }).should('be.visible').click(); + } + /** * Asserts that the "Parent or guardian" tab is currently active. * @@ -181,4 +194,20 @@ export class AccountDetailsNavActions { .and('have.attr', 'aria-current', 'page') .and('contain.text', 'Payment terms'); } + + /** + * Asserts that the "Enforcement" tab is currently active. + * + * @description + * Confirms that the active tab link displays “Enforcement” + * and has the `aria-current="page"` attribute. + */ + assertEnforcementTabIsActive(): void { + log('assert', 'Asserting "Enforcement" tab is active'); + + cy.get(N.subNav.currentTab, { timeout: 10_000 }) + .should('be.visible') + .and('have.attr', 'aria-current', 'page') + .and('contain.text', 'Enforcement'); + } } diff --git a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts index 947d4d2dcc..5bad2d534d 100644 --- a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts +++ b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts @@ -17,6 +17,7 @@ import { CommonActions } from '../actions/common/common.actions'; import { EditDefendantDetailsActions } from '../actions/account-details/edit.defendant-details.actions'; import { EditCompanyDetailsActions } from '../actions/account-details/edit.company-details.actions'; import { EditParentGuardianDetailsActions } from '../actions/account-details/edit.parent-guardian-details.actions'; +import { AccountDetailsEnforcementActions } from '../actions/account-details/details.enforcement.actions'; import { createScopedLogger, createScopedSyncLogger } from '../../../../support/utils/log.helper'; const logAE = createScopedLogger('AccountEnquiryFlow'); @@ -71,6 +72,7 @@ export class AccountEnquiryFlow { private readonly editCompanyDetailsActions = new EditCompanyDetailsActions(); private readonly editParentGuardianActions = new EditParentGuardianDetailsActions(); private readonly paymentTerms = new AccountDetailsPaymentTermsActions(); + private readonly enforcement = new AccountDetailsEnforcementActions(); /** * Ensures the test is on the Individuals Account Search page. @@ -252,6 +254,26 @@ export class AccountEnquiryFlow { this.paymentTerms.assertAmendFormVisible(); } + /** + * Navigates to the Enforcement tab and asserts it is active. + */ + public goToEnforcementTab(): void { + logAE('method', 'goToEnforcementTab()'); + logAE('navigate', 'Navigating to Enforcement tab'); + this.detailsNav.goToEnforcementTab(); + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Opens the add enforcement override form from the Enforcement tab. + */ + public openAddEnforcementOverrideForm(): void { + logAE('method', 'openAddEnforcementOverrideForm()'); + this.enforcement.openAddEnforcementOverrideForm(); + this.enforcement.assertAddEnforcementOverrideFormVisible(); + } + /** * Submits instalments-only payment terms with a payment card request. * Stores the POST request body for later assertions. diff --git a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts index 026390add6..d95cb6ffc5 100644 --- a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts +++ b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts @@ -140,6 +140,22 @@ When('I open the amend payment terms form', () => { flow().openPaymentTermsAmendForm(); }); +/** + * @step Navigates to the Enforcement tab. + */ +When('I go to the Enforcement tab', () => { + log('step', 'Navigate to Enforcement tab'); + flow().goToEnforcementTab(); +}); + +/** + * @step Opens the add enforcement override form from the Enforcement tab. + */ +When('I open the add enforcement override form', () => { + log('step', 'Open add enforcement override form'); + flow().openAddEnforcementOverrideForm(); +}); + /** * @step Submits instalments-only payment terms with a payment card request. */ From e7bc431838ecc9a3b484b1801cc88d461c2e4293 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 14:45:57 +0000 Subject: [PATCH 22/37] feat: add accessibility tests for enforcement tab and override forms --- .../FineAccountEnquiryAccessibility.feature | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature diff --git a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature new file mode 100644 index 0000000000..3cd701bffc --- /dev/null +++ b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature @@ -0,0 +1,84 @@ +@JIRA-LABEL:account-enquiry @JIRA-LABEL:accessibility + +Feature: Account Enquiries - Enforcement Accessibility + + Background: + Given I am logged in with email "opal-test@dev.platform.hmcts.net" + And I clear all approved accounts + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 + Scenario: Enforcement tab accessibility + Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.forenames | Erin | + | account.defendant.surname | EnfAccess{uniq} | + | account.defendant.email_address_1 | Erin.EnfAccess{uniq}@test.com | + | account.defendant.telephone_number_home | 02078259314 | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-015 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.defendant.dob | 2002-05-15 | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I search for the account by last name "EnfAccess{uniq}" and open the latest result + And I go to the Enforcement tab + Then I check the page for accessibility + + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 + Scenario: Add enforcement override page accessibility + Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.forenames | Evan | + | account.defendant.surname | EnfOverrideAccess{uniq} | + | account.defendant.email_address_1 | Evan.EnfOverrideAccess{uniq}@test.com | + | account.defendant.telephone_number_home | 02078259315 | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-016 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.defendant.dob | 2002-05-15 | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I search for the account by last name "EnfOverrideAccess{uniq}" and open the latest result + And I go to the Enforcement tab + And I open the add enforcement override form + Then I check the page for accessibility + + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 @only + Scenario: Company enforcement tab accessibility + Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.company_name | Enf Company{uniq} | + | account.defendant.email_address_1 | EnfCompany{uniq}@test.com | + | account.defendant.post_code | AB23 4RN | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-017 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I open the company account details for "Enf Company{uniq}" + And I go to the Enforcement tab + Then I check the page for accessibility + + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 + Scenario: Company add enforcement override page accessibility + Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.company_name | Enf Override Company{uniq} | + | account.defendant.email_address_1 | EnfOverrideCompany{uniq}@test.com | + | account.defendant.post_code | AB23 4RN | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-018 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I open the company account details for "Enf Override Company{uniq}" + And I go to the Enforcement tab + And I open the add enforcement override form + Then I check the page for accessibility From c7c7c632e5fff9fe38282ad00e93f1493ee269ab Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Fri, 27 Mar 2026 14:52:28 +0000 Subject: [PATCH 23/37] Prevent form submit on select change. Fully prevent actionEnabled when required. --- ...s-acc-defendant-details-enforcement-tab.component.html | 7 ++++++- .../fines-acc-enf-override-add-change-form.component.ts | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html index 0e4e06973f..1d85256eb9 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html @@ -14,7 +14,12 @@

Enforcement status

" summaryListId="enforcementOverviewDetails" summaryListRowId="collection_order_status" - [actionEnabled]="hasAccountMaintenancePermission ? true : false" + [actionEnabled]=" + hasAccountMaintenancePermission && + (!isCompanyAccount || tabData.enforcement_overview.collection_order.collection_order_flag) + ? true + : false + " > Collection Order status diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts index b63d38e74d..11f0250966 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts @@ -134,6 +134,12 @@ export class FinesAccEnfOverrideAddChangeFormComponent extends AbstractFormBaseC * @returns void */ public handleChangeEnforcementAction(id: string): void { - this.getEnforcementActionResult(id); + if (id) { + this.getEnforcementActionResult(id); + } else { + this.disableFormControl('fenf_account_enforcement_enforcer'); + this.disableFormControl('fenf_account_enforcement_lja'); + this.form.updateValueAndValidity(); + } } } From 6da207e79b9a3eb2c6ab5518b5a26d2083c132e3 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 15:00:35 +0000 Subject: [PATCH 24/37] feat: add intercept for results in enforcement override tests and update accessibility feature scenarios --- .../amendDefendantEnforcement/addEnforcementOverride.cy.ts | 5 +++++ .../accountEnquiry/FineAccountEnquiryAccessibility.feature | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts index 481f4a6ca4..390a3b7d52 100644 --- a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts @@ -59,6 +59,7 @@ function setupAddEnforcementOverride( interceptResultByCode(code), ); interceptPatchDefendantAccount(); + cy.intercept('/opal-fines-service/results/').as('getResults'); setupAccountEnquiryComponent({ ...componentProperties, accountId: accountId }); cy.get(ENF.addEnforcementOverrideLink).click(); @@ -132,6 +133,8 @@ describe( cy.press(Cypress.Keyboard.Keys.TAB); cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + + cy.get('@getResults.all').should('have.length', 0); }); it('AC2. Enforcer dropdown for valid override', () => { @@ -379,6 +382,8 @@ describe( cy.press(Cypress.Keyboard.Keys.TAB); cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + + cy.get('@getResults.all').should('have.length', 0); }, ); }, diff --git a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature index 3cd701bffc..004070502a 100644 --- a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature +++ b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature @@ -46,7 +46,7 @@ Feature: Account Enquiries - Enforcement Accessibility And I open the add enforcement override form Then I check the page for accessibility - @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 @only + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 Scenario: Company enforcement tab accessibility Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": | Account_status | Submitted | From ea1eb7cf7ccb3e094654c3ae51554ad6a3b74b58 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 15:29:19 +0000 Subject: [PATCH 25/37] feat: add tags for accessibility and JIRA tracking in enforcement override tests --- .../addEnforcementOverride.cy.ts | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts index 390a3b7d52..c8bd474714 100644 --- a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts @@ -91,14 +91,14 @@ describe( 'Add Enforcement Override - Parent/Guardian', { tags: ['@JIRA-STORY:PO-1866', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, () => { - it('AC1a, AC1b. Should render the form with title', () => { + it('AC1a, AC1b. Should render the form with title', {tags: ['@JIRA-KEY:POT-4440']}, () => { commonSetup(); cy.get(ENF_OVR.title).should('contain.text', 'Mr Robert THOMSON - 177A'); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); }); - it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', () => { + it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', {tags: ['@JIRA-KEY:POT-4441']}, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -121,7 +121,7 @@ describe( cy.get(ENF_OVR.cancelLink).should('exist'); }); - it('Should support forward keyboard navigation across the add enforcement override form', () => { + it('Should support forward keyboard navigation across the add enforcement override form', {tags: ['@JIRA-KEY:POT-4442']}, () => { commonSetup(); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); @@ -137,7 +137,7 @@ describe( cy.get('@getResults.all').should('have.length', 0); }); - it('AC2. Enforcer dropdown for valid override', () => { + it('AC2. Enforcer dropdown for valid override', {tags: ['@JIRA-KEY:POT-4443']}, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -164,7 +164,7 @@ describe( cy.get(ENF_OVR.dropdownOptions).contains('The DWP (3)').should('exist'); }); - it('AC3. LJA dropdown for valid override', () => { + it('AC3. LJA dropdown for valid override', {tags: ['@JIRA-KEY:POT-4444']}, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -178,7 +178,7 @@ describe( cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").should('exist'); }); - it('AC4a. Error when no enforcement override is selected', () => { + it('AC4a. Error when no enforcement override is selected', {tags: ['@JIRA-KEY:POT-4445']}, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -190,7 +190,7 @@ describe( .should('contain.text', 'Select an enforcement override'); }); - it('AC4b. Error when no enforcer is selected', () => { + it('AC4b. Error when no enforcer is selected', {tags: ['@JIRA-KEY:POT-4446']}, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -209,7 +209,7 @@ describe( .should('contain.text', 'Select an enforcer'); }); - it('AC4c. Error when no LJA is selected', () => { + it('AC4c. Error when no LJA is selected', {tags: ['@JIRA-KEY:POT-4447']}, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -227,7 +227,7 @@ describe( .should('contain.text', 'Select a Local Justice Area'); }); - it('AC5. Valid submission returns to Enforcement tab with success banner and new override panel', () => { + it('AC5. Valid submission returns to Enforcement tab with success banner and new override panel', {tags: ['@JIRA-KEY:POT-4448']}, () => { const { accountId } = commonSetup(); const updatedEnforcementMock = structuredClone( OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK, @@ -287,7 +287,7 @@ describe( cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); }); - it('AC6a. Cancel without changes returns away from the add override page without confirmation', () => { + it('AC6a. Cancel without changes returns away from the add override page without confirmation', {tags: ['@JIRA-KEY:POT-4449']}, () => { commonSetup(); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); @@ -305,7 +305,7 @@ describe( cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); }); - it('AC6b. Cancel after selecting a value shows confirmation before navigating away', () => { + it('AC6b. Cancel after selecting a value shows confirmation before navigating away', {tags: ['@JIRA-KEY:POT-4450']}, () => { commonSetup(); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); @@ -330,7 +330,7 @@ describe( cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); }); - it('AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', () => { + it('AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', {tags: ['@JIRA-KEY:POT-4451']}, () => { commonSetup(); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); @@ -360,17 +360,14 @@ describe( 'Add Enforcement Override - Company', { tags: ['@JIRA-STORY:PO-1867', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, () => { - it('AC1a, AC1b. Should render the form with company title', () => { + it('AC1a, AC1b. Should render the form with company title', {tags: ['@JIRA-KEY:POT-4452']}, () => { companySetup(); cy.get(ENF_OVR.title).should('contain.text', 'Test Org Ltd - 177A'); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); }); - it( - 'Should support forward keyboard navigation across the company add enforcement override form', - { tags: ['@JIRA-LABEL:accessibility'] }, - () => { + it('Should support forward keyboard navigation across the company add enforcement override form', { tags: ['@JIRA-LABEL:accessibility', '@JIRA-KEY:POT-4453'] }, () => { companySetup(); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); @@ -384,7 +381,6 @@ describe( cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); cy.get('@getResults.all').should('have.length', 0); - }, - ); + }); }, ); From dbb53ea5048217716b261c0039cdafdfd798b8a7 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 15:39:11 +0000 Subject: [PATCH 26/37] feat: add JIRA keys for tracking in accessibility feature scenarios --- .../FineAccountEnquiryAccessibility.feature | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature index 004070502a..b9572b15b5 100644 --- a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature +++ b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature @@ -5,7 +5,7 @@ Feature: Account Enquiries - Enforcement Accessibility Background: Given I am logged in with email "opal-test@dev.platform.hmcts.net" And I clear all approved accounts - @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4454 Scenario: Enforcement tab accessibility Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": | Account_status | Submitted | @@ -25,7 +25,7 @@ Feature: Account Enquiries - Enforcement Accessibility And I go to the Enforcement tab Then I check the page for accessibility - @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4455 Scenario: Add enforcement override page accessibility Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": | Account_status | Submitted | @@ -46,7 +46,7 @@ Feature: Account Enquiries - Enforcement Accessibility And I open the add enforcement override form Then I check the page for accessibility - @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4456 Scenario: Company enforcement tab accessibility Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": | Account_status | Submitted | @@ -64,7 +64,7 @@ Feature: Account Enquiries - Enforcement Accessibility And I go to the Enforcement tab Then I check the page for accessibility - @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4457 Scenario: Company add enforcement override page accessibility Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": | Account_status | Submitted | From 8e5d14eae9bc487213d8a710eadeb4ae9be35f4d Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 15:50:45 +0000 Subject: [PATCH 27/37] linting --- .../addEnforcementOverride.cy.ts | 340 ++++++++++-------- .../details.enforcement.actions.ts | 6 +- 2 files changed, 187 insertions(+), 159 deletions(-) diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts index c8bd474714..5c4659f627 100644 --- a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts @@ -91,53 +91,61 @@ describe( 'Add Enforcement Override - Parent/Guardian', { tags: ['@JIRA-STORY:PO-1866', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, () => { - it('AC1a, AC1b. Should render the form with title', {tags: ['@JIRA-KEY:POT-4440']}, () => { + it('AC1a, AC1b. Should render the form with title', { tags: ['@JIRA-KEY:POT-4440'] }, () => { commonSetup(); cy.get(ENF_OVR.title).should('contain.text', 'Mr Robert THOMSON - 177A'); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); }); - it('AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', {tags: ['@JIRA-KEY:POT-4441']}, () => { - commonSetup(); - - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.get(ENF_OVR.enfOverrideDropdown).click(); - cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('DW').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('exist'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + it( + 'AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', + { tags: ['@JIRA-KEY:POT-4441'] }, + () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(ENF_OVR.addOverrideButton).should('exist'); + cy.get(ENF_OVR.cancelLink).should('exist'); + }, + ); + + it( + 'Should support forward keyboard navigation across the add enforcement override form', + { tags: ['@JIRA-KEY:POT-4442'] }, + () => { + commonSetup(); - cy.get(ENF_OVR.addOverrideButton).should('exist'); - cy.get(ENF_OVR.cancelLink).should('exist'); - }); - - it('Should support forward keyboard navigation across the add enforcement override form', {tags: ['@JIRA-KEY:POT-4442']}, () => { - commonSetup(); - - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).should('be.visible').focus(); - cy.get(ENF_OVR.enfOverrideDropdown).should('have.focus'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('be.visible').focus(); + cy.get(ENF_OVR.enfOverrideDropdown).should('have.focus'); - cy.press(Cypress.Keyboard.Keys.TAB); - cy.get(ENF_OVR.addOverrideButton).should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get(ENF_OVR.addOverrideButton).should('have.focus'); - cy.press(Cypress.Keyboard.Keys.TAB); - cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); - cy.get('@getResults.all').should('have.length', 0); - }); + cy.get('@getResults.all').should('have.length', 0); + }, + ); - it('AC2. Enforcer dropdown for valid override', {tags: ['@JIRA-KEY:POT-4443']}, () => { + it('AC2. Enforcer dropdown for valid override', { tags: ['@JIRA-KEY:POT-4443'] }, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -164,7 +172,7 @@ describe( cy.get(ENF_OVR.dropdownOptions).contains('The DWP (3)').should('exist'); }); - it('AC3. LJA dropdown for valid override', {tags: ['@JIRA-KEY:POT-4444']}, () => { + it('AC3. LJA dropdown for valid override', { tags: ['@JIRA-KEY:POT-4444'] }, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -178,7 +186,7 @@ describe( cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").should('exist'); }); - it('AC4a. Error when no enforcement override is selected', {tags: ['@JIRA-KEY:POT-4445']}, () => { + it('AC4a. Error when no enforcement override is selected', { tags: ['@JIRA-KEY:POT-4445'] }, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -190,7 +198,7 @@ describe( .should('contain.text', 'Select an enforcement override'); }); - it('AC4b. Error when no enforcer is selected', {tags: ['@JIRA-KEY:POT-4446']}, () => { + it('AC4b. Error when no enforcer is selected', { tags: ['@JIRA-KEY:POT-4446'] }, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -209,7 +217,7 @@ describe( .should('contain.text', 'Select an enforcer'); }); - it('AC4c. Error when no LJA is selected', {tags: ['@JIRA-KEY:POT-4447']}, () => { + it('AC4c. Error when no LJA is selected', { tags: ['@JIRA-KEY:POT-4447'] }, () => { commonSetup(); cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); @@ -227,132 +235,148 @@ describe( .should('contain.text', 'Select a Local Justice Area'); }); - it('AC5. Valid submission returns to Enforcement tab with success banner and new override panel', {tags: ['@JIRA-KEY:POT-4448']}, () => { - const { accountId } = commonSetup(); - const updatedEnforcementMock = structuredClone( - OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK, - ); - - updatedEnforcementMock.enforcement_override!.enforcement_override_result = { - enforcement_override_result_id: 'TFOOUT', - enforcement_override_result_name: 'Transfer of Fine Order to a Court in England or Wales', - }; - updatedEnforcementMock.enforcement_override!.enforcer = { - enforcer_id: 0, - enforcer_name: '', - }; - updatedEnforcementMock.enforcement_override!.lja = { - lja_id: 4165, - lja_name: "Bedfordshire Magistrates' Court", - }; - - interceptEnforcementStatus(accountId, updatedEnforcementMock, '124'); - - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + it( + 'AC5. Valid submission returns to Enforcement tab with success banner and new override panel', + { tags: ['@JIRA-KEY:POT-4448'] }, + () => { + const { accountId } = commonSetup(); + const updatedEnforcementMock = structuredClone( + OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK, + ); + + updatedEnforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: 'TFOOUT', + enforcement_override_result_name: 'Transfer of Fine Order to a Court in England or Wales', + }; + updatedEnforcementMock.enforcement_override!.enforcer = { + enforcer_id: 0, + enforcer_name: '', + }; + updatedEnforcementMock.enforcement_override!.lja = { + lja_id: 4165, + lja_name: "Bedfordshire Magistrates' Court", + }; + + interceptEnforcementStatus(accountId, updatedEnforcementMock, '124'); + + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").click(); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('contain.value', "Bedfordshire Magistrates' Court (4165)"); + + cy.get(ENF_OVR.addOverrideButton).click(); + + cy.wait('@patchDefendantAccount') + .its('request.body.enforcement_override') + .should('deep.equal', { + enforcement_override_result: { + enforcement_override_result_id: 'TFOOUT', + }, + enforcer: null, + lja: { + lja_id: 4165, + }, + }); + + cy.wait('@getEnforcementStatus'); + + cy.get(ENF.tabName).should('exist').and('contain.text', 'Enforcement'); + + cy.get(VERSION_CONTROL.successBanner).should('exist'); + cy.get(VERSION_CONTROL.successBannerText).should('contain', 'Enforcement override added'); + + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverride).should('exist').and('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverrideValue).and( + 'contain.text', + 'Transfer of Fine Order to a Court in England or Wales(TFOOUT)', + ); + cy.get(ENF.localJusticeArea).should('exist').and('contain.text', 'Local Justice Area (LJA)'); + cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); + }, + ); + + it( + 'AC6a. Cancel without changes returns away from the add override page without confirmation', + { tags: ['@JIRA-KEY:POT-4449'] }, + () => { + commonSetup(); - cy.get(ENF_OVR.localJusticeAreaDropdown).click(); - cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").click(); - cy.get(ENF_OVR.localJusticeAreaDropdown).should('contain.value', "Bedfordshire Magistrates' Court (4165)"); - - cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - cy.wait('@patchDefendantAccount') - .its('request.body.enforcement_override') - .should('deep.equal', { - enforcement_override_result: { - enforcement_override_result_id: 'TFOOUT', - }, - enforcer: null, - lja: { - lja_id: 4165, - }, + cy.window().then((win) => { + cy.stub(win, 'confirm').as('confirm'); }); - cy.wait('@getEnforcementStatus'); + cy.contains('a.govuk-link', /^Cancel$/i).click(); - cy.get(ENF.tabName).should('exist').and('contain.text', 'Enforcement'); + cy.get('@confirm').should('not.have.been.called'); + cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }, + ); - cy.get(VERSION_CONTROL.successBanner).should('exist'); - cy.get(VERSION_CONTROL.successBannerText).should('contain', 'Enforcement override added'); + it( + 'AC6b. Cancel after selecting a value shows confirmation before navigating away', + { tags: ['@JIRA-KEY:POT-4450'] }, + () => { + commonSetup(); - cy.get(ENF.tableTitle).should('contain.text', 'Enforcement override'); - cy.get(ENF.enforcementOverride).should('exist').and('contain.text', 'Enforcement override'); - cy.get(ENF.enforcementOverrideValue).and( - 'contain.text', - 'Transfer of Fine Order to a Court in England or Wales(TFOOUT)', - ); - cy.get(ENF.localJusticeArea).should('exist').and('contain.text', 'Local Justice Area (LJA)'); - cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); - }); - - it('AC6a. Cancel without changes returns away from the add override page without confirmation', {tags: ['@JIRA-KEY:POT-4449']}, () => { - commonSetup(); - - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); - - cy.window().then((win) => { - cy.stub(win, 'confirm').as('confirm'); - }); - - cy.contains('a.govuk-link', /^Cancel$/i).click(); - - cy.get('@confirm').should('not.have.been.called'); - cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); - cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); - cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); - }); - - it('AC6b. Cancel after selecting a value shows confirmation before navigating away', {tags: ['@JIRA-KEY:POT-4450']}, () => { - commonSetup(); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return true; + }) + .as('confirm'); + }); - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); - - cy.window().then((win) => { - cy.stub(win, 'confirm') - .callsFake((message: string) => { - expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); - return true; - }) - .as('confirm'); - }); - - cy.contains('a.govuk-link', /^Cancel$/i).click(); - - cy.get('@confirm').should('have.been.calledOnce'); - cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); - cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); - cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); - }); + cy.contains('a.govuk-link', /^Cancel$/i).click(); - it('AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', {tags: ['@JIRA-KEY:POT-4451']}, () => { - commonSetup(); + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }, + ); - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); - cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + it( + 'AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', + { tags: ['@JIRA-KEY:POT-4451'] }, + () => { + commonSetup(); - cy.window().then((win) => { - cy.stub(win, 'confirm') - .callsFake((message: string) => { - expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); - return false; - }) - .as('confirm'); - }); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return false; + }) + .as('confirm'); + }); - cy.contains('a.govuk-link', /^Cancel$/i).click(); + cy.contains('a.govuk-link', /^Cancel$/i).click(); - cy.get('@confirm').should('have.been.calledOnce'); - cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); - cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); - }); + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + }, + ); }, ); @@ -360,14 +384,17 @@ describe( 'Add Enforcement Override - Company', { tags: ['@JIRA-STORY:PO-1867', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, () => { - it('AC1a, AC1b. Should render the form with company title', {tags: ['@JIRA-KEY:POT-4452']}, () => { + it('AC1a, AC1b. Should render the form with company title', { tags: ['@JIRA-KEY:POT-4452'] }, () => { companySetup(); cy.get(ENF_OVR.title).should('contain.text', 'Test Org Ltd - 177A'); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); }); - it('Should support forward keyboard navigation across the company add enforcement override form', { tags: ['@JIRA-LABEL:accessibility', '@JIRA-KEY:POT-4453'] }, () => { + it( + 'Should support forward keyboard navigation across the company add enforcement override form', + { tags: ['@JIRA-LABEL:accessibility', '@JIRA-KEY:POT-4453'] }, + () => { companySetup(); cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); @@ -381,6 +408,7 @@ describe( cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); cy.get('@getResults.all').should('have.length', 0); - }); + }, + ); }, ); diff --git a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts index 35af170f8c..ea3388f32e 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts @@ -19,9 +19,9 @@ export class AccountDetailsEnforcementActions { */ public assertEnforcementTabVisible(): void { log('assert', 'Enforcement tab is visible'); - cy.contains(ENF.tableTitle, 'Enforcement overview', { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( - 'be.visible', - ); + cy.contains(ENF.tableTitle, 'Enforcement overview', { + timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT, + }).should('be.visible'); } /** From ff642658797d4d468a255cdea700bafc31081ae8 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 16:35:26 +0000 Subject: [PATCH 28/37] chore: remove outdated yarn audit known issues --- yarn-audit-known-issues | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index 0afd1c6fac..ce218b7bf3 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1,3 +1,4 @@ + {"value":"@angular/ssr","children":{"ID":1113509,"Issue":"Angular SSR is vulnerable to SSRF and Header Injection via request handling pipeline","URL":"https://github.com/advisories/GHSA-x288-3778-4hhx","Severity":"critical","Vulnerable Versions":">=21.0.0-next.0 <21.1.5","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} {"value":"@angular/ssr","children":{"ID":1113513,"Issue":"Angular SSR has an Open Redirect via X-Forwarded-Prefix","URL":"https://github.com/advisories/GHSA-xh43-g2fq-wjrj","Severity":"moderate","Vulnerable Versions":">=21.0.0-next.0 <21.1.5","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} {"value":"@angular/ssr","children":{"ID":1115053,"Issue":"Protocol-Relative URL Injection via Single Backslash Bypass in Angular SSR","URL":"https://github.com/advisories/GHSA-vfx2-hv2g-xj5f","Severity":"moderate","Vulnerable Versions":">=21.0.0-next.0 <21.2.3","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} From 9a3c71884dc6ad5078d36ff587e67595555fcdde Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 16:44:59 +0000 Subject: [PATCH 29/37] chore: remove outdated yarn audit known issues --- yarn-audit-known-issues | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index ce218b7bf3..e69de29bb2 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -1,24 +0,0 @@ - -{"value":"@angular/ssr","children":{"ID":1113509,"Issue":"Angular SSR is vulnerable to SSRF and Header Injection via request handling pipeline","URL":"https://github.com/advisories/GHSA-x288-3778-4hhx","Severity":"critical","Vulnerable Versions":">=21.0.0-next.0 <21.1.5","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} -{"value":"@angular/ssr","children":{"ID":1113513,"Issue":"Angular SSR has an Open Redirect via X-Forwarded-Prefix","URL":"https://github.com/advisories/GHSA-xh43-g2fq-wjrj","Severity":"moderate","Vulnerable Versions":">=21.0.0-next.0 <21.1.5","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} -{"value":"@angular/ssr","children":{"ID":1115053,"Issue":"Protocol-Relative URL Injection via Single Backslash Bypass in Angular SSR","URL":"https://github.com/advisories/GHSA-vfx2-hv2g-xj5f","Severity":"moderate","Vulnerable Versions":">=21.0.0-next.0 <21.2.3","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} -{"value":"ajv","children":{"ID":1113715,"Issue":"ajv has ReDoS when using `$data` option","URL":"https://github.com/advisories/GHSA-2g4f-4pwh-qvx6","Severity":"moderate","Vulnerable Versions":">=7.0.0-alpha.0 <8.18.0","Tree Versions":["8.17.1"],"Dependents":["schema-utils@npm:4.3.3"]}} -{"value":"minimatch","children":{"ID":1113459,"Issue":"minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern","URL":"https://github.com/advisories/GHSA-3ppc-4f35-3m26","Severity":"high","Vulnerable Versions":"<3.1.3","Tree Versions":["3.1.2"],"Dependents":["find-cypress-specs@npm:1.47.2"]}} -{"value":"minimatch","children":{"ID":1113465,"Issue":"minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern","URL":"https://github.com/advisories/GHSA-3ppc-4f35-3m26","Severity":"high","Vulnerable Versions":">=9.0.0 <9.0.6","Tree Versions":["9.0.5"],"Dependents":["mocha@npm:11.7.5"]}} -{"value":"minimatch","children":{"ID":1113538,"Issue":"minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments","URL":"https://github.com/advisories/GHSA-7r86-cg39-jmmj","Severity":"high","Vulnerable Versions":"<3.1.3","Tree Versions":["3.1.2"],"Dependents":["find-cypress-specs@npm:1.47.2"]}} -{"value":"minimatch","children":{"ID":1113544,"Issue":"minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments","URL":"https://github.com/advisories/GHSA-7r86-cg39-jmmj","Severity":"high","Vulnerable Versions":">=9.0.0 <9.0.7","Tree Versions":["9.0.5"],"Dependents":["mocha@npm:11.7.5"]}} -{"value":"minimatch","children":{"ID":1113545,"Issue":"minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments","URL":"https://github.com/advisories/GHSA-7r86-cg39-jmmj","Severity":"high","Vulnerable Versions":">=10.0.0 <10.2.3","Tree Versions":["10.2.2"],"Dependents":["glob@npm:13.0.6"]}} -{"value":"minimatch","children":{"ID":1113546,"Issue":"minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions","URL":"https://github.com/advisories/GHSA-23c5-xmqv-rm74","Severity":"high","Vulnerable Versions":"<3.1.4","Tree Versions":["3.1.2"],"Dependents":["find-cypress-specs@npm:1.47.2"]}} -{"value":"minimatch","children":{"ID":1113552,"Issue":"minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions","URL":"https://github.com/advisories/GHSA-23c5-xmqv-rm74","Severity":"high","Vulnerable Versions":">=9.0.0 <9.0.7","Tree Versions":["9.0.5"],"Dependents":["mocha@npm:11.7.5"]}} -{"value":"minimatch","children":{"ID":1113553,"Issue":"minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions","URL":"https://github.com/advisories/GHSA-23c5-xmqv-rm74","Severity":"high","Vulnerable Versions":">=10.0.0 <10.2.3","Tree Versions":["10.2.2"],"Dependents":["glob@npm:13.0.6"]}} -{"value":"picomatch","children":{"ID":1115490,"Issue":"Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching","URL":"https://github.com/advisories/GHSA-3v7f-55p6-f55p","Severity":"moderate","Vulnerable Versions":"<2.3.2","Tree Versions":["2.3.1"],"Dependents":["micromatch@npm:4.0.8"]}} -{"value":"picomatch","children":{"ID":1115492,"Issue":"Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching","URL":"https://github.com/advisories/GHSA-3v7f-55p6-f55p","Severity":"moderate","Vulnerable Versions":">=4.0.0 <4.0.4","Tree Versions":["4.0.2"],"Dependents":["tinyglobby@npm:0.2.10"]}} -{"value":"picomatch","children":{"ID":1115493,"Issue":"Picomatch has a ReDoS vulnerability via extglob quantifiers","URL":"https://github.com/advisories/GHSA-c2c7-rcm5-vvqj","Severity":"high","Vulnerable Versions":"<2.3.2","Tree Versions":["2.3.1"],"Dependents":["micromatch@npm:4.0.8"]}} -{"value":"picomatch","children":{"ID":1115495,"Issue":"Picomatch has a ReDoS vulnerability via extglob quantifiers","URL":"https://github.com/advisories/GHSA-c2c7-rcm5-vvqj","Severity":"high","Vulnerable Versions":">=4.0.0 <4.0.4","Tree Versions":["4.0.2"],"Dependents":["tinyglobby@npm:0.2.10"]}} -{"value":"serialize-javascript","children":{"ID":1113686,"Issue":"Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()","URL":"https://github.com/advisories/GHSA-5c6j-r48x-rmvq","Severity":"high","Vulnerable Versions":"<=7.0.2","Tree Versions":["6.0.2"],"Dependents":["mocha@npm:11.7.5"]}} -{"value":"undici","children":{"ID":1114591,"Issue":"Undici: Malicious WebSocket 64-bit length overflows parser and crashes the client","URL":"https://github.com/advisories/GHSA-f269-vfmq-vjvj","Severity":"high","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} -{"value":"undici","children":{"ID":1114593,"Issue":"Undici has an HTTP Request/Response Smuggling issue","URL":"https://github.com/advisories/GHSA-2mjp-6q6p-2qxm","Severity":"moderate","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} -{"value":"undici","children":{"ID":1114637,"Issue":"Undici has Unbounded Memory Consumption in WebSocket permessage-deflate Decompression","URL":"https://github.com/advisories/GHSA-vrm6-8vpv-qv8q","Severity":"high","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} -{"value":"undici","children":{"ID":1114639,"Issue":"Undici has Unhandled Exception in WebSocket Client Due to Invalid server_max_window_bits Validation","URL":"https://github.com/advisories/GHSA-v9p9-hfj2-hcw8","Severity":"high","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} -{"value":"undici","children":{"ID":1114641,"Issue":"Undici has CRLF Injection in undici via `upgrade` option","URL":"https://github.com/advisories/GHSA-4992-7rv2-5pvq","Severity":"moderate","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} -{"value":"undici","children":{"ID":1114643,"Issue":"Undici has Unbounded Memory Consumption in its DeduplicationHandler via Response Buffering that leads to DoS","URL":"https://github.com/advisories/GHSA-phc3-fgpg-7m6h","Severity":"moderate","Vulnerable Versions":">=7.17.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} From dc7c96ecffbf86fe7712f6cfd4eb25de33bef975 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Fri, 27 Mar 2026 17:17:23 +0000 Subject: [PATCH 30/37] Improving Unit test coverage --- ...override-add-change-form.component.spec.ts | 7 +- .../opal-fines.service.spec.ts | 124 ++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts index 09adf48097..86e2db543b 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts @@ -43,10 +43,13 @@ describe('FinesAccEnfOverrideAddChangeFormComponent', () => { expect(ljaControl?.disabled).toBe(true); }); - it('should call handleChangeEnforcementAction on enforcement action change', () => { + it('should call handleChangeEnforcementAction for both selected and cleared enforcement action values', () => { const spy = vi.spyOn(component, 'handleChangeEnforcementAction'); component.form.get('fenf_account_enforcement_action')?.setValue('R1'); - expect(spy).toHaveBeenCalledWith('R1'); + component.form.get('fenf_account_enforcement_action')?.setValue(null); + + expect(spy).toHaveBeenNthCalledWith(1, 'R1'); + expect(spy).toHaveBeenNthCalledWith(2, ''); }); it('should enable only enforcer control when result requires enforcer', () => { diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index 13f8299858..335c29ca00 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -343,6 +343,30 @@ describe('OpalFines', () => { expect(result).toBeNull(); }); + it('should return the item value for a given configuration item name on non-snake-case business unit', () => { + const businessUnit = OPAL_FINES_BUSINESS_UNIT_NON_SNAKE_CASE_MOCK; + const itemName = 'Item1'; + + const result = service.getConfigurationItemValue(businessUnit, itemName); + + expect(result).toEqual('Item1'); + }); + + it('should return null for non-snake-case business unit when item exists but itemValue is null', () => { + const businessUnit = structuredClone(OPAL_FINES_BUSINESS_UNIT_NON_SNAKE_CASE_MOCK); + businessUnit.configurationItems = [ + { + itemName: 'Item1', + itemValue: null, + itemValues: [], + }, + ]; + + const result = service.getConfigurationItemValue(businessUnit, 'Item1'); + + expect(result).toBeNull(); + }); + it('should send a GET request to results ref data API', () => { const resultIds = ['1', '2', '3']; const expectedResponse: IOpalFinesResultsRefData = OPAL_FINES_RESULTS_REF_DATA_MOCK; @@ -1235,6 +1259,58 @@ describe('OpalFines', () => { req.flush({ defendant_account_id: accountId, message: 'Account comments notes updated successfully' }); }); + describe('patchDefendantAccount headers', () => { + it('should include If-Match and Business-Unit-Id headers when both are provided', () => { + const accountId = 123456; + const payload = OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS; + const version = '10'; + const businessUnitId = '2002'; + + service.patchDefendantAccount(accountId, payload, version, businessUnitId).subscribe((response) => { + expect(response).toEqual({ defendant_account_id: accountId }); + }); + + const req = httpMock.expectOne(`${OPAL_FINES_PATHS.defendantAccounts}/${accountId}`); + expect(req.request.method).toBe('PATCH'); + expect(req.request.headers.get('If-Match')).toBe(version); + expect(req.request.headers.get('Business-Unit-Id')).toBe(businessUnitId); + req.flush({ defendant_account_id: accountId }); + }); + + it('should include only Business-Unit-Id when version is not provided', () => { + const accountId = 123456; + const payload = OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS; + const businessUnitId = '2002'; + + service.patchDefendantAccount(accountId, payload, undefined, businessUnitId).subscribe((response) => { + expect(response).toEqual({ defendant_account_id: accountId }); + }); + + const req = httpMock.expectOne(`${OPAL_FINES_PATHS.defendantAccounts}/${accountId}`); + expect(req.request.method).toBe('PATCH'); + expect(req.request.headers.has('If-Match')).toBe(false); + expect(req.request.headers.get('Business-Unit-Id')).toBe(businessUnitId); + req.flush({ defendant_account_id: accountId }); + }); + + it('should not include If-Match and should include Business-Unit-Id when version is empty string and business unit is empty string', () => { + const accountId = 123456; + const payload = OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS; + const version = ''; + const businessUnitId = ''; + + service.patchDefendantAccount(accountId, payload, version, businessUnitId).subscribe((response) => { + expect(response).toEqual({ defendant_account_id: accountId }); + }); + + const req = httpMock.expectOne(`${OPAL_FINES_PATHS.defendantAccounts}/${accountId}`); + expect(req.request.method).toBe('PATCH'); + expect(req.request.headers.has('If-Match')).toBe(false); + expect(req.request.headers.get('Business-Unit-Id')).toBe(''); + req.flush({ defendant_account_id: accountId }); + }); + }); + describe('putDefendantAccountPaymentTerms', () => { it('should send a PUT request to amend payment terms for a defendant account', () => { const defendantAccountId = 123456; @@ -1283,6 +1359,54 @@ describe('OpalFines', () => { }); }); + describe('putDefendantAccountParty', () => { + it('should send a PUT request to amend defendant account party with optional headers', () => { + const defendantAccountId = 123456; + const defendantAccountPartyId = 'PARTY-123'; + const version = '8'; + const businessUnitId = '61'; + const payload = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK.defendant_account_party; + const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK; + + service + .putDefendantAccountParty(defendantAccountId, defendantAccountPartyId, payload, version, businessUnitId) + .subscribe((response) => { + expect(response).toEqual(expectedResponse); + }); + + const expectedUrl = `${OPAL_FINES_PATHS.defendantAccounts}/${defendantAccountId}/defendant-account-parties/${defendantAccountPartyId}`; + const req = httpMock.expectOne(expectedUrl); + + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(payload); + expect(req.request.headers.get('If-Match')).toBe(version); + expect(req.request.headers.get('Business-Unit-Id')).toBe(businessUnitId); + + req.flush(expectedResponse); + }); + + it('should send a PUT request without optional headers when version and businessUnitId are not provided', () => { + const defendantAccountId = 123456; + const defendantAccountPartyId = 'PARTY-123'; + const payload = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK.defendant_account_party; + const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK; + + service.putDefendantAccountParty(defendantAccountId, defendantAccountPartyId, payload).subscribe((response) => { + expect(response).toEqual(expectedResponse); + }); + + const expectedUrl = `${OPAL_FINES_PATHS.defendantAccounts}/${defendantAccountId}/defendant-account-parties/${defendantAccountPartyId}`; + const req = httpMock.expectOne(expectedUrl); + + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(payload); + expect(req.request.headers.has('If-Match')).toBe(false); + expect(req.request.headers.has('Business-Unit-Id')).toBe(false); + + req.flush(expectedResponse); + }); + }); + it('should getMinorCreditorAccountHeader', () => { const accountId = 456; const expectedResponse = FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK; From 3cc11ded64cacdb636304fecd46d5e98d1152913 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Fri, 27 Mar 2026 17:28:04 +0000 Subject: [PATCH 31/37] chore: update yarn audit known issues with new vulnerabilities --- yarn-audit-known-issues | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/yarn-audit-known-issues b/yarn-audit-known-issues index e69de29bb2..0afd1c6fac 100644 --- a/yarn-audit-known-issues +++ b/yarn-audit-known-issues @@ -0,0 +1,23 @@ +{"value":"@angular/ssr","children":{"ID":1113509,"Issue":"Angular SSR is vulnerable to SSRF and Header Injection via request handling pipeline","URL":"https://github.com/advisories/GHSA-x288-3778-4hhx","Severity":"critical","Vulnerable Versions":">=21.0.0-next.0 <21.1.5","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} +{"value":"@angular/ssr","children":{"ID":1113513,"Issue":"Angular SSR has an Open Redirect via X-Forwarded-Prefix","URL":"https://github.com/advisories/GHSA-xh43-g2fq-wjrj","Severity":"moderate","Vulnerable Versions":">=21.0.0-next.0 <21.1.5","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} +{"value":"@angular/ssr","children":{"ID":1115053,"Issue":"Protocol-Relative URL Injection via Single Backslash Bypass in Angular SSR","URL":"https://github.com/advisories/GHSA-vfx2-hv2g-xj5f","Severity":"moderate","Vulnerable Versions":">=21.0.0-next.0 <21.2.3","Tree Versions":["21.1.4"],"Dependents":["opal-frontend@workspace:."]}} +{"value":"ajv","children":{"ID":1113715,"Issue":"ajv has ReDoS when using `$data` option","URL":"https://github.com/advisories/GHSA-2g4f-4pwh-qvx6","Severity":"moderate","Vulnerable Versions":">=7.0.0-alpha.0 <8.18.0","Tree Versions":["8.17.1"],"Dependents":["schema-utils@npm:4.3.3"]}} +{"value":"minimatch","children":{"ID":1113459,"Issue":"minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern","URL":"https://github.com/advisories/GHSA-3ppc-4f35-3m26","Severity":"high","Vulnerable Versions":"<3.1.3","Tree Versions":["3.1.2"],"Dependents":["find-cypress-specs@npm:1.47.2"]}} +{"value":"minimatch","children":{"ID":1113465,"Issue":"minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern","URL":"https://github.com/advisories/GHSA-3ppc-4f35-3m26","Severity":"high","Vulnerable Versions":">=9.0.0 <9.0.6","Tree Versions":["9.0.5"],"Dependents":["mocha@npm:11.7.5"]}} +{"value":"minimatch","children":{"ID":1113538,"Issue":"minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments","URL":"https://github.com/advisories/GHSA-7r86-cg39-jmmj","Severity":"high","Vulnerable Versions":"<3.1.3","Tree Versions":["3.1.2"],"Dependents":["find-cypress-specs@npm:1.47.2"]}} +{"value":"minimatch","children":{"ID":1113544,"Issue":"minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments","URL":"https://github.com/advisories/GHSA-7r86-cg39-jmmj","Severity":"high","Vulnerable Versions":">=9.0.0 <9.0.7","Tree Versions":["9.0.5"],"Dependents":["mocha@npm:11.7.5"]}} +{"value":"minimatch","children":{"ID":1113545,"Issue":"minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments","URL":"https://github.com/advisories/GHSA-7r86-cg39-jmmj","Severity":"high","Vulnerable Versions":">=10.0.0 <10.2.3","Tree Versions":["10.2.2"],"Dependents":["glob@npm:13.0.6"]}} +{"value":"minimatch","children":{"ID":1113546,"Issue":"minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions","URL":"https://github.com/advisories/GHSA-23c5-xmqv-rm74","Severity":"high","Vulnerable Versions":"<3.1.4","Tree Versions":["3.1.2"],"Dependents":["find-cypress-specs@npm:1.47.2"]}} +{"value":"minimatch","children":{"ID":1113552,"Issue":"minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions","URL":"https://github.com/advisories/GHSA-23c5-xmqv-rm74","Severity":"high","Vulnerable Versions":">=9.0.0 <9.0.7","Tree Versions":["9.0.5"],"Dependents":["mocha@npm:11.7.5"]}} +{"value":"minimatch","children":{"ID":1113553,"Issue":"minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions","URL":"https://github.com/advisories/GHSA-23c5-xmqv-rm74","Severity":"high","Vulnerable Versions":">=10.0.0 <10.2.3","Tree Versions":["10.2.2"],"Dependents":["glob@npm:13.0.6"]}} +{"value":"picomatch","children":{"ID":1115490,"Issue":"Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching","URL":"https://github.com/advisories/GHSA-3v7f-55p6-f55p","Severity":"moderate","Vulnerable Versions":"<2.3.2","Tree Versions":["2.3.1"],"Dependents":["micromatch@npm:4.0.8"]}} +{"value":"picomatch","children":{"ID":1115492,"Issue":"Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching","URL":"https://github.com/advisories/GHSA-3v7f-55p6-f55p","Severity":"moderate","Vulnerable Versions":">=4.0.0 <4.0.4","Tree Versions":["4.0.2"],"Dependents":["tinyglobby@npm:0.2.10"]}} +{"value":"picomatch","children":{"ID":1115493,"Issue":"Picomatch has a ReDoS vulnerability via extglob quantifiers","URL":"https://github.com/advisories/GHSA-c2c7-rcm5-vvqj","Severity":"high","Vulnerable Versions":"<2.3.2","Tree Versions":["2.3.1"],"Dependents":["micromatch@npm:4.0.8"]}} +{"value":"picomatch","children":{"ID":1115495,"Issue":"Picomatch has a ReDoS vulnerability via extglob quantifiers","URL":"https://github.com/advisories/GHSA-c2c7-rcm5-vvqj","Severity":"high","Vulnerable Versions":">=4.0.0 <4.0.4","Tree Versions":["4.0.2"],"Dependents":["tinyglobby@npm:0.2.10"]}} +{"value":"serialize-javascript","children":{"ID":1113686,"Issue":"Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()","URL":"https://github.com/advisories/GHSA-5c6j-r48x-rmvq","Severity":"high","Vulnerable Versions":"<=7.0.2","Tree Versions":["6.0.2"],"Dependents":["mocha@npm:11.7.5"]}} +{"value":"undici","children":{"ID":1114591,"Issue":"Undici: Malicious WebSocket 64-bit length overflows parser and crashes the client","URL":"https://github.com/advisories/GHSA-f269-vfmq-vjvj","Severity":"high","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} +{"value":"undici","children":{"ID":1114593,"Issue":"Undici has an HTTP Request/Response Smuggling issue","URL":"https://github.com/advisories/GHSA-2mjp-6q6p-2qxm","Severity":"moderate","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} +{"value":"undici","children":{"ID":1114637,"Issue":"Undici has Unbounded Memory Consumption in WebSocket permessage-deflate Decompression","URL":"https://github.com/advisories/GHSA-vrm6-8vpv-qv8q","Severity":"high","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} +{"value":"undici","children":{"ID":1114639,"Issue":"Undici has Unhandled Exception in WebSocket Client Due to Invalid server_max_window_bits Validation","URL":"https://github.com/advisories/GHSA-v9p9-hfj2-hcw8","Severity":"high","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} +{"value":"undici","children":{"ID":1114641,"Issue":"Undici has CRLF Injection in undici via `upgrade` option","URL":"https://github.com/advisories/GHSA-4992-7rv2-5pvq","Severity":"moderate","Vulnerable Versions":">=7.0.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} +{"value":"undici","children":{"ID":1114643,"Issue":"Undici has Unbounded Memory Consumption in its DeduplicationHandler via Response Buffering that leads to DoS","URL":"https://github.com/advisories/GHSA-phc3-fgpg-7m6h","Severity":"moderate","Vulnerable Versions":">=7.17.0 <7.24.0","Tree Versions":["7.22.0"],"Dependents":["@actions/http-client@npm:2.2.3"]}} From f2a2c59a1f39497330abeec3d429ad4526c54148 Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 30 Mar 2026 10:51:42 +0100 Subject: [PATCH 32/37] Change patchdefendantaccount payload to only pass 1 property to match API endpoint requirement. --- .../fines-acc-payload.service.spec.ts | 2 -- .../services/fines-acc-payload.service.ts | 3 -- ...account-patch-payload-defaults.constant.ts | 6 ---- ...ines-update-defendant-account.interface.ts | 4 +-- .../opal-fines.service.spec.ts | 33 +++++++++++++++---- 5 files changed, 29 insertions(+), 19 deletions(-) delete mode 100644 src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts index 74a28b2b30..69988df8bf 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts @@ -545,7 +545,6 @@ describe('FinesAccPayloadService', () => { free_text_note_2: 'Updated note 2', free_text_note_3: 'Updated note 3', }, - enforcement_override: null, }); }); @@ -566,7 +565,6 @@ describe('FinesAccPayloadService', () => { free_text_note_2: null, free_text_note_3: null, }, - enforcement_override: null, }); }); diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts index 06959f87ef..12f9231b73 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts @@ -26,7 +26,6 @@ import { buildPaymentTermsAmendPayloadUtil } from './utils/fines-acc-payload-bui import { buildAccountPartyFromFormState } from './utils/fines-acc-payload-build-defendant-data.utils'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; import { IFinesAccEnfOverrideAddChangeFormState } from '../fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface'; -import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from '../../services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant'; @Injectable({ providedIn: 'root', @@ -163,7 +162,6 @@ export class FinesAccPayloadService { */ public buildCommentsFormPayload(formState: IFinesAccAddCommentsFormState): IOpalFinesUpdateDefendantAccountPayload { return { - ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: formState.facc_add_comment || null, free_text_note_1: formState.facc_add_free_text_1 || null, @@ -189,7 +187,6 @@ export class FinesAccPayloadService { fenf_account_enforcement_lja: lja_id, } = formState; return { - ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, enforcement_override: { enforcement_override_result: enforcement_override_result_id ? { diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts deleted file mode 100644 index b9fb0ce6fd..0000000000 --- a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IOpalFinesUpdateDefendantAccountPayload } from '../interfaces/opal-fines-update-defendant-account.interface'; - -export const OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS: IOpalFinesUpdateDefendantAccountPayload = { - comment_and_notes: null, - enforcement_override: null, -}; diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts index 09d92d1f52..26eddbfd54 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts @@ -5,6 +5,6 @@ import { IOpalFinesUpdateDefendantAccountEnforcementOverride } from './opal-fine * Interface for the payload to update a defendant account *Subject to change */ export interface IOpalFinesUpdateDefendantAccountPayload { - comment_and_notes: IOpalFinesUpdateDefendantAccountCommentsNotes | null; - enforcement_override: IOpalFinesUpdateDefendantAccountEnforcementOverride | null; + comment_and_notes?: IOpalFinesUpdateDefendantAccountCommentsNotes; + enforcement_override?: IOpalFinesUpdateDefendantAccountEnforcementOverride | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index 335c29ca00..c1ddefd19b 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -56,7 +56,6 @@ import { FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK } from '../../fines-acc/fi import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from './constants/opal-fines-defendant-account-patch-payload-defaults.constant'; import { OPAL_FINES_ENFORCER_MOCK } from './mocks/opal-fines-enforcer.mock'; describe('OpalFines', () => { @@ -1217,7 +1216,6 @@ describe('OpalFines', () => { it('should return a mock response for patching defendant account', () => { const accountId = 123456; const updatePayload = { - ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: 'Updated comment', free_text_note_1: 'Updated note 1', @@ -1239,7 +1237,6 @@ describe('OpalFines', () => { it('should handle different payload values in mock response for patching defendant account', () => { const accountId = 789012; const updatePayload = { - ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 5, comment_and_notes: { account_comment: 'Different comment', @@ -1262,7 +1259,15 @@ describe('OpalFines', () => { describe('patchDefendantAccount headers', () => { it('should include If-Match and Business-Unit-Id headers when both are provided', () => { const accountId = 123456; - const payload = OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS; + const payload = { + version: 10, + comment_and_notes: { + account_comment: 'Test comment', + free_text_note_1: null, + free_text_note_2: null, + free_text_note_3: null, + }, + }; const version = '10'; const businessUnitId = '2002'; @@ -1279,7 +1284,15 @@ describe('OpalFines', () => { it('should include only Business-Unit-Id when version is not provided', () => { const accountId = 123456; - const payload = OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS; + const payload = { + version: 10, + comment_and_notes: { + account_comment: 'Test comment', + free_text_note_1: null, + free_text_note_2: null, + free_text_note_3: null, + }, + }; const businessUnitId = '2002'; service.patchDefendantAccount(accountId, payload, undefined, businessUnitId).subscribe((response) => { @@ -1295,7 +1308,15 @@ describe('OpalFines', () => { it('should not include If-Match and should include Business-Unit-Id when version is empty string and business unit is empty string', () => { const accountId = 123456; - const payload = OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS; + const payload = { + version: 10, + comment_and_notes: { + account_comment: 'Test comment', + free_text_note_1: null, + free_text_note_2: null, + free_text_note_3: null, + }, + }; const version = ''; const businessUnitId = ''; From 82602acfec8445ac9e62deaf0cd2b0cd1aa9f538 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Mon, 30 Mar 2026 11:38:16 +0100 Subject: [PATCH 33/37] feat: implement enforcement override form actions and assertions --- .../details.enforcement.actions.ts | 113 ++++++++++++++++ .../opal/flows/account-enquiry.flow.ts | 127 ++++++++++++++++++ .../account.enquiry.enforcement.locators.ts | 3 + .../searchForAccount/account-enquiry.steps.ts | 103 ++++++++++++++ 4 files changed, 346 insertions(+) diff --git a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts index ea3388f32e..a7b4f9fe89 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts @@ -14,6 +14,24 @@ const log = createScopedLogger('AccountDetailsEnforcementActions'); export class AccountDetailsEnforcementActions { private static readonly DEFAULT_TIMEOUT = 15_000; + /** + * Selects an option from an accessible autocomplete control. + * + * @param selector - Input selector for the autocomplete. + * @param query - Text to type into the input to filter options. + * @param optionText - Visible option text to select from the dropdown. + */ + private selectAutocompleteOption(selector: string, query: string, optionText: string): void { + cy.get(selector, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .click() + .type('{selectall}{backspace}', { force: true }) + .type(query, { delay: 0 }); + + cy.contains(ENF_OVR.dropdownOptions, optionText, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).click(); + cy.get(selector, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should('contain.value', optionText); + } + /** * Asserts the Enforcement tab content is visible. */ @@ -46,4 +64,99 @@ export class AccountDetailsEnforcementActions { 'be.visible', ); } + + /** + * Selects an enforcement override from the add form. + * + * @param resultCode - Enforcement override code to select. + */ + public selectEnforcementOverride(resultCode: string): void { + log('action', 'Selecting enforcement override', { resultCode }); + this.selectAutocompleteOption(ENF_OVR.enfOverrideDropdown, resultCode, resultCode); + } + + /** + * Selects a Local Justice Area from the add form. + * + * @param localJusticeArea - Visible LJA option text. + */ + public selectLocalJusticeArea(localJusticeArea: string): void { + log('action', 'Selecting Local Justice Area', { localJusticeArea }); + this.selectAutocompleteOption(ENF_OVR.localJusticeAreaDropdown, localJusticeArea, localJusticeArea); + } + + /** + * Selects an enforcer from the add form. + * + * @param enforcer - Visible enforcer option text. + */ + public selectEnforcer(enforcer: string): void { + log('action', 'Selecting enforcer', { enforcer }); + this.selectAutocompleteOption(ENF_OVR.enforcerDropdown, enforcer, enforcer); + } + + /** + * Submits the add enforcement override form. + */ + public submitAddEnforcementOverride(): void { + log('action', 'Submitting add enforcement override form'); + cy.get(ENF_OVR.addOverrideButton, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .and('not.be.disabled') + .click(); + } + + /** + * Clicks the cancel link on the add enforcement override form. + */ + public cancelAddEnforcementOverride(): void { + log('action', 'Cancelling add enforcement override form'); + cy.get(ENF_OVR.cancelLink, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).contains(/^Cancel$/i).click(); + } + + /** + * Asserts the success banner text shown after saving. + * + * @param expected - Expected success message. + */ + public assertSuccessBannerText(expected: string): void { + log('assert', 'Enforcement success banner text', { expected }); + cy.get(ENF.successBanner, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .find(ENF.successBannerText) + .should('contain.text', expected); + } + + /** + * Asserts the enforcement override summary values on the Enforcement tab. + * + * @param expected - Expected summary values. + * @param expected.override - Expected enforcement override display text. + * @param expected.enforcer - Expected enforcer display text. + * @param expected.lja - Expected Local Justice Area display text. + */ + public assertEnforcementOverrideSummary(expected: { override?: string; enforcer?: string; lja?: string }): void { + log('assert', 'Enforcement override summary', expected); + + if (expected.override) { + cy.get(ENF.enforcementOverrideValue, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'contain.text', + expected.override, + ); + } + + if (expected.lja) { + cy.get(ENF.localJusticeAreaValue, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'contain.text', + expected.lja, + ); + } + + if (expected.enforcer) { + cy.get(ENF.enfOverrideEnforcer, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'contain.text', + expected.enforcer, + ); + } + } } diff --git a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts index 5bad2d534d..77d2fe3cbf 100644 --- a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts +++ b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts @@ -274,6 +274,126 @@ export class AccountEnquiryFlow { this.enforcement.assertAddEnforcementOverrideFormVisible(); } + /** + * Selects an enforcement override code on the add form. + * + * @param resultCode - Enforcement override result code. + */ + public selectEnforcementOverride(resultCode: string): void { + logAE('method', 'selectEnforcementOverride()', { resultCode }); + this.enforcement.selectEnforcementOverride(resultCode); + } + + /** + * Selects a Local Justice Area on the add form. + * + * @param localJusticeArea - Visible LJA option text. + */ + public selectEnforcementOverrideLocalJusticeArea(localJusticeArea: string): void { + logAE('method', 'selectEnforcementOverrideLocalJusticeArea()', { localJusticeArea }); + this.enforcement.selectLocalJusticeArea(localJusticeArea); + } + + /** + * Selects an enforcer on the add form. + * + * @param enforcer - Visible enforcer option text. + */ + public selectEnforcementOverrideEnforcer(enforcer: string): void { + logAE('method', 'selectEnforcementOverrideEnforcer()', { enforcer }); + this.enforcement.selectEnforcer(enforcer); + } + + /** + * Submits the add enforcement override form and stores the request body for later assertions. + */ + public submitAddEnforcementOverride(): void { + logAE('method', 'submitAddEnforcementOverride()'); + + this.interceptEnforcementOverrideSave(); + this.enforcement.submitAddEnforcementOverride(); + + cy.wait('@enforcementOverrideSave').then(({ request }) => { + cy.wrap(request.body, { log: false }).as('enforcementOverrideSaveBody'); + }); + + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Cancels the add enforcement override form after confirming unsaved changes should be discarded. + */ + public cancelAddEnforcementOverrideAndDiscardChanges(): void { + logAE('method', 'cancelAddEnforcementOverrideAndDiscardChanges()'); + + this.common.confirmNextUnsavedChanges(true); + this.enforcement.cancelAddEnforcementOverride(); + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Asserts the Enforcement tab is active and visible. + */ + public assertEnforcementTabIsActive(): void { + logAE('method', 'assertEnforcementTabIsActive()'); + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Asserts the enforcement override success banner text. + * + * @param expected - Expected success banner message. + */ + public assertEnforcementOverrideSuccessBanner(expected: string): void { + logAE('method', 'assertEnforcementOverrideSuccessBanner()', { expected }); + this.enforcement.assertSuccessBannerText(expected); + } + + /** + * Asserts the intercepted enforcement override save payload. + * + * @param expected - Expected payload values. + * @param expected.overrideId - Expected enforcement override result id. + * @param expected.enforcerId - Expected enforcer id. + * @param expected.ljaId - Expected local justice area id. + */ + public assertEnforcementOverrideSaveRequest(expected: { overrideId?: string; enforcerId?: string; ljaId?: string }): void { + logAE('method', 'assertEnforcementOverrideSaveRequest()', expected); + + cy.get('@enforcementOverrideSaveBody').then((body: any) => { + if (expected.overrideId) { + expect(body).to.have.nested.property( + 'enforcement_override.enforcement_override_result.enforcement_override_result_id', + expected.overrideId, + ); + } + + if (expected.ljaId) { + expect(String(body?.enforcement_override?.lja?.lja_id)).to.eq(expected.ljaId); + } + + if (expected.enforcerId) { + expect(String(body?.enforcement_override?.enforcer?.enforcer_id)).to.eq(expected.enforcerId); + } + }); + } + + /** + * Asserts the enforcement override summary card values. + * + * @param expected - Expected summary values. + * @param expected.override - Expected enforcement override display text. + * @param expected.enforcer - Expected enforcer display text. + * @param expected.lja - Expected LJA display text. + */ + public assertEnforcementOverrideSummary(expected: { override?: string; enforcer?: string; lja?: string }): void { + logAE('method', 'assertEnforcementOverrideSummary()', expected); + this.enforcement.assertEnforcementOverrideSummary(expected); + } + /** * Submits instalments-only payment terms with a payment card request. * Stores the POST request body for later assertions. @@ -629,6 +749,13 @@ export class AccountEnquiryFlow { cy.intercept('POST', '**/defendant-accounts/*/payment-terms').as('paymentTermsSave'); } + /** + * Intercepts the enforcement override save request for later assertions. + */ + private interceptEnforcementOverrideSave(): void { + cy.intercept('PATCH', '**/defendant-accounts/*').as('enforcementOverrideSave'); + } + /** * Default headers for Account Enquiry API requests. * @returns JSON and accept headers used for API calls. diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts index eb331d2197..822bcdc6f3 100644 --- a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts @@ -42,6 +42,9 @@ export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { localJusticeArea: '#enforcementOverrideDetailsLocal_justice_areaKey', localJusticeAreaValue: '#enforcementOverrideDetailsLocal_justice_areaValue', + successBanner: 'opal-lib-moj-alert[type="success"]', + successBannerText: 'opal-lib-moj-alert-content-text', + detailsDaysInDefault: '[id="enforcementActionDetailsDays in defaultKey"]', detailsReason: '#enforcementActionDetailsReasonKey', diff --git a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts index d95cb6ffc5..7ae20d2bf7 100644 --- a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts +++ b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts @@ -156,6 +156,66 @@ When('I open the add enforcement override form', () => { flow().openAddEnforcementOverrideForm(); }); +/** + * @step Selects an enforcement override on the add form. + */ +When('I choose the enforcement override {string}', (resultCode: string) => { + log('step', 'Choose enforcement override', { resultCode }); + flow().selectEnforcementOverride(resultCode); +}); + +/** + * @step Selects a Local Justice Area on the add form. + */ +When('I choose the Local Justice Area {string}', (localJusticeArea: string) => { + log('step', 'Choose Local Justice Area', { localJusticeArea }); + flow().selectEnforcementOverrideLocalJusticeArea(localJusticeArea); +}); + +/** + * @step Selects an enforcer on the add form. + */ +When('I choose the enforcer {string}', (enforcer: string) => { + log('step', 'Choose enforcer', { enforcer }); + flow().selectEnforcementOverrideEnforcer(enforcer); +}); + +/** + * @step Submits the add enforcement override form. + */ +When('I add the enforcement override', () => { + log('step', 'Submit add enforcement override form'); + flow().submitAddEnforcementOverride(); +}); + +/** + * @step Completes the add enforcement override form with the provided values and submits it. + */ +When('I add the enforcement override {string} with the Local Justice Area {string}', (resultCode: string, lja: string) => { + log('step', 'Add enforcement override with Local Justice Area', { resultCode, lja }); + flow().selectEnforcementOverride(resultCode); + flow().selectEnforcementOverrideLocalJusticeArea(lja); + flow().submitAddEnforcementOverride(); +}); + +/** + * @step Completes the add enforcement override form with the provided values and submits it. + */ +When('I add the enforcement override {string} with the enforcer {string}', (resultCode: string, enforcer: string) => { + log('step', 'Add enforcement override with enforcer', { resultCode, enforcer }); + flow().selectEnforcementOverride(resultCode); + flow().selectEnforcementOverrideEnforcer(enforcer); + flow().submitAddEnforcementOverride(); +}); + +/** + * @step Cancels the add enforcement override form and discards changes. + */ +When('I cancel the add enforcement override form and discard changes', () => { + log('step', 'Cancel add enforcement override form and discard changes'); + flow().cancelAddEnforcementOverrideAndDiscardChanges(); +}); + /** * @step Submits instalments-only payment terms with a payment card request. */ @@ -180,6 +240,49 @@ Then('I should return to the Payment terms tab', () => { flow().assertPaymentTermsTabIsActive(); }); +/** + * @step Asserts the Enforcement tab is active. + */ +Then('I should return to the Enforcement tab', () => { + log('assert', 'Enforcement tab is active'); + flow().assertEnforcementTabIsActive(); +}); + +/** + * @step Asserts the enforcement override success banner text. + */ +Then('the enforcement override success banner is {string}', (expected: string) => { + log('assert', 'Enforcement override success banner text', { expected }); + flow().assertEnforcementOverrideSuccessBanner(expected); +}); + +/** + * @step Asserts the intercepted enforcement override save request body. + */ +Then('the enforcement override save request shows:', (table: DataTable) => { + const rows = rowsHashSafe(table); + const overrideId = + rows['enforcement override result id'] ?? rows['enforcement override'] ?? rows['override result id'] ?? ''; + const enforcerId = rows['enforcer id'] ?? ''; + const ljaId = rows['lja id'] ?? rows['local justice area id'] ?? ''; + + log('assert', 'Enforcement override save request payload', { overrideId, enforcerId, ljaId }); + flow().assertEnforcementOverrideSaveRequest({ overrideId, enforcerId, ljaId }); +}); + +/** + * @step Asserts the enforcement override summary card values. + */ +Then('the enforcement override summary shows:', (table: DataTable) => { + const rows = rowsHashSafe(table); + const override = rows['enforcement override'] ?? ''; + const enforcer = rows['enforcer'] ?? ''; + const lja = rows['local justice area'] ?? rows['local justice area (lja)'] ?? ''; + + log('assert', 'Enforcement override summary values', { override, enforcer, lja }); + flow().assertEnforcementOverrideSummary({ override, enforcer, lja }); +}); + /** * @step Asserts payment terms instalment summary values. */ From 6a0685a4a2fefefd46889e93072451dcd5e54cad Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Mon, 30 Mar 2026 11:38:22 +0100 Subject: [PATCH 34/37] feat: add enforcement override feature for account enquiries --- ...untEnquiriesAddEnforcementOverride.feature | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature diff --git a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature new file mode 100644 index 0000000000..0d7ac77bf3 --- /dev/null +++ b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature @@ -0,0 +1,67 @@ +@JIRA-LABEL:account-enquiry +Feature: Account Enquiries - Add Enforcement Override + As an Opal user + I want to add an enforcement override from account enquiry + So that the override is saved and shown on the Enforcement tab + + Background: + Given I am logged in with email "opal-test@dev.platform.hmcts.net" + And I clear all approved accounts + + Rule: Adult or youth account + Background: + Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.forenames | Evan | + | account.defendant.surname | AddEnfOverride{uniq} | + | account.defendant.email_address_1 | evan.override{uniq}@test.com | + | account.defendant.telephone_number_home | 02078259316 | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-019 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.defendant.dob | 2002-05-15 | + | account.payment_terms.enforcements[0].result_id | PRIS | + + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 + Scenario: Save an enforcement override and return to the Enforcement tab + When I search for the account by last name "AddEnfOverride{uniq}" and open the latest result + And I go to the Enforcement tab + And I open the add enforcement override form + When I add the enforcement override "ABDC" with the enforcer "The DWP (3)" + Then I should return to the Enforcement tab + And the enforcement override success banner is "Enforcement override added" + And the enforcement override save request shows: + | enforcement override result id | ABDC | + | enforcer id | 770000000003 | + And the enforcement override summary shows: + | enforcement override | Application made for Benefit Deductions(ABDC) | + + Rule: Company account + Background: + Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.company_name | Add Override Company{uniq} | + | account.defendant.email_address_1 | add.override.company{uniq}@test.com | + | account.defendant.post_code | AB23 4RN | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-023 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.payment_terms.enforcements[0].result_id | PRIS | + + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 + Scenario: Company save an enforcement override and return to the Enforcement tab + When I open the company account details for "Add Override Company{uniq}" + And I go to the Enforcement tab + And I open the add enforcement override form + When I add the enforcement override "ABDC" with the enforcer "The DWP (3)" + Then I should return to the Enforcement tab + And the enforcement override success banner is "Enforcement override added" + And the enforcement override save request shows: + | enforcement override result id | ABDC | + | enforcer id | 770000000003 | + And the enforcement override summary shows: + | enforcement override | Application made for Benefit Deductions(ABDC) | From 5e8a22118bf438e142d34197cac2ec50b429974a Mon Sep 17 00:00:00 2001 From: louisbriggs Date: Mon, 30 Mar 2026 11:55:54 +0100 Subject: [PATCH 35/37] Re-applying grouped payload for patch defendant account. --- .../fines-acc/services/fines-acc-payload.service.spec.ts | 2 ++ .../fines/fines-acc/services/fines-acc-payload.service.ts | 3 +++ ...nes-defendant-account-patch-payload-defaults.constant.ts | 6 ++++++ .../opal-fines-update-defendant-account.interface.ts | 4 ++-- .../services/opal-fines-service/opal-fines.service.spec.ts | 6 ++++++ 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts index 69988df8bf..74a28b2b30 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts @@ -545,6 +545,7 @@ describe('FinesAccPayloadService', () => { free_text_note_2: 'Updated note 2', free_text_note_3: 'Updated note 3', }, + enforcement_override: null, }); }); @@ -565,6 +566,7 @@ describe('FinesAccPayloadService', () => { free_text_note_2: null, free_text_note_3: null, }, + enforcement_override: null, }); }); diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts index 12f9231b73..06959f87ef 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts @@ -26,6 +26,7 @@ import { buildPaymentTermsAmendPayloadUtil } from './utils/fines-acc-payload-bui import { buildAccountPartyFromFormState } from './utils/fines-acc-payload-build-defendant-data.utils'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; import { IFinesAccEnfOverrideAddChangeFormState } from '../fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from '../../services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant'; @Injectable({ providedIn: 'root', @@ -162,6 +163,7 @@ export class FinesAccPayloadService { */ public buildCommentsFormPayload(formState: IFinesAccAddCommentsFormState): IOpalFinesUpdateDefendantAccountPayload { return { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: formState.facc_add_comment || null, free_text_note_1: formState.facc_add_free_text_1 || null, @@ -187,6 +189,7 @@ export class FinesAccPayloadService { fenf_account_enforcement_lja: lja_id, } = formState; return { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, enforcement_override: { enforcement_override_result: enforcement_override_result_id ? { diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts new file mode 100644 index 0000000000..b9fb0ce6fd --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts @@ -0,0 +1,6 @@ +import { IOpalFinesUpdateDefendantAccountPayload } from '../interfaces/opal-fines-update-defendant-account.interface'; + +export const OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS: IOpalFinesUpdateDefendantAccountPayload = { + comment_and_notes: null, + enforcement_override: null, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts index 26eddbfd54..09d92d1f52 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts @@ -5,6 +5,6 @@ import { IOpalFinesUpdateDefendantAccountEnforcementOverride } from './opal-fine * Interface for the payload to update a defendant account *Subject to change */ export interface IOpalFinesUpdateDefendantAccountPayload { - comment_and_notes?: IOpalFinesUpdateDefendantAccountCommentsNotes; - enforcement_override?: IOpalFinesUpdateDefendantAccountEnforcementOverride | null; + comment_and_notes: IOpalFinesUpdateDefendantAccountCommentsNotes | null; + enforcement_override: IOpalFinesUpdateDefendantAccountEnforcementOverride | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index c1ddefd19b..c9338550cc 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -57,6 +57,7 @@ import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-re import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { OPAL_FINES_ENFORCER_MOCK } from './mocks/opal-fines-enforcer.mock'; +import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from './constants/opal-fines-defendant-account-patch-payload-defaults.constant'; describe('OpalFines', () => { let service: OpalFines; @@ -1216,6 +1217,7 @@ describe('OpalFines', () => { it('should return a mock response for patching defendant account', () => { const accountId = 123456; const updatePayload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: 'Updated comment', free_text_note_1: 'Updated note 1', @@ -1237,6 +1239,7 @@ describe('OpalFines', () => { it('should handle different payload values in mock response for patching defendant account', () => { const accountId = 789012; const updatePayload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 5, comment_and_notes: { account_comment: 'Different comment', @@ -1260,6 +1263,7 @@ describe('OpalFines', () => { it('should include If-Match and Business-Unit-Id headers when both are provided', () => { const accountId = 123456; const payload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 10, comment_and_notes: { account_comment: 'Test comment', @@ -1285,6 +1289,7 @@ describe('OpalFines', () => { it('should include only Business-Unit-Id when version is not provided', () => { const accountId = 123456; const payload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 10, comment_and_notes: { account_comment: 'Test comment', @@ -1309,6 +1314,7 @@ describe('OpalFines', () => { it('should not include If-Match and should include Business-Unit-Id when version is empty string and business unit is empty string', () => { const accountId = 123456; const payload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 10, comment_and_notes: { account_comment: 'Test comment', From 9fa6942abc03cbe99caa682311f9ed1941f48c01 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Mon, 30 Mar 2026 17:21:03 +0100 Subject: [PATCH 36/37] feat: update functional weights for account enquiry features --- cypress/parallel/weights/functional-parallel-weights.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/parallel/weights/functional-parallel-weights.json b/cypress/parallel/weights/functional-parallel-weights.json index c5075dd8af..fa85bbd955 100644 --- a/cypress/parallel/weights/functional-parallel-weights.json +++ b/cypress/parallel/weights/functional-parallel-weights.json @@ -1 +1 @@ -{"cypress/e2e/functional/opal/features/consolidation/FineAccountConsolidation.feature":{"time":6818,"weight":2},"cypress/e2e/functional/opal/features/consolidation/FinesAccountConsolidationAccessibility.feature":{"time":5801,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummary.feature":{"time":36372,"weight":11},"cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummaryAccessibility.feature":{"time":23523,"weight":7},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTerms.feature":{"time":39830,"weight":12},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTermsAccessibility.feature":{"time":7240,"weight":2},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCard.feature":{"time":7158,"weight":2},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCardAccessibility.feature":{"time":6716,"weight":2},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetails.feature":{"time":74496,"weight":23},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetailsAccessibility.feature":{"time":9986,"weight":3},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNote.feature":{"time":36028,"weight":11},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNoteAccessibility.feature":{"time":11601,"weight":3},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatches.feature":{"time":85126,"weight":26},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatchesAccessibility.feature":{"time":5790,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchForAccountFilterByBU.feature":{"time":11671,"weight":3},"cypress/e2e/functional/opal/features/globalApiInterceptor.feature":{"time":48272,"weight":14},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccounts.feature":{"time":43834,"weight":13},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccountsE2ETech.feature":{"time":23160,"weight":7},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/draftAccountsConcurrency.feature":{"time":5148,"weight":1},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidate.feature":{"time":63923,"weight":19},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidateAccessibility.feature":{"time":17748,"weight":5},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/fixedPenaltyFailedAccounts.feature":{"time":14573,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenalty.feature":{"time":94533,"weight":29},"cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenaltyAccessibility.feature":{"time":3948,"weight":1},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmit.feature":{"time":135902,"weight":42},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmitAccessibility.feature":{"time":35148,"weight":10},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/AccountCommentsAndNotes.feature":{"time":13340,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CompanyDetails.feature":{"time":17982,"weight":5},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ContactDetails.feature":{"time":20648,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CourtDetails.feature":{"time":22659,"weight":7},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/EmployerDetails.feature":{"time":20335,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LanguagePreferences.feature":{"time":8129,"weight":2},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LocalJusticeAreasFiltering.feature":{"time":7335,"weight":2},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/OffenceDetails.feature":{"time":167890,"weight":52},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ParentGuardianDetails.feature":{"time":29915,"weight":9},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PaymentTerms.feature":{"time":30409,"weight":9},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PersonalDetails.feature":{"time":28349,"weight":8},"cypress/e2e/functional/opal/features/reciprocalMaintenance/dummyTest.feature":{"time":1679,"weight":0}} \ No newline at end of file +{"cypress/e2e/functional/opal/features/consolidation/FineAccountConsolidation.feature":{"time":6262,"weight":2},"cypress/e2e/functional/opal/features/consolidation/FinesAccountConsolidationAccessibility.feature":{"time":3397,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummary.feature":{"time":18108,"weight":8},"cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummaryAccessibility.feature":{"time":13270,"weight":5},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature":{"time":7392,"weight":3},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTerms.feature":{"time":25066,"weight":11},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTermsAccessibility.feature":{"time":3132,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCard.feature":{"time":3277,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCardAccessibility.feature":{"time":2905,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetails.feature":{"time":46493,"weight":20},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetailsAccessibility.feature":{"time":6675,"weight":2},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature":{"time":16998,"weight":7},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNote.feature":{"time":26186,"weight":11},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNoteAccessibility.feature":{"time":7724,"weight":3},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatches.feature":{"time":63683,"weight":28},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatchesAccessibility.feature":{"time":3650,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchForAccountFilterByBU.feature":{"time":7899,"weight":3},"cypress/e2e/functional/opal/features/globalApiInterceptor.feature":{"time":44363,"weight":19},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccounts.feature":{"time":27448,"weight":12},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccountsE2ETech.feature":{"time":10012,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/draftAccountsConcurrency.feature":{"time":2426,"weight":1},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidate.feature":{"time":39955,"weight":17},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidateAccessibility.feature":{"time":12920,"weight":5},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/fixedPenaltyFailedAccounts.feature":{"time":10577,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenalty.feature":{"time":74293,"weight":33},"cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenaltyAccessibility.feature":{"time":3166,"weight":1},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmit.feature":{"time":98678,"weight":44},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmitAccessibility.feature":{"time":23971,"weight":10},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/AccountCommentsAndNotes.feature":{"time":12522,"weight":5},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CompanyDetails.feature":{"time":15870,"weight":7},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ContactDetails.feature":{"time":14275,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CourtDetails.feature":{"time":15399,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/EmployerDetails.feature":{"time":15351,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LanguagePreferences.feature":{"time":5857,"weight":2},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LocalJusticeAreasFiltering.feature":{"time":6333,"weight":2},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/OffenceDetails.feature":{"time":140263,"weight":62},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ParentGuardianDetails.feature":{"time":20200,"weight":9},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PaymentTerms.feature":{"time":19376,"weight":8},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PersonalDetails.feature":{"time":18238,"weight":8},"cypress/e2e/functional/opal/features/reciprocalMaintenance/dummyTest.feature":{"time":679,"weight":0}} \ No newline at end of file From 2ab860541f82a430781a1e300a6f595bda2ff788 Mon Sep 17 00:00:00 2001 From: cadefaulkner Date: Mon, 30 Mar 2026 17:22:09 +0100 Subject: [PATCH 37/37] refactor: improve code formatting and readability in enforcement actions and account enquiry flow --- .../details.enforcement.actions.ts | 8 ++++++-- .../functional/opal/flows/account-enquiry.flow.ts | 6 +++++- .../searchForAccount/account-enquiry.steps.ts | 15 +++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts index a7b4f9fe89..3ef3aabb01 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts @@ -28,7 +28,9 @@ export class AccountDetailsEnforcementActions { .type('{selectall}{backspace}', { force: true }) .type(query, { delay: 0 }); - cy.contains(ENF_OVR.dropdownOptions, optionText, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).click(); + cy.contains(ENF_OVR.dropdownOptions, optionText, { + timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT, + }).click(); cy.get(selector, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should('contain.value', optionText); } @@ -111,7 +113,9 @@ export class AccountDetailsEnforcementActions { */ public cancelAddEnforcementOverride(): void { log('action', 'Cancelling add enforcement override form'); - cy.get(ENF_OVR.cancelLink, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).contains(/^Cancel$/i).click(); + cy.get(ENF_OVR.cancelLink, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .contains(/^Cancel$/i) + .click(); } /** diff --git a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts index 8e0378b808..6f436a03f2 100644 --- a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts +++ b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts @@ -371,7 +371,11 @@ export class AccountEnquiryFlow { * @param expected.enforcerId - Expected enforcer id. * @param expected.ljaId - Expected local justice area id. */ - public assertEnforcementOverrideSaveRequest(expected: { overrideId?: string; enforcerId?: string; ljaId?: string }): void { + public assertEnforcementOverrideSaveRequest(expected: { + overrideId?: string; + enforcerId?: string; + ljaId?: string; + }): void { logAE('method', 'assertEnforcementOverrideSaveRequest()', expected); cy.get('@enforcementOverrideSaveBody').then((body: any) => { diff --git a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts index 7ae20d2bf7..8cce3d8215 100644 --- a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts +++ b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts @@ -191,12 +191,15 @@ When('I add the enforcement override', () => { /** * @step Completes the add enforcement override form with the provided values and submits it. */ -When('I add the enforcement override {string} with the Local Justice Area {string}', (resultCode: string, lja: string) => { - log('step', 'Add enforcement override with Local Justice Area', { resultCode, lja }); - flow().selectEnforcementOverride(resultCode); - flow().selectEnforcementOverrideLocalJusticeArea(lja); - flow().submitAddEnforcementOverride(); -}); +When( + 'I add the enforcement override {string} with the Local Justice Area {string}', + (resultCode: string, lja: string) => { + log('step', 'Add enforcement override with Local Justice Area', { resultCode, lja }); + flow().selectEnforcementOverride(resultCode); + flow().selectEnforcementOverrideLocalJusticeArea(lja); + flow().submitAddEnforcementOverride(); + }, +); /** * @step Completes the add enforcement override form with the provided values and submits it.