diff --git a/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.html b/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.html
index 2253459973..b2c3386217 100644
--- a/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.html
+++ b/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.html
@@ -1,8 +1,6 @@
@if (selectedOffenceConfirmation) {
- @if (offenceCode.count === 1) {
-
+ @if (matchedOffenceTitle; as offenceTitle) {
+
} @else {
}
diff --git a/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.spec.ts b/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.spec.ts
index 5e6bc74881..2c749e8623 100644
--- a/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.spec.ts
+++ b/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.spec.ts
@@ -53,6 +53,7 @@ describe('FinesMacOffenceCodeHintComponent', () => {
it('should render component with both inputs provided', () => {
fixture.componentRef.setInput('offenceCode', mockOffenceCode);
+ fixture.componentRef.setInput('searchedOffenceCode', 'AK123456');
fixture.componentRef.setInput('selectedOffenceConfirmation', true);
fixture.detectChanges();
@@ -70,6 +71,7 @@ describe('FinesMacOffenceCodeHintComponent', () => {
it('should maintain component state through input changes', () => {
// Initial state
fixture.componentRef.setInput('offenceCode', mockOffenceCode);
+ fixture.componentRef.setInput('searchedOffenceCode', 'AK123456');
fixture.componentRef.setInput('selectedOffenceConfirmation', false);
fixture.detectChanges();
@@ -82,4 +84,76 @@ describe('FinesMacOffenceCodeHintComponent', () => {
expect(component.selectedOffenceConfirmation).toBe(true);
expect(component.offenceCode).toEqual(mockOffenceCode);
});
+
+ it('should render Offence found when the searched code matches one result exactly', () => {
+ const multipleMatchResponse: IOpalFinesOffencesRefData = {
+ count: 4,
+ refData: [
+ {
+ offence_id: 41799,
+ get_cjs_code: 'CD71039',
+ business_unit_id: 52,
+ offence_title: 'Criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1997-11-16T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30733,
+ get_cjs_code: 'CD71039A',
+ business_unit_id: 52,
+ offence_title: 'Attempt criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to section 1(1) of the Criminal Attempts Act 1981.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30734,
+ get_cjs_code: 'CD71039B',
+ business_unit_id: 52,
+ offence_title: 'Aid, abet, counsel and procure damage under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30735,
+ get_cjs_code: 'CD71039C',
+ business_unit_id: 52,
+ offence_title: 'Conspiracy to destroy or damage property under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: '2004-12-25T00:00:00Z',
+ offence_oas: 'Contrary to section 1 of the Criminal Law Act 1977.',
+ offence_oas_cy: null,
+ },
+ ],
+ };
+
+ fixture.componentRef.setInput('offenceCode', multipleMatchResponse);
+ fixture.componentRef.setInput('searchedOffenceCode', 'CD71039');
+ fixture.componentRef.setInput('selectedOffenceConfirmation', true);
+ fixture.detectChanges();
+
+ const textContent = fixture.nativeElement.textContent;
+ expect(textContent).toContain('Offence found');
+ expect(textContent).toContain('Criminal damage to property valued under £5000');
+ });
+
+ it('should render Offence not found when there is no exact code match', () => {
+ fixture.componentRef.setInput('offenceCode', mockOffenceCode);
+ fixture.componentRef.setInput('searchedOffenceCode', 'AK12345');
+ fixture.componentRef.setInput('selectedOffenceConfirmation', true);
+ fixture.detectChanges();
+
+ const textContent = fixture.nativeElement.textContent;
+ expect(textContent).toContain('Offence not found');
+ expect(textContent).toContain('Enter a valid offence code');
+ });
});
diff --git a/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.ts b/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.ts
index 71b6ee902d..ad7cd02472 100644
--- a/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.ts
+++ b/src/app/flows/fines/fines-mac/components/fines-mac-offence-code-hint/fines-mac-offence-code-hint.component.ts
@@ -1,7 +1,8 @@
import { NgTemplateOutlet } from '@angular/common';
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { ChangeDetectionStrategy, Component, Input, inject } from '@angular/core';
import { MojTicketPanelComponent } from '@hmcts/opal-frontend-common/components/moj/moj-ticket-panel';
import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-offences-ref-data.interface';
+import { FinesMacOffenceDetailsService } from '../../fines-mac-offence-details/services/fines-mac-offence-details.service';
@Component({
selector: 'app-fines-mac-offence-code-hint',
@@ -10,6 +11,13 @@ import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/in
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FinesMacOffenceCodeHintComponent {
+ private readonly offenceDetailsService = inject(FinesMacOffenceDetailsService);
+
@Input() public offenceCode!: IOpalFinesOffencesRefData;
+ @Input() public searchedOffenceCode: string | null = null;
@Input() public selectedOffenceConfirmation!: boolean;
+
+ public get matchedOffenceTitle(): string | null {
+ return this.offenceDetailsService.findExactOffenceMatch(this.offenceCode, this.searchedOffenceCode)?.offence_title ?? null;
+ }
}
diff --git a/src/app/flows/fines/fines-mac/fines-mac-fixed-penalty-details/fines-mac-fixed-penalty-details-form/fines-mac-fixed-penalty-details-form.component.html b/src/app/flows/fines/fines-mac/fines-mac-fixed-penalty-details/fines-mac-fixed-penalty-details-form/fines-mac-fixed-penalty-details-form.component.html
index 575b3f9cf9..30b542379c 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-fixed-penalty-details/fines-mac-fixed-penalty-details-form/fines-mac-fixed-penalty-details-form.component.html
+++ b/src/app/flows/fines/fines-mac/fines-mac-fixed-penalty-details/fines-mac-fixed-penalty-details-form/fines-mac-fixed-penalty-details-form.component.html
@@ -266,6 +266,7 @@
Offence Details
@if (offenceCode$ | async; as offenceCode) {
}
diff --git a/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-add-an-offence/fines-mac-offence-details-add-an-offence-form/fines-mac-offence-details-add-an-offence-form.component.html b/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-add-an-offence/fines-mac-offence-details-add-an-offence-form/fines-mac-offence-details-add-an-offence-form.component.html
index 10a03ccf0b..3ee53afb19 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-add-an-offence/fines-mac-offence-details-add-an-offence-form/fines-mac-offence-details-add-an-offence-form.component.html
+++ b/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-add-an-offence/fines-mac-offence-details-add-an-offence-form/fines-mac-offence-details-add-an-offence-form.component.html
@@ -61,6 +61,7 @@
@if (offenceCode$ | async; as offenceCode) {
}
diff --git a/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.spec.ts b/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.spec.ts
index 8d220e125d..7c9ac5d736 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.spec.ts
+++ b/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.spec.ts
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FinesMacOffenceDetailsReviewOffenceHeadingTitleComponent } from './fines-mac-offence-details-review-offence-heading-title.component';
+import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-offences-ref-data.interface';
import { OPAL_FINES_OFFENCES_REF_DATA_SINGULAR_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-offences-ref-data-singular.mock';
import { beforeEach, describe, expect, it, vi } from 'vitest';
@@ -16,6 +17,7 @@ describe('FinesMacOffenceDetailsReviewOffenceHeadingTitleComponent', () => {
fixture = TestBed.createComponent(FinesMacOffenceDetailsReviewOffenceHeadingTitleComponent);
component = fixture.componentInstance;
+ component.offenceCode = OPAL_FINES_OFFENCES_REF_DATA_SINGULAR_MOCK.refData[0].get_cjs_code;
component.offenceRefData = OPAL_FINES_OFFENCES_REF_DATA_SINGULAR_MOCK;
fixture.detectChanges();
@@ -40,4 +42,63 @@ describe('FinesMacOffenceDetailsReviewOffenceHeadingTitleComponent', () => {
expect(component.offenceTitle).toEqual(component.offenceRefData.refData[0].offence_title);
});
+
+ it('should use the exact code match when multiple offences are returned', () => {
+ const multiResultResponse: IOpalFinesOffencesRefData = {
+ count: 4,
+ refData: [
+ {
+ offence_id: 41799,
+ get_cjs_code: 'CD71039',
+ business_unit_id: 52,
+ offence_title: 'Criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1997-11-16T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30733,
+ get_cjs_code: 'CD71039A',
+ business_unit_id: 52,
+ offence_title: 'Attempt criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to section 1(1) of the Criminal Attempts Act 1981.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30734,
+ get_cjs_code: 'CD71039B',
+ business_unit_id: 52,
+ offence_title: 'Aid, abet, counsel and procure damage under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30735,
+ get_cjs_code: 'CD71039C',
+ business_unit_id: 52,
+ offence_title: 'Conspiracy to destroy or damage property under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: '2004-12-25T00:00:00Z',
+ offence_oas: 'Contrary to section 1 of the Criminal Law Act 1977.',
+ offence_oas_cy: null,
+ },
+ ],
+ };
+
+ component.offenceCode = 'CD71039';
+ component.offenceRefData = multiResultResponse;
+
+ component.getOffenceTitle();
+
+ expect(component.offenceTitle).toEqual('Criminal damage to property valued under £5000');
+ });
});
diff --git a/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.ts b/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.ts
index 9885656ab9..d08fd596d9 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.ts
+++ b/src/app/flows/fines/fines-mac/fines-mac-offence-details/fines-mac-offence-details-review-offence/fines-mac-offence-details-review-offence-heading/fines-mac-offence-details-review-offence-heading-title/fines-mac-offence-details-review-offence-heading-title.component.ts
@@ -1,11 +1,12 @@
import { CommonModule } from '@angular/common';
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';
import { GovukHeadingWithCaptionComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-heading-with-caption';
import {
GovukSummaryListRowActionItemComponent,
GovukSummaryListRowActionsComponent,
} from '@hmcts/opal-frontend-common/components/govuk/govuk-summary-list';
import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-offences-ref-data.interface';
+import { FinesMacOffenceDetailsService } from '../../../services/fines-mac-offence-details.service';
@Component({
selector: 'app-fines-mac-offence-details-review-offence-heading-title',
@@ -20,6 +21,8 @@ import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/in
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FinesMacOffenceDetailsReviewOffenceHeadingTitleComponent implements OnInit {
+ private readonly offenceDetailsService = inject(FinesMacOffenceDetailsService);
+
@Input({ required: true }) public offenceCode!: string;
@Input({ required: true }) public offenceRefData!: IOpalFinesOffencesRefData;
@Input({ required: false }) public showActions!: boolean;
@@ -41,7 +44,8 @@ export class FinesMacOffenceDetailsReviewOffenceHeadingTitleComponent implements
* Retrieves the offence title from the offence reference data and assigns it to the `offenceTitle` property.
*/
public getOffenceTitle(): void {
- this.offenceTitle = this.offenceRefData.refData[0].offence_title;
+ const exactMatch = this.offenceDetailsService.findExactOffenceMatch(this.offenceRefData, this.offenceCode);
+ this.offenceTitle = exactMatch?.offence_title ?? this.offenceRefData.refData[0]?.offence_title ?? '';
}
public ngOnInit(): void {
diff --git a/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.spec.ts b/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.spec.ts
index ce71eb0d95..d1b191ced4 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.spec.ts
+++ b/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.spec.ts
@@ -132,7 +132,7 @@ describe('FinesMacOffenceDetailsService', () => {
it('should call populateHint immediately if initial code is present', () => {
vi.useFakeTimers();
- form.get('code')?.setValue('ab12345');
+ form.get('code')?.setValue('ak123456');
service.initOffenceCodeListener(
form,
@@ -166,13 +166,13 @@ describe('FinesMacOffenceDetailsService', () => {
onConfirmChangeSpy,
);
- form.get('code')?.setValue('xy98765');
+ form.get('code')?.setValue('ak123456');
form.get('code')?.updateValueAndValidity();
vi.advanceTimersByTime(FINES_MAC_OFFENCE_DETAILS_DEFAULT_VALUES.defaultDebounceTime);
- expect(uppercaseAllLettersSpy).toHaveBeenCalledWith('xy98765');
- expect(form.get('code')?.value).toBe('XY98765');
+ expect(uppercaseAllLettersSpy).toHaveBeenCalledWith('ak123456');
+ expect(form.get('code')?.value).toBe('AK123456');
expect(form.get('id')?.value).toBe(314441);
expect(onResultSpy).toHaveBeenCalled();
expect(onConfirmChangeSpy).toHaveBeenCalledWith(true);
@@ -201,6 +201,125 @@ describe('FinesMacOffenceDetailsService', () => {
expect(onConfirmChangeSpy).toHaveBeenCalledWith(true);
});
+ it('should populate the offence id when an exact match exists in a multi-result response', () => {
+ vi.useFakeTimers();
+ const multiResultResponse: IOpalFinesOffencesRefData = {
+ count: 4,
+ refData: [
+ {
+ offence_id: 41799,
+ get_cjs_code: 'CD71039',
+ business_unit_id: 52,
+ offence_title: 'Criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1997-11-16T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30733,
+ get_cjs_code: 'CD71039A',
+ business_unit_id: 52,
+ offence_title: 'Attempt criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to section 1(1) of the Criminal Attempts Act 1981.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30734,
+ get_cjs_code: 'CD71039B',
+ business_unit_id: 52,
+ offence_title: 'Aid, abet, counsel and procure damage under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30735,
+ get_cjs_code: 'CD71039C',
+ business_unit_id: 52,
+ offence_title: 'Conspiracy to destroy or damage property under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: '2004-12-25T00:00:00Z',
+ offence_oas: 'Contrary to section 1 of the Criminal Law Act 1977.',
+ offence_oas_cy: null,
+ },
+ ],
+ };
+ getOffenceByCjsCode = () => of(multiResultResponse);
+
+ service.initOffenceCodeListener(
+ form,
+ 'code',
+ 'id',
+ destroy$,
+ getOffenceByCjsCode,
+ onResultSpy,
+ onConfirmChangeSpy,
+ );
+
+ form.get('code')?.setValue('cd71039');
+ vi.advanceTimersByTime(FINES_MAC_OFFENCE_DETAILS_DEFAULT_VALUES.defaultDebounceTime);
+
+ expect(form.get('code')?.errors).toBeNull();
+ expect(form.get('id')?.value).toBe(41799);
+ expect(onConfirmChangeSpy).toHaveBeenCalledWith(true);
+ });
+
+ it('should mark code as invalid when results are returned but none match exactly', () => {
+ vi.useFakeTimers();
+ const nonExactResponse: IOpalFinesOffencesRefData = {
+ count: 2,
+ refData: [
+ {
+ offence_id: 1,
+ get_cjs_code: 'TEST123A',
+ business_unit_id: 52,
+ offence_title: 'Test A',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Test A',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 2,
+ get_cjs_code: 'TEST123B',
+ business_unit_id: 52,
+ offence_title: 'Test B',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Test B',
+ offence_oas_cy: null,
+ },
+ ],
+ };
+ getOffenceByCjsCode = () => of(nonExactResponse);
+
+ service.initOffenceCodeListener(
+ form,
+ 'code',
+ 'id',
+ destroy$,
+ getOffenceByCjsCode,
+ onResultSpy,
+ onConfirmChangeSpy,
+ );
+
+ form.get('code')?.setValue('TEST123');
+ vi.advanceTimersByTime(FINES_MAC_OFFENCE_DETAILS_DEFAULT_VALUES.defaultDebounceTime);
+
+ expect(form.get('code')?.errors).toEqual({ invalidOffenceCode: true });
+ expect(form.get('id')?.value).toBeNull();
+ });
+
it('should not call populateHint for short code', () => {
vi.useFakeTimers();
service.initOffenceCodeListener(
diff --git a/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.ts b/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.ts
index 3519764552..584800b2c6 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.ts
+++ b/src/app/flows/fines/fines-mac/fines-mac-offence-details/services/fines-mac-offence-details.service.ts
@@ -4,6 +4,7 @@ import { FormGroup } from '@angular/forms';
import { debounceTime, distinctUntilChanged, Observable, Subject, takeUntil, tap } from 'rxjs';
import { FINES_MAC_OFFENCE_DETAILS_DEFAULT_VALUES } from '../constants/fines-mac-offence-details-default-values.constant';
import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service';
+import { IOpalFinesOffences } from '@services/fines/opal-fines-service/interfaces/opal-fines-offences.interface';
import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-offences-ref-data.interface';
@Injectable({
@@ -55,6 +56,16 @@ export class FinesMacOffenceDetailsService {
}
}
+ /**
+ * Extracts the offence code from a ref-data item.
+ *
+ * @param offence - The offence ref-data item.
+ * @returns The offence code when present.
+ */
+ private getOffenceCode(offence: IOpalFinesOffences & { cjs_code?: string }): string | undefined {
+ return offence.get_cjs_code ?? offence.cjs_code;
+ }
+
/**
* Removes an imposition from the specified offence in the data array.
*
@@ -115,6 +126,32 @@ export class FinesMacOffenceDetailsService {
});
}
+ /**
+ * Finds the exact offence match for a supplied offence code from the returned offence reference data.
+ *
+ * Supports both `get_cjs_code` and `cjs_code` shaped response objects so the caller does not need
+ * to care about the source format.
+ *
+ * @param response - The offence lookup response.
+ * @param offenceCode - The offence code entered by the user.
+ * @returns The matching offence entry, if one exists.
+ */
+ public findExactOffenceMatch(
+ response: IOpalFinesOffencesRefData | null | undefined,
+ offenceCode: string | null | undefined,
+ ): IOpalFinesOffences | undefined {
+ if (!response?.refData?.length || !offenceCode) {
+ return undefined;
+ }
+
+ const normalisedOffenceCode = offenceCode.trim().toUpperCase();
+
+ return response.refData.find((offence) => {
+ const returnedCode = this.getOffenceCode(offence as IOpalFinesOffences & { cjs_code?: string });
+ return returnedCode?.trim().toUpperCase() === normalisedOffenceCode;
+ });
+ }
+
/**
* Initializes the offence code listener for a form control.
* @param form - The FormGroup containing the controls.
@@ -144,8 +181,10 @@ export class FinesMacOffenceDetailsService {
if (code?.length >= 7 && code?.length <= 8) {
const result$ = getOffenceByCjsCode(code).pipe(
tap((response) => {
- codeControl.setErrors(response.count === 0 ? { invalidOffenceCode: true } : null, { emitEvent: false });
- idControl.setValue(response.count === 1 ? response.refData[0].offence_id : null, { emitEvent: false });
+ const exactMatch = this.findExactOffenceMatch(response, code);
+
+ codeControl.setErrors(exactMatch ? null : { invalidOffenceCode: true }, { emitEvent: false });
+ idControl.setValue(exactMatch?.offence_id ?? null, { emitEvent: false });
if (typeof onResult === 'function') {
onResult(response);
diff --git a/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.spec.ts b/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.spec.ts
index 44a9ed904b..ea3c523472 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.spec.ts
+++ b/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.spec.ts
@@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FinesMacReviewAccountFixedPenaltyOffenceDetailsComponent } from './fines-mac-review-account-fixed-penalty-offence-details.component';
import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service';
+import { IOpalFinesOffencesRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-offences-ref-data.interface';
import { OPAL_FINES_OFFENCES_REF_DATA_SINGULAR_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-offences-ref-data-singular.mock';
import { of } from 'rxjs';
import { FINES_MAC_FIXED_PENALTY_DETAILS_STORE_STATE_MOCK } from '../../fines-mac-fixed-penalty-details/mocks/fines-mac-fixed-penalty-details-store-state.mock';
@@ -48,4 +49,62 @@ describe('FinesMacReviewAccountFixedPenaltyDetailsComponent', () => {
expect(mockOpalFinesService.getOffenceByCjsCode).toHaveBeenCalledWith(offenceCode);
expect(component.offence).toBe('ak test (12345)');
});
+
+ it('should use the exact offence match when multiple offences are returned', () => {
+ const multiResultResponse: IOpalFinesOffencesRefData = {
+ count: 4,
+ refData: [
+ {
+ offence_id: 41799,
+ get_cjs_code: 'CD71039',
+ business_unit_id: 52,
+ offence_title: 'Criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1997-11-16T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30733,
+ get_cjs_code: 'CD71039A',
+ business_unit_id: 52,
+ offence_title: 'Attempt criminal damage to property valued under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to section 1(1) of the Criminal Attempts Act 1981.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30734,
+ get_cjs_code: 'CD71039B',
+ business_unit_id: 52,
+ offence_title: 'Aid, abet, counsel and procure damage under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: null,
+ offence_oas: 'Contrary to sections 1(1) and 4 of the Criminal Damage Act 1971.',
+ offence_oas_cy: null,
+ },
+ {
+ offence_id: 30735,
+ get_cjs_code: 'CD71039C',
+ business_unit_id: 52,
+ offence_title: 'Conspiracy to destroy or damage property under £5000',
+ offence_title_cy: null,
+ date_used_from: '1971-01-01T00:00:00Z',
+ date_used_to: '2004-12-25T00:00:00Z',
+ offence_oas: 'Contrary to section 1 of the Criminal Law Act 1977.',
+ offence_oas_cy: null,
+ },
+ ],
+ };
+
+ mockOpalFinesService.getOffenceByCjsCode = vi.fn().mockReturnValue(of(multiResultResponse));
+
+ component.getOffence('CD71039');
+
+ expect(component.offence).toBe('Criminal damage to property valued under £5000 (CD71039)');
+ });
});
diff --git a/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.ts b/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.ts
index 0d76828312..f9d1a5e194 100644
--- a/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.ts
+++ b/src/app/flows/fines/fines-mac/fines-mac-review-account/fines-mac-review-account-fixed-penalty-offence-details/fines-mac-review-account-fixed-penalty-offence-details.component.ts
@@ -13,6 +13,7 @@ import { FINES_DEFAULT_VALUES } from '../../../constants/fines-default-values.co
import { FinesNotProvidedComponent } from '../../../components/fines-not-provided/fines-not-provided.component';
import { DateFormatPipe } from '@hmcts/opal-frontend-common/pipes/date-format';
import { MonetaryPipe } from '@hmcts/opal-frontend-common/pipes/monetary';
+import { FinesMacOffenceDetailsService } from '../../fines-mac-offence-details/services/fines-mac-offence-details.service';
@Component({
selector: 'app-fines-mac-review-account-fixed-penalty-offence-details',
@@ -30,6 +31,7 @@ import { MonetaryPipe } from '@hmcts/opal-frontend-common/pipes/monetary';
})
export class FinesMacReviewAccountFixedPenaltyOffenceDetailsComponent implements OnInit {
private readonly opalFinesService = inject(OpalFines);
+ private readonly offenceDetailsService = inject(FinesMacOffenceDetailsService);
@Input({ required: true }) public offenceDetails!: IFinesMacFixedPenaltyDetailsStoreState;
@Input({ required: false }) public isReadOnly = false;
@Output() public emitChangeOffenceDetails = new EventEmitter();
@@ -56,7 +58,9 @@ export class FinesMacReviewAccountFixedPenaltyOffenceDetailsComponent implements
*/
public getOffence(offenceCode: string): void {
this.opalFinesService.getOffenceByCjsCode(offenceCode).subscribe((offence: IOpalFinesOffencesRefData) => {
- this.offence = `${offence.refData[0].offence_title} (${offenceCode})`;
+ const exactMatch = this.offenceDetailsService.findExactOffenceMatch(offence, offenceCode);
+ const offenceTitle = exactMatch?.offence_title ?? offence.refData[0]?.offence_title ?? offenceCode;
+ this.offence = `${offenceTitle} (${offenceCode})`;
});
}