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})`; }); }