Skip to content
Open

PO-1849 #2417

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ <h2 [class]="style.heading">Enforcement status</h2>
summaryListId="enforcementOverviewDetails"
summaryListRowId="enforcement_court"
[actionEnabled]="hasAccountMaintenancePermission ? true : false"
(actionClick)="handleChangeEnforcementCourt()"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could pass in tabData.enforcement_overview.enforcement_court.court_id here

>
<ng-container name>Enforcement court</ng-container>
<ng-container value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,13 @@ describe('FinesAccDefendantDetailsEnforcementTab', () => {
expect(event.preventDefault).toHaveBeenCalled();
expect(eventEmitterSpy).toHaveBeenCalled();
});

it('should emit the current enforcement court id when handleChangeEnforcementCourt is called', () => {
const eventEmitterSpy = vi.spyOn(component.changeEnforcementCourt, 'emit');
component.hasAccountMaintenancePermission = true;

component.handleChangeEnforcementCourt();

expect(eventEmitterSpy).toHaveBeenCalledWith(123);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class FinesAccDefendantDetailsEnforcementTab {
@Input() hasAccountMaintenancePermission: boolean = false;
@Input() hasEnterEnforcementPermission: boolean = false;
@Output() addEnforcementOverride = new EventEmitter<void>();
@Output() changeEnforcementCourt = new EventEmitter<number | null>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the null should be here


/**
* Emits an event to add an enforcement override if the user has the necessary permissions and there is no existing enforcement override result.
Expand All @@ -53,4 +54,13 @@ export class FinesAccDefendantDetailsEnforcementTab {
this.addEnforcementOverride.emit();
}
}

/**
* Emits the current enforcement court id when the user can change it.
*/
public handleChangeEnforcementCourt(): void {
if (this.hasAccountMaintenancePermission) {
this.changeEnforcementCourt.emit(this.tabData.enforcement_overview.enforcement_court?.court_id ?? null);
}
}
Comment on lines +61 to +65
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the link not be wrapped around this permission to prevent users who didn't have this permission to click a link they can't actually click on. Also, I don't think the link should show if the court_id was null. We could put an if statement in the HTML which looks for the permission and the court_id and pass that into the handleChangeEnforcementCourt() method as a parameter

}
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ <h2 opal-lib-custom-account-information-item-label>Business Unit:</h2>
[hasAccountMaintenancePermission]="hasPermission('account-maintenance')"
[hasEnterEnforcementPermission]="hasPermission('enter-enforcement')"
(addEnforcementOverride)="navigateToAddEnforcementOverridePage()"
(changeEnforcementCourt)="navigateToChangeEnforcementCourtPage($event)"
></app-fines-acc-defendant-details-enforcement-tab>
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FINES_ACC_PARTY_ADD_AMEND_CONVERT_PARTY_TYPES } from '../fines-acc-part
import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_PARENT_OR_GUARDIAN_TAB_REF_DATA_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-account-defendant-details-parent-or-guardian-tab-ref-data.mock';
import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_FIXED_PENALTY_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-account-defendant-details-fixed-penalty.mock';
import { OPAL_FINES_RESULT_REF_DATA_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-result-ref-data.mock';
import { FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS } from '../fines-acc-enf-court-change/constants/fines-acc-enf-court-change-routing-paths.constant';
import { beforeEach, describe, expect, it, vi } from 'vitest';

describe('FinesAccDefendantDetailsComponent', () => {
Expand Down Expand Up @@ -168,6 +169,20 @@ describe('FinesAccDefendantDetailsComponent', () => {
);
});

it('should call router.navigate when navigateToChangeEnforcementCourtPage is called', () => {
component.navigateToChangeEnforcementCourtPage(123);

expect(routerSpy.navigate).toHaveBeenCalledWith(
[
`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/${FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS.root}/${FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS.children.change}`,
],
{
relativeTo: component['activatedRoute'],
state: { currentEnforcementCourtId: 123 },
},
);
});

it('should fetch the defendant tab data when fragment is changed to defendant', () => {
component['refreshFragment$'].next('defendant');
// Subscribe to trigger the pipe execution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { FINES_ACCOUNT_TYPES } from '../../constants/fines-account-types.constan
import { IOpalFinesResultRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-result-ref-data.interface';
import { FinesAccDefendantDetailsEnforcementTab } from './fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component';
import { FinesAccSummaryHeaderComponent } from '../fines-acc-summary-header/fines-acc-summary-header.component';
import { FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS } from '../fines-acc-enf-court-change/constants/fines-acc-enf-court-change-routing-paths.constant';

@Component({
selector: 'app-fines-acc-defendant-details',
Expand Down Expand Up @@ -458,4 +459,21 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement
relativeTo: this.activatedRoute,
});
}

/**
* Navigates to the change enforcement court page, preserving the current court id for no-op submissions.
*
* @param currentEnforcementCourtId The current enforcement court id shown on the enforcement tab.
*/
public navigateToChangeEnforcementCourtPage(currentEnforcementCourtId: number | null): void {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about passing in the enforcement court id here. This would be cached data so we could just grab that on the component, this is being treated as throwaway data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we not use the resolver here?

this['router'].navigate(
[
`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/${FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS.root}/${FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS.children.change}`,
],
{
relativeTo: this.activatedRoute,
state: { currentEnforcementCourtId },
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IFinesAccEnfCourtChangeFieldErrors } from '../interfaces/fines-acc-enf-court-change-field-errors.interface';

export const FINES_ACC_ENF_COURT_CHANGE_FIELD_ERRORS: IFinesAccEnfCourtChangeFieldErrors = {
facc_enf_court: {
required: {
message: 'Select an enforcement court',
priority: 1,
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IFinesAccEnfCourtChangeRoutingPaths } from '../interfaces/fines-acc-enf-court-change-routing-paths.interface';

export const FINES_ACC_ENF_COURT_CHANGE_ROUTING_PATHS: IFinesAccEnfCourtChangeRoutingPaths = {
root: 'court',
children: {
change: 'change',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IFinesAccEnfCourtChangeRoutingPaths } from '../interfaces/fines-acc-enf-court-change-routing-paths.interface';

export const FINES_ACC_ENF_COURT_CHANGE_ROUTING_TITLES: IFinesAccEnfCourtChangeRoutingPaths = {
root: 'Enforcement court',
children: {
change: 'Change enforcement court',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const FINES_ACC_ENF_COURT_CHANGE_SUCCESS_MESSAGE = 'Enforcement court changed';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<opal-lib-govuk-heading-with-caption
[captionText]="`${accountNumber} - ${partyName}`"
[headingText]="'Change enforcement court'"
headingClasses="govuk-heading-l govuk-!-margin-bottom-30"
>
</opal-lib-govuk-heading-with-caption>
<opal-lib-govuk-error-summary
[errors]="formErrorSummaryMessage"
(errorClick)="scrollTo($event)"
></opal-lib-govuk-error-summary>
<form (submit)="handleFormSubmit($event)" [formGroup]="form" class="govuk-form">
<opal-lib-alphagov-accessible-autocomplete
[control]="form.get('facc_enf_court')"
labelText="Enforcement court"
labelClasses="govuk-fieldset__legend--m"
inputId="facc_enf_court"
inputName="facc_enf_court"
label="Enforcement court"
[autoCompleteItems]="courtOptions"
[errors]="formControlErrorMessages['facc_enf_court']"
[tabIndex]="0"
></opal-lib-alphagov-accessible-autocomplete>

<div class="govuk-button-group">
<opal-lib-govuk-button buttonId="submitForm" type="submit" buttonClasses="nested-flow govuk-button--primary">
Change
</opal-lib-govuk-button>
<opal-lib-govuk-cancel-link (linkClickEvent)="handleCancel()"></opal-lib-govuk-cancel-link>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FinesAccEnfCourtChangeFormComponent } from './fines-acc-enf-court-change-form.component';

describe('FinesAccEnfCourtChangeFormComponent', () => {
let component: FinesAccEnfCourtChangeFormComponent;
let fixture: ComponentFixture<FinesAccEnfCourtChangeFormComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FinesAccEnfCourtChangeFormComponent],
providers: [{ provide: ActivatedRoute, useValue: { snapshot: { params: {}, data: {} } } }],
}).compileComponents();

fixture = TestBed.createComponent(FinesAccEnfCourtChangeFormComponent);
component = fixture.componentInstance;
component.accountNumber = '123456';
component.courtOptions = [{ value: 101, name: 'Test Court (101)' }];
component.partyName = 'Mr Test PERSON';
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should initialize the enforcement court control as required', () => {
const control = component.form.get('facc_enf_court');

expect(control).toBeTruthy();
expect(control?.hasError('required')).toBe(true);
});

it('should emit cancel when handleCancel is called', () => {
const emitSpy = vi.spyOn(component.cancelRequested, 'emit');

component.handleCancel();

expect(emitSpy).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output } 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 { AlphagovAccessibleAutocompleteComponent } from '@hmcts/opal-frontend-common/components/alphagov/alphagov-accessible-autocomplete';
import { IAlphagovAccessibleAutocompleteItem } from '@hmcts/opal-frontend-common/components/alphagov/alphagov-accessible-autocomplete/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 { FINES_ACC_ENF_COURT_CHANGE_FIELD_ERRORS } from '../constants/fines-acc-enf-court-change-field-errors.constant';
import { IFinesAccEnfCourtChangeFormState } from '../interfaces/fines-acc-enf-court-change-form-state.interface';

@Component({
selector: 'app-fines-acc-enf-court-change-form',
imports: [
FormsModule,
ReactiveFormsModule,
AlphagovAccessibleAutocompleteComponent,
GovukButtonComponent,
GovukCancelLinkComponent,
GovukErrorSummaryComponent,
GovukHeadingWithCaptionComponent,
],
templateUrl: './fines-acc-enf-court-change-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
})
export class FinesAccEnfCourtChangeFormComponent extends AbstractFormBaseComponent implements OnInit, OnDestroy {
protected override fieldErrors: IAbstractFormBaseFieldErrors = {
...FINES_ACC_ENF_COURT_CHANGE_FIELD_ERRORS,
};
protected override formSubmit = new EventEmitter<IAbstractFormBaseForm<IFinesAccEnfCourtChangeFormState>>();
@Output() public cancelRequested = new EventEmitter<void>();
public override formControlErrorMessages: IAbstractFormControlErrorMessage = {};
@Input({ required: true }) public accountNumber!: string;
@Input({ required: true }) public courtOptions!: IAlphagovAccessibleAutocompleteItem[];
@Input({ required: true }) public partyName!: string;

/**
* Sets up the enforcement court change form with the required court control.
*/
private setupForm(): void {
this.form = new FormGroup({
facc_enf_court: new FormControl<number | null>(null, Validators.required),
});
}

/**
* Initializes the form before wiring up the shared form behaviour.
*/
public override ngOnInit(): void {
this.setupForm();
super.ngOnInit();
}

/**
* Emits the cancel action to the parent component.
*/
public handleCancel(): void {
this.cancelRequested.emit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="govuk-grid-column-two-thirds">
<app-fines-acc-enf-court-change-form
[accountNumber]="accountNumber"
[courtOptions]="courtOptions"
[partyName]="partyName"
(cancelRequested)="handleCancel()"
(unsavedChanges)="handleUnsavedChanges($event)"
(formSubmit)="handleSubmit($event)"
></app-fines-acc-enf-court-change-form>
</div>
Loading
Loading