diff --git a/AdminWebsite/AdminWebsite.UnitTests/Controllers/HearingsController/EditHearingTests.cs b/AdminWebsite/AdminWebsite.UnitTests/Controllers/HearingsController/EditHearingTests.cs index 649209bc1..5f05ac59d 100644 --- a/AdminWebsite/AdminWebsite.UnitTests/Controllers/HearingsController/EditHearingTests.cs +++ b/AdminWebsite/AdminWebsite.UnitTests/Controllers/HearingsController/EditHearingTests.cs @@ -21,6 +21,7 @@ using AdminWebsite.UnitTests.Helper; using BookingsApi.Client; using BookingsApi.Contract.V1.Requests; +using BookingsApi.Contract.V1.Requests.Enums; using BookingsApi.Contract.V1.Responses; using BookingsApi.Contract.V2.Enums; using BookingsApi.Contract.V2.Requests; @@ -32,6 +33,7 @@ using BookingStatus = BookingsApi.Contract.V1.Enums.BookingStatus; using CaseResponse = BookingsApi.Contract.V1.Responses.CaseResponse; using EndpointResponse = BookingsApi.Contract.V1.Responses.EndpointResponse; +using JudiciaryParticipantRequest = AdminWebsite.Contracts.Requests.JudiciaryParticipantRequest; using LinkedParticipantResponse = BookingsApi.Contract.V1.Responses.LinkedParticipantResponse; using LinkedParticipantType = BookingsApi.Contract.V1.Enums.LinkedParticipantType; @@ -358,6 +360,11 @@ public void Setup() Pin = "pin", Sip = "sip" } + }, + JudiciaryParticipants = new List<JudiciaryParticipantResponse>() + { + new (){FullName = "Judge Fudge", FirstName = "John", LastName = "Doe", HearingRoleCode = JudiciaryParticipantHearingRoleCode.Judge, PersonalCode = "1234"}, + new (){FullName = "Jane Doe", FirstName = "Jane", LastName = "Doe", HearingRoleCode = JudiciaryParticipantHearingRoleCode.PanelMember, PersonalCode = "4567"} } }; } @@ -605,8 +612,19 @@ public async Task Should_return_updated_hearingV2() LinkedParticipantContactEmail = "interpreter@domain.net", Type = AdminWebsite.Contracts.Enums.LinkedParticipantType.Interpreter } - } + }, }); + _addNewParticipantRequest.JudiciaryParticipants = new List<JudiciaryParticipantRequest>() + { + new() + { + PersonalCode = "4567", DisplayName = "Jane Doe 2", Role = JudiciaryParticipantHearingRoleCode.PanelMember.ToString() + }, + new() + { + PersonalCode = "5678", DisplayName = "New Judge Fudge", Role = JudiciaryParticipantHearingRoleCode.Judge.ToString() + } + }; var result = await _controller.EditHearing(_validId, _addNewParticipantRequest); var hearing = (AdminWebsite.Contracts.Responses.HearingDetailsResponse)((OkObjectResult)result.Result).Value; hearing.Id.Should().Be(updatedHearing.Id); @@ -614,6 +632,16 @@ public async Task Should_return_updated_hearingV2() It.Is<UpdateHearingRequestV2>(u => !u.Cases.IsNullOrEmpty())), Times.Once); + + _bookingsApiClient.Verify(x => x.RemoveJudiciaryParticipantFromHearingAsync(hearing.Id, "1234"), + Times.Once); + + _bookingsApiClient.Verify(x => x.UpdateJudiciaryParticipantAsync(hearing.Id, "4567", It.IsAny<UpdateJudiciaryParticipantRequest>()), + Times.Once); + + _bookingsApiClient.Verify( + x => x.AddJudiciaryParticipantsToHearingAsync(hearing.Id, + It.IsAny<IEnumerable<BookingsApi.Contract.V1.Requests.JudiciaryParticipantRequest>>()), Times.Once); } [Test] diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.spec.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.spec.ts index d7e49107d..bacd8f275 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.spec.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.spec.ts @@ -188,7 +188,7 @@ describe('EndpointsComponent', () => { expect(component.duplicateDa).toBe(false); expect(component.failedValidation).toBe(false); expect(component.hearing.endpoints[0].displayName).toBe('200'); - expect(component.hearing.endpoints[0].defenceAdvocate).toBe(''); + expect(component.hearing.endpoints[0].defenceAdvocate).toBeNull(); expect(videoHearingsServiceSpy.updateHearingRequest).toHaveBeenCalled(); expect(routerSpy.navigate).toHaveBeenCalledWith(['/other-information']); }); diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.ts index 1387c33c6..64cd8d77d 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/endpoints/endpoints.component.ts @@ -89,10 +89,13 @@ export class EndpointsComponent extends BookingBaseComponent implements OnInit, for (const control of this.endpoints.controls) { const endpointModel = new EndpointModel(); if (control.value.displayName.trim() !== '') { - const displayNameText = SanitizeInputText(control.value.displayName); - endpointModel.displayName = displayNameText; + let defenceAdvocate = null; + if (control.value.defenceAdvocate !== this.constants.None) { + defenceAdvocate = control.value.defenceAdvocate; + } + endpointModel.displayName = SanitizeInputText(control.value.displayName); endpointModel.id = control.value.id; - endpointModel.defenceAdvocate = control.value.defenceAdvocate !== this.constants.None ? control.value.defenceAdvocate : ''; + endpointModel.defenceAdvocate = defenceAdvocate; newEndpointsArray.push(endpointModel); } } diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.spec.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.spec.ts index 9857dce17..923f0e6e3 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.spec.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.spec.ts @@ -39,7 +39,8 @@ describe('AddJudicialOfficeHoldersComponent', () => { ]); judicialServiceSpy = jasmine.createSpyObj('JudicialService', ['searchJudicialMembers']); videoHearingsServiceSpy.getCurrentRequest.and.returnValue(hearing); - bookingServiceSpy = jasmine.createSpyObj('BookingService', ['getParticipantEmail', 'removeParticipantEmail']); + bookingServiceSpy = jasmine.createSpyObj('BookingService', ['getParticipantEmail', 'removeParticipantEmail', 'isEditMode']); + bookingServiceSpy.isEditMode.and.returnValue(false); loggerSpy = jasmine.createSpyObj('Logger', ['debug', 'warn']); routerSpy = jasmine.createSpyObj('Router', ['navigate']); await TestBed.configureTestingModule({ @@ -107,10 +108,17 @@ describe('AddJudicialOfficeHoldersComponent', () => { }); describe('continueToNextStep', () => { - it('should navigate to the add participants page', () => { + it('should navigate to the add participants page when not in edit mode', () => { + bookingServiceSpy.isEditMode.and.returnValue(false); component.continueToNextStep(); expect(routerSpy.navigate).toHaveBeenCalledWith([PageUrls.AddParticipants]); }); + + it('should navigate to the summary page when in edit mode', () => { + bookingServiceSpy.isEditMode.and.returnValue(true); + component.continueToNextStep(); + expect(routerSpy.navigate).toHaveBeenCalledWith([PageUrls.Summary]); + }); }); describe('prepoplateFormForEdit', () => { diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.ts index 1df31d276..aa5b7bcd3 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/add-judicial-office-holders/add-judicial-office-holders.component.ts @@ -117,8 +117,13 @@ export class AddJudicialOfficeHoldersComponent implements OnInit, OnDestroy { } continueToNextStep() { - this.logger.debug(`${this.loggerPrefix} Navigating to add participants.`); - this.router.navigate([PageUrls.AddParticipants]); + if (this.bookingService.isEditMode()) { + this.logger.debug(`${this.loggerPrefix} In edit mode. Returning to summary.`); + this.router.navigate([PageUrls.Summary]); + } else { + this.logger.debug(`${this.loggerPrefix} Navigating to add participants.`); + this.router.navigate([PageUrls.AddParticipants]); + } } refreshPanelMemberText() { diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/models/add-judicial-member.model.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/models/add-judicial-member.model.ts index 7bf0a08d2..52bccf9bc 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/models/add-judicial-member.model.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/judicial-office-holders/models/add-judicial-member.model.ts @@ -1,3 +1,5 @@ +import { JudiciaryParticipantResponse } from 'src/app/services/clients/api-client'; + export type JudicaryRoleCode = 'Judge' | 'PanelMember'; export class JudicialMemberDto { @@ -11,4 +13,18 @@ export class JudicialMemberDto { public telephone: string, public personalCode: string ) {} + + static fromJudiciaryParticipantResponse(response: JudiciaryParticipantResponse): JudicialMemberDto { + const dto = new JudicialMemberDto( + response.first_name, + response.last_name, + response.full_name, + response.email, + response.work_phone, + response.personal_code + ); + dto.roleCode = response.role_code as JudicaryRoleCode; + dto.displayName = response.display_name; + return dto; + } } diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.spec.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.spec.ts index 2e4fa218a..19fb0f018 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.spec.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.spec.ts @@ -123,6 +123,7 @@ videoHearingsServiceSpy = jasmine.createSpyObj<VideoHearingsService>('VideoHeari ]); const launchDarklyServiceSpy = jasmine.createSpyObj<LaunchDarklyService>('LaunchDarklyService', ['getFlag']); launchDarklyServiceSpy.getFlag.withArgs(FeatureFlags.eJudFeature).and.returnValue(of(true)); +launchDarklyServiceSpy.getFlag.withArgs(FeatureFlags.useV2Api).and.returnValue(of(false)); const bookingStatusService = new BookingStatusService(videoHearingsServiceSpy); describe('SummaryComponent with valid request', () => { diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.ts index fb9415bde..27e1ab1b9 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/booking/summary/summary.component.ts @@ -73,6 +73,7 @@ export class SummaryComponent implements OnInit, OnDestroy { @ViewChild(RemoveInterpreterPopupComponent) removeInterpreterPopupComponent: RemoveInterpreterPopupComponent; judgeAssigned: boolean; ejudFeatureFlag = false; + useApiV2 = false; saveFailedMessages: string[]; constructor( @@ -95,6 +96,24 @@ export class SummaryComponent implements OnInit, OnDestroy { this.ejudFeatureFlag = result; }) ); + + this.$subscriptions.push( + this.featureService + .getFlag<boolean>(FeatureFlags.eJudFeature) + .pipe(first()) + .subscribe(result => { + this.ejudFeatureFlag = result; + }) + ); + + this.$subscriptions.push( + this.featureService + .getFlag<boolean>(FeatureFlags.useV2Api) + .pipe(first()) + .subscribe(result => { + this.useApiV2 = result; + }) + ); } ngOnInit() { @@ -143,17 +162,26 @@ export class SummaryComponent implements OnInit, OnDestroy { private confirmRemoveParticipant() { const participant = this.hearing.participants.find(x => x.email.toLowerCase() === this.selectedParticipantEmail.toLowerCase()); - const title = participant?.title ? `${participant.title}` : ''; - this.removerFullName = participant ? `${title} ${participant.first_name} ${participant.last_name}` : ''; - - const isInterpretee = - (participant.linked_participants && - participant.linked_participants.length > 0 && - participant.hearing_role_name.toLowerCase() !== HearingRoles.INTERPRETER) || - this.hearing.participants.some(p => p.interpreterFor === participant.email); - if (isInterpretee) { - this.showConfirmRemoveInterpretee = true; - } else { + + if (participant) { + const title = participant?.title ? `${participant.title}` : ''; + this.removerFullName = participant ? `${title} ${participant.first_name} ${participant.last_name}` : ''; + + const isInterpretee = + (participant.linked_participants && + participant.linked_participants.length > 0 && + participant.hearing_role_name.toLowerCase() !== HearingRoles.INTERPRETER) || + this.hearing.participants.some(p => p.interpreterFor === participant.email); + if (isInterpretee) { + this.showConfirmRemoveInterpretee = true; + } else { + this.showConfirmationRemoveParticipant = true; + } + } + + const judicalParticipant = this.hearing.judiciaryParticipants.findIndex(x => x.email === this.selectedParticipantEmail); + if (judicalParticipant > -1) { + this.removerFullName = this.hearing.judiciaryParticipants[judicalParticipant].fullName; this.showConfirmationRemoveParticipant = true; } } @@ -184,10 +212,17 @@ export class SummaryComponent implements OnInit, OnDestroy { this.hearing.participants.splice(indexOfParticipant, 1); this.removeLinkedParticipant(this.selectedParticipantEmail); this.hearing = { ...this.hearing }; - this.hearingService.updateHearingRequest(this.hearing); - this.hearingService.setBookingHasChanged(true); - this.bookingService.removeParticipantEmail(); } + + const judicalParticipant = this.hearing.judiciaryParticipants.findIndex(x => x.email === this.selectedParticipantEmail); + if (judicalParticipant > -1) { + this.hearing.judiciaryParticipants.splice(judicalParticipant, 1); + this.hearing = { ...this.hearing }; + } + + this.hearingService.updateHearingRequest(this.hearing); + this.hearingService.setBookingHasChanged(true); + this.bookingService.removeParticipantEmail(); } private retrieveHearingSummary() { @@ -472,6 +507,10 @@ export class SummaryComponent implements OnInit, OnDestroy { } navToAddJudge() { - this.router.navigate([PageUrls.AssignJudge]); + if (this.useApiV2) { + this.router.navigate([PageUrls.AddJudicialOfficeHolders]); + } else { + this.router.navigate([PageUrls.AssignJudge]); + } } } diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/app-insights-logger.service.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/app-insights-logger.service.ts index da2723582..9a48050aa 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/app-insights-logger.service.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/app-insights-logger.service.ts @@ -19,6 +19,14 @@ export class AppInsightsLogger implements LogAdapter { this.appInsights.loadAppInsights(); this.oidcService.userData$.subscribe(ud => { this.appInsights.addTelemetryInitializer((envelope: ITelemetryItem) => { + const remoteDepedencyType = 'RemoteDependencyData'; + if (envelope.baseType === remoteDepedencyType && (envelope.baseData.name as string)) { + const name = envelope.baseData.name as string; + if (name.startsWith('HEAD /assets/images/favicons/favicon.ico?')) { + // ignore favicon requests used to poll for availability + return false; + } + } envelope.tags['ai.cloud.role'] = 'vh-admin-web'; envelope.tags['ai.user.id'] = ud.userData.preferred_username.toLowerCase(); }); diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/clients/api-client.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/clients/api-client.ts index be61c61b1..35ff9ba1d 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/clients/api-client.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/clients/api-client.ts @@ -7401,6 +7401,8 @@ export class EditHearingRequest implements IEditHearingRequest { case!: EditCaseRequest; /** List of participants in hearing */ participants!: EditParticipantRequest[] | undefined; + /** List of judiciary participants in hearing */ + judiciary_participants?: JudiciaryParticipantRequest[] | undefined; telephone_participants?: EditTelephoneParticipantRequest[] | undefined; /** Any other information about the hearing */ other_information?: string | undefined; @@ -7432,6 +7434,11 @@ export class EditHearingRequest implements IEditHearingRequest { this.participants = [] as any; for (let item of _data['participants']) this.participants!.push(EditParticipantRequest.fromJS(item)); } + if (Array.isArray(_data['judiciary_participants'])) { + this.judiciary_participants = [] as any; + for (let item of _data['judiciary_participants']) + this.judiciary_participants!.push(JudiciaryParticipantRequest.fromJS(item)); + } if (Array.isArray(_data['telephone_participants'])) { this.telephone_participants = [] as any; for (let item of _data['telephone_participants']) @@ -7465,6 +7472,10 @@ export class EditHearingRequest implements IEditHearingRequest { data['participants'] = []; for (let item of this.participants) data['participants'].push(item.toJSON()); } + if (Array.isArray(this.judiciary_participants)) { + data['judiciary_participants'] = []; + for (let item of this.judiciary_participants) data['judiciary_participants'].push(item.toJSON()); + } if (Array.isArray(this.telephone_participants)) { data['telephone_participants'] = []; for (let item of this.telephone_participants) data['telephone_participants'].push(item.toJSON()); @@ -7494,6 +7505,8 @@ export interface IEditHearingRequest { case: EditCaseRequest; /** List of participants in hearing */ participants: EditParticipantRequest[] | undefined; + /** List of judiciary participants in hearing */ + judiciary_participants?: JudiciaryParticipantRequest[] | undefined; telephone_participants?: EditTelephoneParticipantRequest[] | undefined; /** Any other information about the hearing */ other_information?: string | undefined; diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/recording-guard.service.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/recording-guard.service.ts index d1428c693..539cf3719 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/recording-guard.service.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/recording-guard.service.ts @@ -7,12 +7,16 @@ import { ParticipantModel } from '../common/model/participant.model'; export class RecordingGuardService { excludedCaseTypes: string[] = ['Court of Appeal Criminal Division', 'Crime Crown Court']; mandatoryRecordingRoles: string[] = ['Interpreter']; + mandatoryRecordingRoleCodes: string[] = ['INTP']; switchOffRecording(caseType: string): boolean { return this.excludedCaseTypes.indexOf(caseType) > -1; } mandatoryRecordingForHearingRole(participants: ParticipantModel[]) { - return participants.some(pat => this.mandatoryRecordingRoles.includes(pat.hearing_role_name.trim())); + return ( + participants.some(pat => this.mandatoryRecordingRoles.includes(pat.hearing_role_name?.trim())) || + participants.some(pat => this.mandatoryRecordingRoleCodes.includes(pat.hearing_role_code?.trim())) + ); } } diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.spec.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.spec.ts index 154c82f55..6843ab886 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.spec.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.spec.ts @@ -12,7 +12,8 @@ import { LinkedParticipantResponse, BookingStatus, AllocatedCsoResponse, - JusticeUserResponse + JusticeUserResponse, + JudiciaryParticipantResponse } from './clients/api-client'; import { HearingModel } from '../common/model/hearing.model'; import { CaseModel } from '../common/model/case.model'; @@ -201,6 +202,19 @@ describe('Video hearing service', () => { model.other_information = 'note'; model.cases = [caseModel]; model.participants = []; + model.judiciary_participants = [ + new JudiciaryParticipantResponse({ + title: 'Mr', + first_name: 'Dan', + last_name: 'Smith', + display_name: 'Judge Dan Smith', + email: 'joh@judge.com', + full_name: 'Dan Smith', + personal_code: '1234', + work_phone: '123123123', + role_code: 'Judge' + }) + ]; model.audio_recording_required = true; const request = service.mapHearingDetailsResponseToHearingModel(model); @@ -215,6 +229,8 @@ describe('Video hearing service', () => { expect(request.scheduled_date_time).toEqual(new Date(date)); expect(request.scheduled_duration).toBe(30); expect(request.audio_recording_required).toBeTruthy(); + expect(request.judiciaryParticipants[0]).toBeTruthy(); + expect(request.judiciaryParticipants[0].displayName).toBe('Judge Dan Smith'); }); it('should map ParticipantResponse to ParticipantModel', () => { diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.ts b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.ts index 401d42dd7..3dc4af6c5 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.ts +++ b/AdminWebsite/AdminWebsite/ClientApp/src/app/services/video-hearings.service.ts @@ -27,7 +27,8 @@ import { BookingStatus, AllocatedCsoResponse, HearingRoleResponse, - JudiciaryParticipantRequest + JudiciaryParticipantRequest, + JudiciaryParticipantResponse } from './clients/api-client'; import { HearingModel } from '../common/model/hearing.model'; import { CaseModel } from '../common/model/case.model'; @@ -216,6 +217,7 @@ export class VideoHearingsService { hearing.scheduled_date_time = new Date(booking.scheduled_date_time); hearing.scheduled_duration = booking.scheduled_duration; hearing.participants = this.mapParticipantModelToEditParticipantRequest(booking.participants); + hearing.judiciary_participants = this.mapJudicialMemberDtoToJudiciaryParticipantRequest(booking.judiciaryParticipants); hearing.audio_recording_required = booking.audio_recording_required; hearing.endpoints = this.mapEndpointModelToEditEndpointRequest(booking.endpoints); return hearing; @@ -324,6 +326,9 @@ export class VideoHearingsService { hearing.status = response.status; hearing.audio_recording_required = response.audio_recording_required; hearing.endpoints = this.mapEndpointResponseToEndpointModel(response.endpoints, response.participants); + hearing.judiciaryParticipants = response.judiciary_participants.map(judiciaryParticipant => + JudicialMemberDto.fromJudiciaryParticipantResponse(judiciaryParticipant) + ); hearing.isConfirmed = Boolean(response.confirmed_date); return hearing; } @@ -360,7 +365,7 @@ export class VideoHearingsService { return judicialMemberDtos.map(judicialMemberDto => { const judiciaryParticipantRequest: JudiciaryParticipantRequest = new JudiciaryParticipantRequest({ personal_code: judicialMemberDto.personalCode, - display_name: judicialMemberDto.fullName, + display_name: judicialMemberDto.displayName, role: judicialMemberDto.roleCode }); return judiciaryParticipantRequest; diff --git a/AdminWebsite/AdminWebsite/ClientApp/src/tslint.json b/AdminWebsite/AdminWebsite/ClientApp/src/tslint.json index 68e1466fa..435337719 100644 --- a/AdminWebsite/AdminWebsite/ClientApp/src/tslint.json +++ b/AdminWebsite/AdminWebsite/ClientApp/src/tslint.json @@ -5,6 +5,7 @@ }, "rules": { "directive-selector": [true, "attribute", "app", "camelCase"], - "component-selector": [true, "element", "app", "kebab-case"] + "component-selector": [true, "element", "app", "kebab-case"], + "no-unused-expression": true } } diff --git a/AdminWebsite/AdminWebsite/Contracts/Requests/JudiciaryParticipantRequest.cs b/AdminWebsite/AdminWebsite/Contracts/Requests/JudiciaryParticipantRequest.cs index 5fa75a34d..04b67055d 100644 --- a/AdminWebsite/AdminWebsite/Contracts/Requests/JudiciaryParticipantRequest.cs +++ b/AdminWebsite/AdminWebsite/Contracts/Requests/JudiciaryParticipantRequest.cs @@ -5,4 +5,13 @@ public class JudiciaryParticipantRequest public string PersonalCode { get; set; } public string Role { get; set; } public string DisplayName { get; set; } +} + + +/// <summary> +/// Editing a judiciary participant request +/// </summary> +public class EditJudiciaryParticipantRequest +{ + } \ No newline at end of file diff --git a/AdminWebsite/AdminWebsite/Controllers/HearingsController.cs b/AdminWebsite/AdminWebsite/Controllers/HearingsController.cs index 9abbb7ab5..bb902c589 100644 --- a/AdminWebsite/AdminWebsite/Controllers/HearingsController.cs +++ b/AdminWebsite/AdminWebsite/Controllers/HearingsController.cs @@ -16,6 +16,7 @@ using BookingsApi.Client; using BookingsApi.Contract.Interfaces.Requests; using BookingsApi.Contract.V1.Requests; +using BookingsApi.Contract.V1.Requests.Enums; using BookingsApi.Contract.V2.Requests; using FluentValidation; using Microsoft.AspNetCore.Mvc; @@ -338,13 +339,15 @@ private async Task UpdateHearing(EditHearingRequest request, Guid hearingId, Hea var updateHearingRequestV2 = HearingUpdateRequestMapper.MapToV2(request, _userIdentity.GetUserIdentityName()); await _bookingsApiClient.UpdateHearingDetails2Async(hearingId, updateHearingRequestV2); await UpdateParticipantsV2(hearingId, request, originalHearing); - - return; + await UpdateJudiciaryParticipants(hearingId, request, originalHearing); + } + else + { + var updateHearingRequestV1 = + HearingUpdateRequestMapper.MapToV1(request, _userIdentity.GetUserIdentityName()); + await _bookingsApiClient.UpdateHearingDetailsAsync(hearingId, updateHearingRequestV1); + await UpdateParticipantsV1(hearingId, request, originalHearing); } - - var updateHearingRequestV1 = HearingUpdateRequestMapper.MapToV1(request, _userIdentity.GetUserIdentityName()); - await _bookingsApiClient.UpdateHearingDetailsAsync(hearingId, updateHearingRequestV1); - await UpdateParticipantsV1(hearingId, request, originalHearing); } private async Task UpdateParticipantsV1(Guid hearingId, EditHearingRequest request, HearingDetailsResponse originalHearing) @@ -397,6 +400,50 @@ private static List<Guid> GetRemovedParticipantIds(EditHearingRequest request, H .Select(x => x.Id).ToList(); } + private async Task UpdateJudiciaryParticipants(Guid hearingId, EditHearingRequest request, + HearingDetailsResponse originalHearing) + { + // keep the order of removal first. this will allow admin web to change judiciary judges post booking + var removedJohs = originalHearing.JudiciaryParticipants.Where(ojp => + request.JudiciaryParticipants.TrueForAll(jp => jp.PersonalCode != ojp.PersonalCode)).ToList(); + foreach (var removedJoh in removedJohs) + { + await _bookingsApiClient.RemoveJudiciaryParticipantFromHearingAsync(hearingId, removedJoh.PersonalCode); + } + + var newJohs = request.JudiciaryParticipants.Where(jp => + !originalHearing.JudiciaryParticipants.Exists(ojp => ojp.PersonalCode == jp.PersonalCode)).ToList(); + + var newJohRequest = newJohs.Select(jp => + { + var roleCode = Enum.Parse<JudiciaryParticipantHearingRoleCode>(jp.Role); + return new BookingsApi.Contract.V1.Requests.JudiciaryParticipantRequest() + { + DisplayName = jp.DisplayName, + PersonalCode = jp.PersonalCode, + HearingRoleCode = roleCode + }; + }).ToList(); + if (newJohRequest.Any()) + { + await _bookingsApiClient.AddJudiciaryParticipantsToHearingAsync(hearingId, newJohRequest); + } + + // get existing judiciary participants based on the personal code being present in the original hearing + var existingJohs = request.JudiciaryParticipants.Where(jp => + originalHearing.JudiciaryParticipants.Exists(ojp => ojp.PersonalCode == jp.PersonalCode)).ToList(); + + foreach (var joh in existingJohs) + { + var roleCode = Enum.Parse<JudiciaryParticipantHearingRoleCode>(joh.Role); + await _bookingsApiClient.UpdateJudiciaryParticipantAsync(hearingId, joh.PersonalCode, + new UpdateJudiciaryParticipantRequest() + { + DisplayName = joh.DisplayName, HearingRoleCode = roleCode + }); + } + } + private static List<LinkedParticipantRequest> ExtractLinkedParticipants( EditHearingRequest request, HearingDetailsResponse originalHearing, diff --git a/AdminWebsite/AdminWebsite/Mappers/UpdateParticipantRequestMapper.cs b/AdminWebsite/AdminWebsite/Mappers/UpdateParticipantRequestMapper.cs index 03f748723..f9c998130 100644 --- a/AdminWebsite/AdminWebsite/Mappers/UpdateParticipantRequestMapper.cs +++ b/AdminWebsite/AdminWebsite/Mappers/UpdateParticipantRequestMapper.cs @@ -17,7 +17,7 @@ public static UpdateParticipantRequest MapTo(EditParticipantRequest participant) TelephoneNumber = participant.TelephoneNumber, Representee = participant.Representee, ParticipantId = participant.Id ?? Guid.Empty, - ContactEmail = participant.ContactEmail + ContactEmail = participant.ContactEmail }; return updateParticipantRequest; } @@ -31,7 +31,10 @@ public static UpdateParticipantRequestV2 MapToV2(EditParticipantRequest particip OrganisationName = participant.OrganisationName, TelephoneNumber = participant.TelephoneNumber, Representee = participant.Representee, - ParticipantId = participant.Id ?? Guid.Empty + ParticipantId = participant.Id ?? Guid.Empty, + FirstName = participant.FirstName, + LastName = participant.LastName, + MiddleNames = participant.MiddleNames }; return updateParticipantRequest; } diff --git a/AdminWebsite/AdminWebsite/Models/EditHearingRequest.cs b/AdminWebsite/AdminWebsite/Models/EditHearingRequest.cs index 720fbdeb7..8fccbaef4 100644 --- a/AdminWebsite/AdminWebsite/Models/EditHearingRequest.cs +++ b/AdminWebsite/AdminWebsite/Models/EditHearingRequest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using AdminWebsite.Contracts.Requests; namespace AdminWebsite.Models { @@ -12,6 +13,7 @@ public EditHearingRequest() { Participants = new List<EditParticipantRequest>(); Endpoints = new List<EditEndpointRequest>(); + JudiciaryParticipants = new List<JudiciaryParticipantRequest>(); } /// <summary> @@ -48,6 +50,11 @@ public EditHearingRequest() /// List of participants in hearing /// </summary> public List<EditParticipantRequest> Participants { get; set; } + + /// <summary> + /// List of judiciary participants in hearing + /// </summary> + public List<JudiciaryParticipantRequest> JudiciaryParticipants { get; set; } public List<EditTelephoneParticipantRequest> TelephoneParticipants { get; set; }