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 79213afe10..932ac37121 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
@@ -20,6 +20,7 @@
Enforcement status
? true
: false
"
+ (actionClick)="handleChangeCollectionOrder()"
>
Collection Order status
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 bcace27490..604e614ed9 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
@@ -31,4 +31,24 @@ describe('FinesAccDefendantDetailsEnforcementTab', () => {
expect(event.preventDefault).toHaveBeenCalled();
expect(eventEmitterSpy).toHaveBeenCalled();
});
+
+ it('should emit changeCollectionOrder when handleChangeCollectionOrder is called', () => {
+ const eventEmitterSpy = vi.spyOn(component.changeCollectionOrder, 'emit');
+ component.hasAccountMaintenancePermission = true;
+
+ component.handleChangeCollectionOrder();
+
+ expect(eventEmitterSpy).toHaveBeenCalledWith(
+ component.tabData.enforcement_overview.collection_order?.collection_order_flag ?? false,
+ );
+ });
+
+ it('should not emit changeCollectionOrder when the user lacks account maintenance permission', () => {
+ const eventEmitterSpy = vi.spyOn(component.changeCollectionOrder, 'emit');
+ component.hasAccountMaintenancePermission = false;
+
+ component.handleChangeCollectionOrder();
+
+ expect(eventEmitterSpy).not.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 19ec837685..288dbbfda7 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
@@ -39,6 +39,7 @@ export class FinesAccDefendantDetailsEnforcementTab {
@Input() hasAccountMaintenancePermission: boolean = false;
@Input() hasEnterEnforcementPermission: boolean = false;
@Output() addEnforcementOverride = new EventEmitter();
+ @Output() changeCollectionOrder = 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.
@@ -53,4 +54,15 @@ export class FinesAccDefendantDetailsEnforcementTab {
this.addEnforcementOverride.emit();
}
}
+
+ /**
+ * Emits an event to change the collection order status.
+ */
+ public handleChangeCollectionOrder(): void {
+ if (this.hasAccountMaintenancePermission) {
+ this.changeCollectionOrder.emit(
+ this.tabData.enforcement_overview.collection_order?.collection_order_flag ?? false,
+ );
+ }
+ }
}
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 39396c237f..c15b602227 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
@@ -206,6 +206,7 @@ Business Unit:
[hasAccountMaintenancePermission]="hasPermission('account-maintenance')"
[hasEnterEnforcementPermission]="hasPermission('enter-enforcement')"
(addEnforcementOverride)="navigateToAddEnforcementOverridePage()"
+ (changeCollectionOrder)="navigateToChangeCollectionOrderPage($event)"
>
}
}
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 feaa9c5f9b..725ef86051 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
@@ -168,6 +168,17 @@ describe('FinesAccDefendantDetailsComponent', () => {
);
});
+ it('should call router.navigate when navigateToChangeCollectionOrderPage is called', () => {
+ component.navigateToChangeCollectionOrderPage(true);
+ expect(routerSpy.navigate).toHaveBeenCalledWith(
+ [`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/collection-order/change`],
+ {
+ relativeTo: component['activatedRoute'],
+ state: { currentCollectionOrderFlag: true },
+ },
+ );
+ });
+
it('should fetch the defendant tab data when fragment is changed to defendant', () => {
component['refreshFragment$'].next('defendant');
// Subscribe to trigger the pipe execution
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 c8d7f1b673..0a3231aec7 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
@@ -458,4 +458,14 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement
relativeTo: this.activatedRoute,
});
}
+
+ /**
+ * Navigates to the change collection order page.
+ */
+ public navigateToChangeCollectionOrderPage(currentCollectionOrderFlag: boolean): void {
+ this['router'].navigate([`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/collection-order/change`], {
+ relativeTo: this.activatedRoute,
+ state: { currentCollectionOrderFlag },
+ });
+ }
}
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-field-errors.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-field-errors.constant.ts
new file mode 100644
index 0000000000..645cb940e2
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-field-errors.constant.ts
@@ -0,0 +1,10 @@
+import { IFinesAccEnfColloChangeFieldErrors } from '../interfaces/fines-acc-enf-collo-change-field-errors.interface';
+
+export const FINES_ACC_ENF_COLLO_CHANGE_FIELD_ERRORS: IFinesAccEnfColloChangeFieldErrors = {
+ facc_enf_collection_order_made: {
+ required: {
+ message: 'Select whether the account is subject to a Collection Order',
+ priority: 1,
+ },
+ },
+};
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-routing-titles.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-routing-titles.constant.ts
new file mode 100644
index 0000000000..23e3be0453
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-routing-titles.constant.ts
@@ -0,0 +1,8 @@
+import { IFinesAccEnfColloChangeRoutingTitles } from '../interfaces/fines-acc-enf-collo-change-routing-titles.interface';
+
+export const FINES_ACC_ENF_COLLO_CHANGE_ROUTING_TITLES: IFinesAccEnfColloChangeRoutingTitles = {
+ root: 'Collection Order',
+ children: {
+ change: 'Change Collection Order status',
+ },
+};
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-success-message.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-success-message.constant.ts
new file mode 100644
index 0000000000..512d2013a6
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-success-message.constant.ts
@@ -0,0 +1 @@
+export const FINES_ACC_ENF_COLLO_CHANGE_SUCCESS_MESSAGE = 'Collection Order status changed';
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.html
new file mode 100644
index 0000000000..da532d051d
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+If the status of a Collection Order is incorrect you can change it.
+This will not issue a new notice.
+
+ To add a new Collection Order and issue a notice you should
+ go back
+ and add an enforcement action.
+
+
+
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.spec.ts
new file mode 100644
index 0000000000..345a29a01b
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.spec.ts
@@ -0,0 +1,36 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute } from '@angular/router';
+import { beforeEach, describe, expect, it } from 'vitest';
+import { FinesAccEnfColloChangeFormComponent } from './fines-acc-enf-collo-change-form.component';
+
+describe('FinesAccEnfColloChangeFormComponent', () => {
+ let component: FinesAccEnfColloChangeFormComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [FinesAccEnfColloChangeFormComponent],
+ providers: [{ provide: ActivatedRoute, useValue: { snapshot: { params: {}, data: {} } } }],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(FinesAccEnfColloChangeFormComponent);
+ component = fixture.componentInstance;
+ component.accountNumber = '177A';
+ component.partyName = 'Mr Robert THOMSON';
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should initialize the collection order control', () => {
+ expect(component.form.get('facc_enf_collection_order_made')).toBeTruthy();
+ expect(component.form.get('facc_enf_collection_order_made')?.value).toBeNull();
+ });
+
+ it('should render the account caption in account-number-first format', () => {
+ const compiled = fixture.nativeElement as HTMLElement;
+ expect(compiled.textContent).toContain('177A - Mr Robert THOMSON');
+ });
+});
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.ts
new file mode 100644
index 0000000000..d2312cbcbb
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component.ts
@@ -0,0 +1,64 @@
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit } 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 { GovukHeadingWithCaptionComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-heading-with-caption';
+import {
+ GovukRadioComponent,
+ GovukRadiosItemComponent,
+} from '@hmcts/opal-frontend-common/components/govuk/govuk-radio';
+import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '../../routing/constants/fines-acc-defendant-routing-paths.constant';
+import { IFinesAccEnfColloChangeFormState } from '../interfaces/fines-acc-enf-collo-change-form-state.interface';
+import { FINES_ACC_ENF_COLLO_CHANGE_FIELD_ERRORS } from '../constants/fines-acc-enf-collo-change-field-errors.constant';
+
+@Component({
+ selector: 'app-fines-acc-enf-collo-change-form',
+ imports: [
+ FormsModule,
+ ReactiveFormsModule,
+ GovukButtonComponent,
+ GovukCancelLinkComponent,
+ GovukErrorSummaryComponent,
+ GovukHeadingWithCaptionComponent,
+ GovukRadioComponent,
+ GovukRadiosItemComponent,
+ ],
+ templateUrl: './fines-acc-enf-collo-change-form.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+})
+export class FinesAccEnfColloChangeFormComponent extends AbstractFormBaseComponent implements OnInit {
+ protected override fieldErrors: IAbstractFormBaseFieldErrors = {
+ ...FINES_ACC_ENF_COLLO_CHANGE_FIELD_ERRORS,
+ };
+ protected override formSubmit = new EventEmitter>();
+ public override formControlErrorMessages: IAbstractFormControlErrorMessage = {};
+ public readonly defendantAccRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS;
+
+ @Input({ required: true }) partyName!: string;
+ @Input({ required: true }) accountNumber!: string;
+
+ /**
+ * Creates the form group for changing the Collection Order status.
+ */
+ private setupForm(): void {
+ this.form = new FormGroup({
+ facc_enf_collection_order_made: new FormControl(null, Validators.required),
+ });
+ }
+
+ /**
+ * Initialises the form before running the shared abstract form setup.
+ */
+ public override ngOnInit(): void {
+ this.setupForm();
+ super.ngOnInit();
+ }
+}
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.html
new file mode 100644
index 0000000000..2a44580914
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.html
@@ -0,0 +1,8 @@
+
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.spec.ts
new file mode 100644
index 0000000000..3c47d61966
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.spec.ts
@@ -0,0 +1,226 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute, Navigation, Router } from '@angular/router';
+import { signal, type WritableSignal } from '@angular/core';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { of, throwError } from 'rxjs';
+import { FinesAccEnfColloChangeComponent } from './fines-acc-enf-collo-change.component';
+import { FinesAccountStore } from '../stores/fines-acc.store';
+import { FinesAccPayloadService } from '../services/fines-acc-payload.service';
+import { OpalFines } from '../../services/opal-fines-service/opal-fines.service';
+import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service';
+import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '../routing/constants/fines-acc-defendant-routing-paths.constant';
+import { FinesAccountStoreType } from '../types/fines-account-store.type';
+import { FINES_ACC_ENF_COLLO_CHANGE_SUCCESS_MESSAGE } from './constants/fines-acc-enf-collo-change-success-message.constant';
+
+describe('FinesAccEnfColloChangeComponent', () => {
+ let component: FinesAccEnfColloChangeComponent;
+ let fixture: ComponentFixture;
+ let mockRoute: ActivatedRoute;
+ let mockRouter: Pick;
+ let mockCurrentNavigation: WritableSignal;
+ let mockAccountStore: Pick<
+ FinesAccountStoreType,
+ 'getAccountNumber' | 'party_name' | 'account_id' | 'base_version' | 'business_unit_id' | 'setSuccessMessage'
+ >;
+ let mockPayloadService: Pick;
+ let mockOpalFinesService: Pick;
+ let mockUtilsService: Pick;
+
+ beforeEach(async () => {
+ mockRoute = {
+ snapshot: {
+ data: {},
+ },
+ } as ActivatedRoute;
+
+ mockCurrentNavigation = signal({
+ extras: {
+ state: {},
+ },
+ } as unknown as Navigation);
+
+ mockRouter = {
+ currentNavigation: mockCurrentNavigation,
+ navigate: vi.fn(),
+ };
+
+ mockAccountStore = {
+ getAccountNumber: signal('177A'),
+ party_name: signal('Mr Robert THOMSON'),
+ account_id: signal(1001),
+ base_version: signal('1'),
+ business_unit_id: signal('2002'),
+ setSuccessMessage: vi.fn(),
+ };
+
+ mockPayloadService = {
+ buildCollectionOrderPayload: vi.fn().mockImplementation((form) => ({
+ collection_order: {
+ collection_order_date: null,
+ collection_order_flag: form.formData.facc_enf_collection_order_made,
+ },
+ })),
+ };
+
+ mockOpalFinesService = {
+ patchDefendantAccount: vi.fn().mockReturnValue(of({})),
+ clearCache: vi.fn(),
+ };
+
+ mockUtilsService = {
+ scrollToTop: vi.fn(),
+ };
+
+ await TestBed.configureTestingModule({
+ imports: [FinesAccEnfColloChangeComponent],
+ providers: [
+ { provide: ActivatedRoute, useValue: mockRoute },
+ { provide: Router, useValue: mockRouter },
+ { provide: FinesAccountStore, useValue: mockAccountStore },
+ { provide: FinesAccPayloadService, useValue: mockPayloadService },
+ { provide: OpalFines, useValue: mockOpalFinesService },
+ { provide: UtilsService, useValue: mockUtilsService },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(FinesAccEnfColloChangeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should expose page data from the route and store', () => {
+ expect(component.accountNumber).toBe('177A');
+ expect(component.partyName).toBe('Mr Robert THOMSON');
+ });
+
+ it('should patch and navigate on success when the selection changes', () => {
+ const routerNavigateSpy = vi.spyOn(component as never, 'routerNavigate');
+
+ component.handleSubmit({
+ formData: {
+ facc_enf_collection_order_made: true,
+ },
+ nestedFlow: false,
+ });
+
+ expect(mockPayloadService.buildCollectionOrderPayload).toHaveBeenCalledWith({
+ formData: {
+ facc_enf_collection_order_made: true,
+ },
+ nestedFlow: false,
+ });
+ expect(mockOpalFinesService.patchDefendantAccount).toHaveBeenCalledWith(
+ 1001,
+ {
+ collection_order: {
+ collection_order_date: null,
+ collection_order_flag: true,
+ },
+ },
+ '1',
+ '2002',
+ );
+ expect(mockOpalFinesService.clearCache).toHaveBeenCalledWith('defendantAccountEnforcementCache$');
+ expect(mockAccountStore.setSuccessMessage).toHaveBeenCalledWith(FINES_ACC_ENF_COLLO_CHANGE_SUCCESS_MESSAGE);
+ expect(routerNavigateSpy).toHaveBeenCalledWith(
+ FINES_ACC_DEFENDANT_ROUTING_PATHS.children.details,
+ false,
+ undefined,
+ null,
+ 'enforcement',
+ );
+ });
+
+ 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');
+
+ component.handleSubmit({
+ formData: {
+ facc_enf_collection_order_made: true,
+ },
+ nestedFlow: false,
+ });
+
+ expect(mockUtilsService.scrollToTop).toHaveBeenCalled();
+ expect(routerNavigateSpy).not.toHaveBeenCalled();
+ });
+
+ it('should navigate back without patching when the selected value matches the current collection order flag', () => {
+ mockCurrentNavigation.set({
+ extras: {
+ state: {
+ currentCollectionOrderFlag: false,
+ },
+ },
+ } as unknown as Navigation);
+
+ fixture = TestBed.createComponent(FinesAccEnfColloChangeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ const routerNavigateSpy = vi.spyOn(component as never, 'routerNavigate');
+
+ component.handleSubmit({
+ formData: {
+ facc_enf_collection_order_made: false,
+ },
+ nestedFlow: false,
+ });
+
+ expect(mockPayloadService.buildCollectionOrderPayload).not.toHaveBeenCalled();
+ expect(mockOpalFinesService.patchDefendantAccount).not.toHaveBeenCalled();
+ expect(mockAccountStore.setSuccessMessage).not.toHaveBeenCalled();
+ expect(routerNavigateSpy).toHaveBeenCalledWith(
+ FINES_ACC_DEFENDANT_ROUTING_PATHS.children.details,
+ false,
+ undefined,
+ null,
+ 'enforcement',
+ );
+ });
+
+ it('should send an empty collection order date when selecting yes', () => {
+ component.handleSubmit({
+ formData: {
+ facc_enf_collection_order_made: true,
+ },
+ nestedFlow: false,
+ });
+
+ expect(mockPayloadService.buildCollectionOrderPayload).toHaveBeenCalledWith({
+ formData: {
+ facc_enf_collection_order_made: true,
+ },
+ nestedFlow: false,
+ });
+ });
+
+ it('should send an empty collection order date when selecting no', () => {
+ component.handleSubmit({
+ formData: {
+ facc_enf_collection_order_made: false,
+ },
+ nestedFlow: false,
+ });
+
+ expect(mockPayloadService.buildCollectionOrderPayload).toHaveBeenCalledWith({
+ formData: {
+ facc_enf_collection_order_made: false,
+ },
+ nestedFlow: false,
+ });
+ });
+
+ it('should update unsaved changes state', () => {
+ component.handleUnsavedChanges(true);
+ expect(component.stateUnsavedChanges).toBe(true);
+
+ component.handleUnsavedChanges(false);
+ expect(component.stateUnsavedChanges).toBe(false);
+ });
+});
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.ts
new file mode 100644
index 0000000000..0df8f1807d
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/fines-acc-enf-collo-change.component.ts
@@ -0,0 +1,103 @@
+import { ChangeDetectionStrategy, Component, OnDestroy, inject } from '@angular/core';
+import { Router } from '@angular/router';
+import { AbstractFormParentBaseComponent } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-parent-base';
+import { catchError, EMPTY, Subject, takeUntil } from 'rxjs';
+import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service';
+import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces';
+import { FinesAccountStore } from '../stores/fines-acc.store';
+import { FinesAccPayloadService } from '../services/fines-acc-payload.service';
+import { OpalFines } from '../../services/opal-fines-service/opal-fines.service';
+import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '../routing/constants/fines-acc-defendant-routing-paths.constant';
+import { IFinesAccEnfColloChangeFormState } from './interfaces/fines-acc-enf-collo-change-form-state.interface';
+import { FinesAccEnfColloChangeFormComponent } from './fines-acc-enf-collo-change-form/fines-acc-enf-collo-change-form.component';
+import { FINES_ACC_ENF_COLLO_CHANGE_SUCCESS_MESSAGE } from './constants/fines-acc-enf-collo-change-success-message.constant';
+
+@Component({
+ selector: 'app-fines-acc-enf-collo-change',
+ templateUrl: './fines-acc-enf-collo-change.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+ imports: [FinesAccEnfColloChangeFormComponent],
+})
+export class FinesAccEnfColloChangeComponent extends AbstractFormParentBaseComponent implements OnDestroy {
+ private readonly ngUnsubscribe = new Subject();
+ private readonly finesAccStore = inject(FinesAccountStore);
+ private readonly finesAccPayloadService = inject(FinesAccPayloadService);
+ private readonly opalFinesService = inject(OpalFines);
+ private readonly appRouter = inject(Router);
+ private readonly utilsService = inject(UtilsService);
+ private readonly finesDefendantRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS;
+ private readonly currentCollectionOrderFlag = this.appRouter.currentNavigation()?.extras.state?.[
+ 'currentCollectionOrderFlag'
+ ] as boolean | undefined;
+
+ public readonly accountNumber = this.finesAccStore.getAccountNumber() ?? '';
+ public readonly partyName = this.finesAccStore.party_name() ?? '';
+
+ /**
+ * Navigates back to the enforcement tab and optionally sets a success message.
+ *
+ * @param setSuccessMessage Whether to set the success banner before navigation.
+ */
+ private navigateToEnforcementTab(setSuccessMessage = false): void {
+ this.stateUnsavedChanges = false;
+
+ if (setSuccessMessage) {
+ this.finesAccStore.setSuccessMessage(FINES_ACC_ENF_COLLO_CHANGE_SUCCESS_MESSAGE);
+ }
+
+ this.routerNavigate(this.finesDefendantRoutingPaths.children.details, false, undefined, null, 'enforcement');
+ }
+
+ /**
+ * Submits the selected Collection Order status for the current account.
+ *
+ * @param form The form payload emitted by the child form component.
+ */
+ public handleSubmit(form: IAbstractFormBaseForm): void {
+ const selectedCollectionOrderFlag = form.formData.facc_enf_collection_order_made;
+
+ if (selectedCollectionOrderFlag === this.currentCollectionOrderFlag) {
+ this.navigateToEnforcementTab();
+ return;
+ }
+
+ const payload = this.finesAccPayloadService.buildCollectionOrderPayload(form);
+
+ 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.opalFinesService.clearCache('defendantAccountEnforcementCache$');
+ this.navigateToEnforcementTab(true);
+ });
+ }
+
+ /**
+ * Updates the page-level unsaved changes state from the child form.
+ *
+ * @param unsavedChanges Whether the form currently has unsaved changes.
+ */
+ public handleUnsavedChanges(unsavedChanges: boolean): void {
+ this.stateUnsavedChanges = unsavedChanges;
+ }
+
+ /**
+ * Completes the teardown notifier used by active subscriptions.
+ */
+ public ngOnDestroy(): void {
+ this.ngUnsubscribe.next();
+ this.ngUnsubscribe.complete();
+ }
+}
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-field-errors.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-field-errors.interface.ts
new file mode 100644
index 0000000000..2b8d8aaed3
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-field-errors.interface.ts
@@ -0,0 +1,8 @@
+import {
+ IAbstractFormBaseFieldError,
+ IAbstractFormBaseFieldErrors,
+} from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces';
+
+export interface IFinesAccEnfColloChangeFieldErrors extends IAbstractFormBaseFieldErrors {
+ facc_enf_collection_order_made: IAbstractFormBaseFieldError;
+}
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-form-state.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-form-state.interface.ts
new file mode 100644
index 0000000000..6326f61f3d
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-form-state.interface.ts
@@ -0,0 +1,3 @@
+export interface IFinesAccEnfColloChangeFormState {
+ facc_enf_collection_order_made: boolean | null;
+}
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-routing-titles.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-routing-titles.interface.ts
new file mode 100644
index 0000000000..cba7d32ba3
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-routing-titles.interface.ts
@@ -0,0 +1,8 @@
+import { IChildRoutingPaths } from '@hmcts/opal-frontend-common/pages/routing/interfaces';
+
+export interface IFinesAccEnfColloChangeRoutingTitles extends IChildRoutingPaths {
+ root: string;
+ children: {
+ change: string;
+ };
+}
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 e8420f4f79..c88185e1fa 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
@@ -22,6 +22,7 @@ import { fetchLocalJusticeAreasResolver } from '../../routing/resolvers/fetch-re
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';
+import { FINES_ACC_ENF_COLLO_CHANGE_ROUTING_TITLES } from '../fines-acc-enf-collo-change/constants/fines-acc-enf-collo-change-routing-titles.constant';
const accRootPermissionIds = FINES_PERMISSIONS;
@@ -194,6 +195,22 @@ export const routing: Routes = [
enforcersRefData: fetchEnforcersResolver,
},
},
+ {
+ path: `${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/collection-order/change`,
+ loadComponent: () =>
+ import('../fines-acc-enf-collo-change/fines-acc-enf-collo-change.component').then(
+ (c) => c.FinesAccEnfColloChangeComponent,
+ ),
+ canActivate: [routePermissionsGuard, finesAccStateGuard],
+ canDeactivate: [canDeactivateGuard],
+ data: {
+ routePermissionId: [accRootPermissionIds['account-maintenance']],
+ title: FINES_ACC_ENF_COLLO_CHANGE_ROUTING_TITLES.children.change,
+ },
+ resolve: {
+ title: TitleResolver,
+ },
+ },
],
},
{
diff --git a/src/app/flows/fines/fines-acc/services/constants/fines-acc-collection-order-payload-defaults.constant.ts b/src/app/flows/fines/fines-acc/services/constants/fines-acc-collection-order-payload-defaults.constant.ts
new file mode 100644
index 0000000000..a399aff422
--- /dev/null
+++ b/src/app/flows/fines/fines-acc/services/constants/fines-acc-collection-order-payload-defaults.constant.ts
@@ -0,0 +1,6 @@
+import { IOpalFinesUpdateDefendantAccountCollectionOrder } from '@services/fines/opal-fines-service/interfaces/opal-fines-update-defendant-account-collection-order.interface';
+
+export const FINES_ACC_COLLECTION_ORDER_PAYLOAD_DEFAULTS: IOpalFinesUpdateDefendantAccountCollectionOrder = {
+ collection_order_date: null,
+ collection_order_flag: null,
+};
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..046aa3afad 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',
},
+ collection_order: null,
enforcement_override: null,
});
});
@@ -566,10 +567,27 @@ describe('FinesAccPayloadService', () => {
free_text_note_2: null,
free_text_note_3: null,
},
+ collection_order: null,
enforcement_override: null,
});
});
+ it('should build collection order payload correctly', () => {
+ const result = service.buildCollectionOrderPayload({
+ formData: {
+ facc_enf_collection_order_made: true,
+ },
+ nestedFlow: false,
+ });
+
+ expect(result).toEqual({
+ collection_order: {
+ collection_order_date: null,
+ collection_order_flag: true,
+ },
+ });
+ });
+
it('should transform payload using the transformation service', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
vi.spyOn(service['transformationService'], 'transformObjectValues').mockImplementation(
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..32358d5e9a 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
@@ -27,6 +27,10 @@ import { buildAccountPartyFromFormState } from './utils/fines-acc-payload-build-
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';
+import { IOpalFinesUpdateDefendantAccountCollectionOrder } from '@services/fines/opal-fines-service/interfaces/opal-fines-update-defendant-account-collection-order.interface';
+import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces';
+import { IFinesAccEnfColloChangeFormState } from '../fines-acc-enf-collo-change/interfaces/fines-acc-enf-collo-change-form-state.interface';
+import { FINES_ACC_COLLECTION_ORDER_PAYLOAD_DEFAULTS } from './constants/fines-acc-collection-order-payload-defaults.constant';
@Injectable({
providedIn: 'root',
@@ -173,6 +177,28 @@ export class FinesAccPayloadService {
};
}
+ /**
+ * Transforms the given collection order form into an update payload
+ * for the defendant account API.
+ *
+ * @param form - The submitted collection order form
+ * @returns The transformed payload for updating the defendant account
+ */
+ public buildCollectionOrderPayload(
+ form: IAbstractFormBaseForm,
+ ): IOpalFinesUpdateDefendantAccountPayload {
+ const collectionOrderFlag = form.formData.facc_enf_collection_order_made as boolean;
+
+ const collectionOrder: IOpalFinesUpdateDefendantAccountCollectionOrder = {
+ ...FINES_ACC_COLLECTION_ORDER_PAYLOAD_DEFAULTS,
+ collection_order_flag: collectionOrderFlag,
+ };
+
+ return {
+ collection_order: collectionOrder,
+ };
+ }
+
/**
* Transforms the given IFinesAccEnfOverrideAddChangeFormState into an update payload
* for the defendant account API.
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
index b9fb0ce6fd..51476bb4b1 100644
--- 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
@@ -2,5 +2,6 @@ import { IOpalFinesUpdateDefendantAccountPayload } from '../interfaces/opal-fine
export const OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS: IOpalFinesUpdateDefendantAccountPayload = {
comment_and_notes: null,
+ collection_order: null,
enforcement_override: null,
};
diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-collection-order.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-collection-order.interface.ts
new file mode 100644
index 0000000000..cbb70f8594
--- /dev/null
+++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-collection-order.interface.ts
@@ -0,0 +1,4 @@
+export interface IOpalFinesUpdateDefendantAccountCollectionOrder {
+ collection_order_date: null;
+ collection_order_flag: boolean | 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..0b98d5f216 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,10 +1,12 @@
import { IOpalFinesUpdateDefendantAccountCommentsNotes } from './opal-fines-update-defendant-account-comments-notes.interface';
+import { IOpalFinesUpdateDefendantAccountCollectionOrder } from './opal-fines-update-defendant-account-collection-order.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 | null;
- enforcement_override: IOpalFinesUpdateDefendantAccountEnforcementOverride | null;
+ comment_and_notes?: IOpalFinesUpdateDefendantAccountCommentsNotes | null;
+ collection_order?: IOpalFinesUpdateDefendantAccountCollectionOrder | null;
+ enforcement_override?: IOpalFinesUpdateDefendantAccountEnforcementOverride | null;
}