diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.spec.ts index 7cd024e858..5669aa6981 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.spec.ts @@ -2,7 +2,6 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; -import { MomentDateModule } from '@angular/material-moment-adapter'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; import { MatSnackBarModule } from '@angular/material/snack-bar'; @@ -27,6 +26,7 @@ describe('PlanningReviewDialogComponent', () => { let mockBoardService: DeepMocked; const mockPlanningReviewDto: PlanningReviewDto = { + uuid: '', documentName: '', type: {} as any, open: true, diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.ts index 666a1c9e1c..2caebc4d72 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.ts @@ -9,29 +9,13 @@ import { PlanningReferralDto, PlanningReviewDto } from '../../../../services/pla import { ToastService } from '../../../../services/toast/toast.service'; import { UserService } from '../../../../services/user/user.service'; import { ApplicationPill } from '../../../../shared/application-type-pill/application-type-pill.component'; +import { + CLOSED_PR_LABEL, + OPEN_PR_LABEL, +} from '../../../../shared/application-type-pill/application-type-pill.constants'; import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { CardDialogComponent } from '../card-dialog/card-dialog.component'; -export const OPEN_TYPE = { - label: 'Open', - code: 'Open', - shortLabel: 'Open', - backgroundColor: '#94c6ac', - borderColor: '#94c6ac', - description: 'Open', - textColor: '#313132', -}; - -export const CLOSED_TYPE = { - label: 'Closed', - code: 'Closed', - shortLabel: 'Closed', - backgroundColor: '#C6242A', - borderColor: '#C6242A', - description: 'Closed', - textColor: '#313132', -}; - @Component({ selector: 'app-detail-dialog', templateUrl: './planning-review-dialog.component.html', @@ -42,8 +26,8 @@ export class PlanningReviewDialogComponent extends CardDialogComponent implement title?: string; planningType?: ApplicationPill; cardTitle = ''; - OPEN_TYPE = OPEN_TYPE; - CLOSED_TYPE = CLOSED_TYPE; + OPEN_TYPE = OPEN_PR_LABEL; + CLOSED_TYPE = CLOSED_PR_LABEL; planningReview: PlanningReviewDto = this.data.planningReview; planningReferral: PlanningReferralDto = this.data; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html index dcbf2561f5..109e704f00 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/decision-component.component.html @@ -9,10 +9,10 @@
{{ data.noticeOfIntentDecisionComponentType?.label }}
- +
- +
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.ts index b1f91bb0b9..bc9ce51e14 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/pfrs-input/pfrs-input.component.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; @Component({ - selector: 'app-pfrs-input', + selector: 'app-noi-pfrs-input', templateUrl: './pfrs-input.component.html', styleUrls: ['./pfrs-input.component.scss'], }) diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.ts index cb18c63a8e..b36986b7f1 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-components/decision-component/roso-input/roso-input.component.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; @Component({ - selector: 'app-roso-input', + selector: 'app-noi-roso-input', templateUrl: './roso-input.component.html', styleUrls: ['./roso-input.component.scss'], }) diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.html b/alcs-frontend/src/app/features/planning-review/header/header.component.html new file mode 100644 index 0000000000..66706d5dd4 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.html @@ -0,0 +1,48 @@ +
+
+ Planning Review +
+
+
+
{{ planningReview.fileNumber }} ({{ planningReview.documentName }})
+ +
+ +
+
+
+ + + + + + + +
+
+
+
+
Local/First Nation Government:
+
+ {{ planningReview.localGovernment.name }} + +
+
+
+
+
diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.scss b/alcs-frontend/src/app/features/planning-review/header/header.component.scss new file mode 100644 index 0000000000..9c6cbbe865 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.scss @@ -0,0 +1,58 @@ +@use '../../../../styles/colors'; + +.heading { + color: colors.$primary-color-dark; + margin-bottom: 8px; +} + +.header { + padding: 16px 80px; + border-bottom: 1px solid colors.$primary-color-dark; + + .first-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + } + + .title { + display: flex; + flex-wrap: wrap; + align-items: center; + line-height: 32px; + + h5 { + margin-right: 8px !important; + } + } + + .labels { + display: flex; + flex-wrap: wrap; + margin-top: -8px; + margin-right: 8px; + + app-application-type-pill { + margin-top: 8px; + } + } + + .sub-heading { + margin-top: 16px; + margin-bottom: 8px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + } + + .subheading2 { + margin-bottom: 6px !important; + } +} + +.status-wrapper { + display: flex; + flex-direction: row-reverse; + align-items: flex-end; +} diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts new file mode 100644 index 0000000000..f62ad9f704 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.spec.ts @@ -0,0 +1,53 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { HeaderComponent } from './header.component'; + +describe('HeaderComponent', () => { + let component: HeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [HeaderComponent], + providers: [], + }).compileComponents(); + + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + + component.planningReview = { + documentName: '', + fileNumber: '', + localGovernment: { + uuid: '', + name: '', + isFirstNation: false, + preferredRegionCode: '', + }, + open: false, + referrals: [], + region: { + label: '', + code: '', + description: '', + }, + type: { + code: '', + description: '', + label: '', + backgroundColor: '', + shortLabel: '', + textColor: '', + }, + uuid: '', + }; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/header/header.component.ts b/alcs-frontend/src/app/features/planning-review/header/header.component.ts new file mode 100644 index 0000000000..5506b55ecd --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/header/header.component.ts @@ -0,0 +1,47 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { CardDto } from '../../../services/card/card.dto'; +import { PlanningReviewDetailedDto, PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; +import { CLOSED_PR_LABEL, OPEN_PR_LABEL } from '../../../shared/application-type-pill/application-type-pill.constants'; + +@Component({ + selector: 'app-header[planningReview]', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'], +}) +export class HeaderComponent implements OnInit { + destroy = new Subject(); + + @Input() planningReview!: PlanningReviewDetailedDto; + + legacyId?: string; + applicant?: string; + linkedCards: (CardDto & { displayName: string })[] = []; + statusPill = OPEN_PR_LABEL; + + constructor(private router: Router) {} + + ngOnInit(): void { + this.setupLinkedCards(); + if (!this.planningReview.open) { + this.statusPill = CLOSED_PR_LABEL; + } + } + + async onGoToCard(card: CardDto) { + const boardCode = card.boardCode; + const cardUuid = card.uuid; + const cardTypeCode = card.type; + await this.router.navigateByUrl(`/board/${boardCode}?card=${cardUuid}&type=${cardTypeCode}`); + } + + async setupLinkedCards() { + for (const [index, referral] of this.planningReview.referrals.entries()) { + this.linkedCards.push({ + ...referral.card, + displayName: `Referral ${index}`, + }); + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.html b/alcs-frontend/src/app/features/planning-review/overview/overview.component.html new file mode 100644 index 0000000000..d57bc19dc7 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.html @@ -0,0 +1,16 @@ +
+

Overview

+
+
+ +
+
+
Planning Review Type
+
+ +
+
+
+
Status
+
{{ planningReview.open ? 'Open' : 'Closed' }}
+
diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.scss b/alcs-frontend/src/app/features/planning-review/overview/overview.component.scss new file mode 100644 index 0000000000..439e3e77c2 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.scss @@ -0,0 +1,7 @@ +h5 { + margin: 16px 0 !important; +} + +section { + margin: 32px 0; +} diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts b/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts new file mode 100644 index 0000000000..19932568db --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.spec.ts @@ -0,0 +1,52 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject } from 'rxjs'; +import { NoticeOfIntentDecisionService } from '../../../services/notice-of-intent/decision/notice-of-intent-decision.service'; +import { NoticeOfIntentDetailService } from '../../../services/notice-of-intent/notice-of-intent-detail.service'; +import { NoticeOfIntentTimelineService } from '../../../services/notice-of-intent/notice-of-intent-timeline/notice-of-intent-timeline.service'; +import { NoticeOfIntentDto } from '../../../services/notice-of-intent/notice-of-intent.dto'; +import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDetailedDto } from '../../../services/planning-review/planning-review.dto'; +import { PlanningReviewService } from '../../../services/planning-review/planning-review.service'; +import { ConfirmationDialogService } from '../../../shared/confirmation-dialog/confirmation-dialog.service'; + +import { OverviewComponent } from './overview.component'; +import { NoticeOfIntentSubmissionStatusService } from '../../../services/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-submission-status.service'; + +describe('OverviewComponent', () => { + let component: OverviewComponent; + let fixture: ComponentFixture; + let mockPRDetailService: DeepMocked; + let mockPRService: DeepMocked; + + beforeEach(async () => { + mockPRService = createMock(); + + mockPRDetailService = createMock(); + mockPRDetailService.$planningReview = new BehaviorSubject(undefined); + await TestBed.configureTestingModule({ + providers: [ + { + provide: PlanningReviewDetailService, + useValue: mockPRDetailService, + }, + { + provide: PlanningReviewService, + useValue: mockPRService, + }, + ], + declarations: [OverviewComponent], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + fixture = TestBed.createComponent(OverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts b/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts new file mode 100644 index 0000000000..9808255de5 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/overview/overview.component.ts @@ -0,0 +1,51 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subject, takeUntil } from 'rxjs'; +import { PlanningReviewDetailService } from '../../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; +import { PlanningReviewService } from '../../../services/planning-review/planning-review.service'; + +@Component({ + selector: 'app-overview', + templateUrl: './overview.component.html', + styleUrls: ['./overview.component.scss'], +}) +export class OverviewComponent implements OnInit, OnDestroy { + $destroy = new Subject(); + planningReview?: PlanningReviewDto; + types: { label: string; value: string }[] = []; + + constructor( + private planningReviewDetailService: PlanningReviewDetailService, + private planningReviewService: PlanningReviewService, + ) {} + + ngOnInit(): void { + this.planningReviewDetailService.$planningReview.pipe(takeUntil(this.$destroy)).subscribe((review) => { + this.planningReview = review; + }); + this.loadTypes(); + } + + private async loadTypes() { + const types = await this.planningReviewService.fetchTypes(); + if (types) { + this.types = types.map((type) => ({ + label: type.label, + value: type.code, + })); + } + } + + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } + + async onSaveType($event: string | string[] | null) { + if ($event && !Array.isArray($event) && this.planningReview) { + await this.planningReviewDetailService.update(this.planningReview.fileNumber, { + typeCode: $event, + }); + } + } +} diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.html b/alcs-frontend/src/app/features/planning-review/planning-review.component.html index aba50711ec..dbc7fd31ce 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.html +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.html @@ -1 +1,23 @@ -

planning-review works!

+
+
+ +
+ +
+ +
+
+
+
diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.scss b/alcs-frontend/src/app/features/planning-review/planning-review.component.scss index e69de29bb2..55dfeb4d75 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.scss +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.scss @@ -0,0 +1,78 @@ +@use '../../../styles/colors'; + +.layout { + display: flex; + flex-direction: row; + height: 100%; + width: 100%; + justify-content: center; +} + +.application { + width: 100%; + display: flex; + flex-direction: column; +} + +.content { + display: flex; + flex-grow: 1; + padding-right: 80px; +} + +.child-content { + margin: 24px 0 0 48px; + flex-grow: 1; +} + +.nav { + background-color: colors.$bg-color; + min-width: 240px; + width: 240px; + height: 100%; +} + +.nav-link { + + div { + padding: 12px 24px; + border: 2px solid transparent; + } + + div.active { + font-weight: bold; + background-color: colors.$primary-color-dark; + color: colors.$white; + border-color: colors.$primary-color-dark; + + &:hover { + cursor: default; + background-color: colors.$primary-color-dark; + color: colors.$white; + } + } + + div:not(.disabled):hover { + cursor: pointer; + border-color: colors.$primary-color-dark; + color: colors.$dark-contrast-text; + } + + div.active:not(.disabled):hover { + color: colors.$white; + } +} + +.nav-item { + display: flex; + align-items: center; + white-space: pre-wrap; + + .mat-icon { + padding-right: 32px; + } + + &.disabled { + opacity: 0.5; + } +} diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.spec.ts b/alcs-frontend/src/app/features/planning-review/planning-review.component.spec.ts index e8dac38a3c..3c34e108ec 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.spec.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.spec.ts @@ -1,14 +1,39 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { PlanningReviewDetailService } from '../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDetailedDto } from '../../services/planning-review/planning-review.dto'; import { PlanningReviewComponent } from './planning-review.component'; describe('PlanningReviewComponent', () => { let component: PlanningReviewComponent; let fixture: ComponentFixture; + let mockPlanningReviewDetailService: DeepMocked; + let mockActivateRoute: DeepMocked; beforeEach(() => { + mockPlanningReviewDetailService = createMock(); + mockActivateRoute = createMock(); + + Object.assign(mockActivateRoute, { params: new Observable() }); + mockPlanningReviewDetailService.$planningReview = new BehaviorSubject( + undefined, + ); + TestBed.configureTestingModule({ - declarations: [PlanningReviewComponent] + declarations: [PlanningReviewComponent], + providers: [ + { + provide: PlanningReviewDetailService, + useValue: mockPlanningReviewDetailService, + }, + { + provide: ActivatedRoute, + useValue: mockActivateRoute, + }, + ], }); fixture = TestBed.createComponent(PlanningReviewComponent); component = fixture.componentInstance; diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts index 2ffef4fac0..163daaf48f 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.component.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts @@ -1,10 +1,57 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, take, takeUntil } from 'rxjs'; +import { PlanningReviewDetailService } from '../../services/planning-review/planning-review-detail.service'; +import { PlanningReviewDetailedDto, PlanningReviewDto } from '../../services/planning-review/planning-review.dto'; +import { PlanningReviewService } from '../../services/planning-review/planning-review.service'; +import { OverviewComponent } from './overview/overview.component'; + +export const childRoutes = [ + { + path: '', + menuTitle: 'Overview', + icon: 'summarize', + component: OverviewComponent, + }, +]; @Component({ selector: 'app-planning-review', templateUrl: './planning-review.component.html', - styleUrls: ['./planning-review.component.scss'] + styleUrls: ['./planning-review.component.scss'], }) -export class PlanningReviewComponent { +export class PlanningReviewComponent implements OnInit, OnDestroy { + $destroy = new Subject(); + + planningReview?: PlanningReviewDetailedDto; + fileNumber?: string; + childRoutes = childRoutes; + + constructor( + private planningReviewService: PlanningReviewDetailService, + private route: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.$destroy)).subscribe(async (routeParams) => { + const { fileNumber } = routeParams; + this.fileNumber = fileNumber; + await this.loadReview(); + }); + + this.planningReviewService.$planningReview.pipe(takeUntil(this.$destroy)).subscribe((planningReview) => { + this.planningReview = planningReview; + }); + } + + private async loadReview() { + if (this.fileNumber) { + await this.planningReviewService.loadReview(this.fileNumber); + } + } + ngOnDestroy(): void { + this.$destroy.next(); + this.$destroy.complete(); + } } diff --git a/alcs-frontend/src/app/features/planning-review/planning-review.module.ts b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts index 54737bff42..7e02a8f587 100644 --- a/alcs-frontend/src/app/features/planning-review/planning-review.module.ts +++ b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts @@ -1,18 +1,23 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; +import { PlanningReviewDetailService } from '../../services/planning-review/planning-review-detail.service'; import { SharedModule } from '../../shared/shared.module'; -import { PlanningReviewComponent } from './planning-review.component'; +import { HeaderComponent } from './header/header.component'; +import { OverviewComponent } from './overview/overview.component'; +import { childRoutes, PlanningReviewComponent } from './planning-review.component'; const routes: Routes = [ { path: ':fileNumber', component: PlanningReviewComponent, + children: childRoutes, }, ]; @NgModule({ - declarations: [PlanningReviewComponent], + providers: [PlanningReviewDetailService], + declarations: [PlanningReviewComponent, OverviewComponent, HeaderComponent], imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], }) export class PlanningReviewModule {} diff --git a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts index ee49144c3d..d7928b25ec 100644 --- a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts +++ b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.dto.ts @@ -22,6 +22,11 @@ export interface CreateNotificationStaffJournalDto { body: string; } +export interface CreatePlanningReviewStaffJournalDto { + planningReviewUuid: string; + body: string; +} + export interface UpdateStaffJournalDto { uuid: string; body: string; diff --git a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts index b23283707d..eb15da98e1 100644 --- a/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts +++ b/alcs-frontend/src/app/services/application/application-staff-journal/staff-journal.service.ts @@ -9,6 +9,7 @@ import { UpdateStaffJournalDto, CreateNoticeOfIntentStaffJournalDto, CreateNotificationStaffJournalDto, + CreatePlanningReviewStaffJournalDto, } from './staff-journal.dto'; @Injectable({ @@ -16,7 +17,10 @@ import { }) export class StaffJournalService { baseUrl = `${environment.apiUrl}/application-staff-journal`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} async fetchNotes(applicationUuid: string) { return firstValueFrom(this.http.get(`${this.baseUrl}/${applicationUuid}`)); @@ -40,6 +44,12 @@ export class StaffJournalService { return createdNote; } + async createNoteForPlanningReview(note: CreatePlanningReviewStaffJournalDto) { + const createdNote = firstValueFrom(this.http.post(`${this.baseUrl}/planning-review`, note)); + this.toastService.showSuccessToast('Journal note created'); + return createdNote; + } + async updateNote(note: UpdateStaffJournalDto) { const updatedNote = firstValueFrom(this.http.patch(`${this.baseUrl}`, note)); this.toastService.showSuccessToast('Journal note updated'); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.spec.ts new file mode 100644 index 0000000000..7337cac282 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.spec.ts @@ -0,0 +1,56 @@ +import { TestBed } from '@angular/core/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { firstValueFrom } from 'rxjs'; +import { PlanningReviewDetailService } from './planning-review-detail.service'; +import { PlanningReviewDetailedDto } from './planning-review.dto'; +import { PlanningReviewService } from './planning-review.service'; + +describe('PlanningReviewDetailService', () => { + let service: PlanningReviewDetailService; + let mockPlanningReviewService: DeepMocked; + + beforeEach(() => { + mockPlanningReviewService = createMock(); + + TestBed.configureTestingModule({ + providers: [ + PlanningReviewDetailService, + { + provide: PlanningReviewService, + useValue: mockPlanningReviewService, + }, + ], + }); + service = TestBed.inject(PlanningReviewDetailService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should publish the loaded application', async () => { + mockPlanningReviewService.fetchDetailedByFileNumber.mockResolvedValue({ + fileNumber: '1', + } as PlanningReviewDetailedDto); + + await service.loadReview('1'); + const res = await firstValueFrom(service.$planningReview); + + expect(mockPlanningReviewService.fetchDetailedByFileNumber).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); + + it('should publish the updated application for update', async () => { + mockPlanningReviewService.update.mockResolvedValue({ + fileNumber: '1', + } as PlanningReviewDetailedDto); + + await service.update('1', {}); + const res = await firstValueFrom(service.$planningReview); + + expect(mockPlanningReviewService.update).toHaveBeenCalledTimes(1); + expect(res).toBeDefined(); + expect(res!.fileNumber).toEqual('1'); + }); +}); diff --git a/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts new file mode 100644 index 0000000000..e2aeb87e96 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-review-detail.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { PlanningReviewDetailedDto, PlanningReviewDto, UpdatePlanningReviewDto } from './planning-review.dto'; +import { PlanningReviewService } from './planning-review.service'; + +@Injectable() +export class PlanningReviewDetailService { + $planningReview = new BehaviorSubject(undefined); + + private selectedFileNumber: string | undefined; + + constructor(private planningReviewService: PlanningReviewService) {} + + async loadReview(fileNumber: string) { + this.clearReview(); + + this.selectedFileNumber = fileNumber; + const planningReview = await this.planningReviewService.fetchDetailedByFileNumber(fileNumber); + this.$planningReview.next(planningReview); + } + + async clearReview() { + this.$planningReview.next(undefined); + } + + async update(fileNumber: string, updateDto: UpdatePlanningReviewDto) { + const updatedApp = await this.planningReviewService.update(fileNumber, updateDto); + if (updatedApp) { + this.$planningReview.next(updatedApp); + } + return updatedApp; + } +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review.dto.ts b/alcs-frontend/src/app/services/planning-review/planning-review.dto.ts index 0176a6f063..18bac05d64 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review.dto.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review.dto.ts @@ -14,6 +14,7 @@ export interface CreatePlanningReviewDto { } export interface PlanningReviewDto { + uuid: string; fileNumber: string; open: boolean; localGovernment: ApplicationLocalGovernmentDto; @@ -22,6 +23,10 @@ export interface PlanningReviewDto { documentName: string; } +export interface PlanningReviewDetailedDto extends PlanningReviewDto { + referrals: PlanningReferralDto[]; +} + export interface PlanningReviewTypeDto extends BaseCodeDto { shortLabel: string; backgroundColor: string; @@ -35,3 +40,8 @@ export interface PlanningReferralDto { planningReview: PlanningReviewDto; card: CardDto; } + +export interface UpdatePlanningReviewDto { + open?: boolean; + typeCode?: string; +} diff --git a/alcs-frontend/src/app/services/planning-review/planning-review.service.ts b/alcs-frontend/src/app/services/planning-review/planning-review.service.ts index b2cc55019d..9618ee7464 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review.service.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review.service.ts @@ -6,8 +6,10 @@ import { ToastService } from '../toast/toast.service'; import { CreatePlanningReviewDto, PlanningReferralDto, + PlanningReviewDetailedDto, PlanningReviewDto, PlanningReviewTypeDto, + UpdatePlanningReviewDto, } from './planning-review.dto'; @Injectable({ @@ -52,4 +54,24 @@ export class PlanningReviewService { } return; } + + async fetchDetailedByFileNumber(fileNumber: string) { + try { + return await firstValueFrom(this.http.get(`${this.url}/${fileNumber}`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch planning review'); + } + return; + } + + async update(fileNumber: string, updateDto: UpdatePlanningReviewDto) { + try { + return await firstValueFrom(this.http.post(`${this.url}/${fileNumber}`, updateDto)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to update planning review'); + } + return; + } } diff --git a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts index 0644c4d584..f6cff1d23a 100644 --- a/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts +++ b/alcs-frontend/src/app/shared/application-type-pill/application-type-pill.constants.ts @@ -97,3 +97,19 @@ export const NOTIFICATION_LABEL = { borderColor: '#59ADFA', textColor: '#313132', }; + +export const OPEN_PR_LABEL = { + label: 'Open', + shortLabel: 'Open', + backgroundColor: '#94c6ac', + borderColor: '#94c6ac', + textColor: '#313132', +}; + +export const CLOSED_PR_LABEL = { + label: 'Closed', + shortLabel: 'Closed', + backgroundColor: '#C6242A', + borderColor: '#C6242A', + textColor: '#313132', +}; diff --git a/alcs-frontend/src/app/shared/details-header/details-header.component.html b/alcs-frontend/src/app/shared/details-header/details-header.component.html index 60bf4353e0..d4abe30132 100644 --- a/alcs-frontend/src/app/shared/details-header/details-header.component.html +++ b/alcs-frontend/src/app/shared/details-header/details-header.component.html @@ -4,7 +4,7 @@
-
{{ _application.fileNumber }} ({{ _application.applicant }})
+
{{ _application.fileNumber }} ({{ applicant }})
diff --git a/alcs-frontend/src/app/shared/details-header/details-header.component.ts b/alcs-frontend/src/app/shared/details-header/details-header.component.ts index 5d99f8128e..a5aca02fad 100644 --- a/alcs-frontend/src/app/shared/details-header/details-header.component.ts +++ b/alcs-frontend/src/app/shared/details-header/details-header.component.ts @@ -13,6 +13,7 @@ import { NoticeOfIntentModificationDto } from '../../services/notice-of-intent/n import { NoticeOfIntentDto, NoticeOfIntentTypeDto } from '../../services/notice-of-intent/notice-of-intent.dto'; import { NotificationSubmissionStatusService } from '../../services/notification/notification-submission-status/notification-submission-status.service'; import { NotificationDto } from '../../services/notification/notification.dto'; +import { PlanningReviewDto } from '../../services/planning-review/planning-review.dto'; import { ApplicationSubmissionStatusPill } from '../application-submission-status-type-pill/application-submission-status-type-pill.component'; import { MODIFICATION_TYPE_LABEL, @@ -40,16 +41,37 @@ export class DetailsHeaderComponent { legacyId?: string; - _application: ApplicationDto | CommissionerApplicationDto | NoticeOfIntentDto | NotificationDto | undefined; + _application: + | ApplicationDto + | CommissionerApplicationDto + | NoticeOfIntentDto + | NotificationDto + | PlanningReviewDto + | undefined; types: ApplicationTypeDto[] | NoticeOfIntentTypeDto[] = []; timeTrackable?: TimeTrackable; + applicant?: string; @Input() set application( - application: ApplicationDto | CommissionerApplicationDto | NoticeOfIntentDto | NotificationDto | undefined + application: + | ApplicationDto + | CommissionerApplicationDto + | NoticeOfIntentDto + | NotificationDto + | PlanningReviewDto + | undefined, ) { if (application) { this._application = application; + if ('applicant' in application) { + this.applicant = application.applicant; + } + + if ('documentName' in application) { + this.applicant = application.documentName; + } + if ('retroactive' in application) { this.isNOI = true; } diff --git a/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts b/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts index a1e52d7651..3187274382 100644 --- a/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts +++ b/alcs-frontend/src/app/shared/staff-journal/staff-journal.component.ts @@ -14,7 +14,7 @@ import { ConfirmationDialogService } from '../confirmation-dialog/confirmation-d }) export class StaffJournalComponent implements OnChanges { @Input() parentUuid: string = ''; - @Input() parentType: 'Application' | 'NOI' | 'Notification' = 'Application'; + @Input() parentType: 'Application' | 'NOI' | 'Notification' | 'Planning Review' = 'Application'; labelText = 'Add a journal note'; @@ -35,7 +35,7 @@ export class StaffJournalComponent implements OnChanges { constructor( private staffJournalService: StaffJournalService, private confirmationDialogService: ConfirmationDialogService, - private toastService: ToastService + private toastService: ToastService, ) {} ngOnChanges(changes: SimpleChanges): void { @@ -68,6 +68,11 @@ export class StaffJournalComponent implements OnChanges { notificationUuid: this.parentUuid, body: note, }); + } else if (this.parentType === 'Planning Review') { + await this.staffJournalService.createNoteForPlanningReview({ + planningReviewUuid: this.parentUuid, + body: note, + }); } else { await this.staffJournalService.createNoteForNoticeOfIntent({ noticeOfIntentUuid: this.parentUuid, diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts index e126888a8f..3c60915954 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts @@ -17,6 +17,15 @@ export class PlanningReferral extends Base { } } + @AutoMap(() => String) + @Column({ + type: 'text', + comment: + 'Application Id that is applicable only to paper version applications from 70s - 80s', + nullable: true, + }) + legacyId?: string | null; + @AutoMap() @Column({ type: 'timestamptz' }) submissionDate: Date; diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts index efdbf2ee18..1004bf8ff6 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.spec.ts @@ -4,12 +4,14 @@ import { classes } from 'automapper-classes'; import { AutomapperModule } from 'automapper-nestjs'; import { ClsService } from 'nestjs-cls'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; +import { PlanningReviewProfile } from '../../common/automapper/planning-review.automapper.profile'; import { FileNumberService } from '../../file-number/file-number.service'; import { Board } from '../board/board.entity'; import { BoardService } from '../board/board.service'; import { PlanningReferral } from './planning-referral/planning-referral.entity'; import { PlanningReferralService } from './planning-referral/planning-referral.service'; import { PlanningReviewController } from './planning-review.controller'; +import { PlanningReview } from './planning-review.entity'; import { PlanningReviewService } from './planning-review.service'; describe('PlanningReviewController', () => { @@ -31,6 +33,7 @@ describe('PlanningReviewController', () => { ], controllers: [PlanningReviewController], providers: [ + PlanningReviewProfile, { provide: PlanningReviewService, useValue: mockService, @@ -78,4 +81,20 @@ describe('PlanningReviewController', () => { expect(mockPlanningReferralService.get).toHaveBeenCalledTimes(1); expect(mockPlanningReferralService.mapToDtos).toHaveBeenCalledTimes(1); }); + + it('should call service for fetch types', async () => { + mockService.listTypes.mockResolvedValue([]); + + await controller.fetchTypes(); + + expect(mockService.listTypes).toHaveBeenCalledTimes(1); + }); + + it('should call service for fetch by file number', async () => { + mockService.getDetailedReview.mockResolvedValue(new PlanningReview()); + + await controller.fetchByFileNumber('file-number'); + + expect(mockService.getDetailedReview).toHaveBeenCalledTimes(1); + }); }); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts index 793b1b879e..374073a846 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; @@ -12,8 +12,11 @@ import { PlanningReferralService } from './planning-referral/planning-referral.s import { PlanningReviewType } from './planning-review-type.entity'; import { CreatePlanningReviewDto, + PlanningReviewDetailedDto, PlanningReviewTypeDto, + UpdatePlanningReviewDto, } from './planning-review.dto'; +import { PlanningReview } from './planning-review.entity'; import { PlanningReviewService } from './planning-review.service'; @Controller('planning-review') @@ -59,4 +62,27 @@ export class PlanningReviewController { const mapped = await this.planningReferralService.mapToDtos([referral]); return mapped[0]; } + + @Get('/:fileNumber') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async fetchByFileNumber(@Param('fileNumber') fileNumber: string) { + const review = + await this.planningReviewService.getDetailedReview(fileNumber); + + return this.mapper.map(review, PlanningReview, PlanningReviewDetailedDto); + } + + @Post('/:fileNumber') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async updateByFileNumber( + @Param('fileNumber') fileNumber: string, + @Body() updateDto: UpdatePlanningReviewDto, + ) { + const review = await this.planningReviewService.update( + fileNumber, + updateDto, + ); + + return this.mapper.map(review, PlanningReview, PlanningReviewDetailedDto); + } } diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts index 2dd04a6eae..7135eddd5a 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.dto.ts @@ -47,9 +47,15 @@ export class CreatePlanningReviewDto { } export class PlanningReviewDto { + @AutoMap() + uuid: string; + @AutoMap() fileNumber: string; + @AutoMap(() => String) + legacyId: string | null; + @AutoMap() open: boolean; @@ -88,3 +94,18 @@ export class PlanningReferralDto { @AutoMap(() => CardDto) card: CardDto; } + +export class PlanningReviewDetailedDto extends PlanningReviewDto { + @AutoMap(() => [PlanningReferralDto]) + referrals: PlanningReferralDto[]; +} + +export class UpdatePlanningReviewDto { + @IsString() + @IsOptional() + open?: boolean; + + @IsString() + @IsOptional() + typeCode?: string; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts index 9fa30d217e..a6141523f6 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.entity.ts @@ -1,8 +1,10 @@ -import { Column, Entity, Index, ManyToOne } from 'typeorm'; +import { AutoMap } from 'automapper-classes'; +import { Column, Entity, Index, ManyToOne, OneToMany } from 'typeorm'; import { Base } from '../../common/entities/base.entity'; import { User } from '../../user/user.entity'; import { ApplicationRegion } from '../code/application-code/application-region/application-region.entity'; import { LocalGovernment } from '../local-government/local-government.entity'; +import { PlanningReferral } from './planning-referral/planning-referral.entity'; import { PlanningReviewType } from './planning-review-type.entity'; @Entity({ @@ -37,9 +39,14 @@ export class PlanningReview extends Base { @Column() regionCode: string; + @AutoMap(() => PlanningReviewType) @ManyToOne(() => PlanningReviewType, { nullable: false }) type: PlanningReviewType; + @AutoMap(() => [PlanningReferral]) + @OneToMany(() => PlanningReferral, (referral) => referral.planningReview) + referrals: PlanningReferral[]; + @Column() typeCode: string; diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts index eff498dfd6..447a0327f4 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.service.spec.ts @@ -104,9 +104,18 @@ describe('PlanningReviewService', () => { uuid: '5', }; mockRepository.find.mockResolvedValue([]); + await service.getBy(mockFilter); expect(mockRepository.find).toHaveBeenCalledTimes(1); expect(mockRepository.find.mock.calls[0][0]!.where).toEqual(mockFilter); }); + + it('should call through to the repo for getDetailedReview', async () => { + mockRepository.findOneOrFail.mockResolvedValue(new PlanningReview()); + + await service.getDetailedReview('file-number'); + + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1); + }); }); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts index 7637d37df4..7b1d0c9520 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.service.ts @@ -6,6 +6,7 @@ import { InjectMapper } from 'automapper-nestjs'; import { FindOptionsRelations, FindOptionsWhere, Repository } from 'typeorm'; import { FileNumberService } from '../../file-number/file-number.service'; import { formatIncomingDate } from '../../utils/incoming-date.formatter'; +import { filterUndefined } from '../../utils/undefined'; import { Board } from '../board/board.entity'; import { CARD_TYPE } from '../card/card-type/card-type.entity'; import { CardService } from '../card/card.service'; @@ -14,6 +15,7 @@ import { PlanningReviewType } from './planning-review-type.entity'; import { CreatePlanningReviewDto, PlanningReviewDto, + UpdatePlanningReviewDto, } from './planning-review.dto'; import { PlanningReview } from './planning-review.entity'; @@ -34,6 +36,7 @@ export class PlanningReviewService { private DEFAULT_RELATIONS: FindOptionsRelations = { localGovernment: true, region: true, + type: true, }; async create(data: CreatePlanningReviewDto, board: Board) { @@ -89,6 +92,18 @@ export class PlanningReviewService { }); } + getDetailedReview(fileNumber: string) { + return this.reviewRepository.findOneOrFail({ + where: { + fileNumber, + }, + relations: { + ...this.DEFAULT_RELATIONS, + referrals: true, + }, + }); + } + private get(uuid: string) { return this.reviewRepository.findOne({ where: { @@ -105,4 +120,21 @@ export class PlanningReviewService { }, }); } + + async update(fileNumber: string, updateDto: UpdatePlanningReviewDto) { + const existingApp = await this.reviewRepository.findOneOrFail({ + where: { + fileNumber, + }, + }); + + existingApp.open = filterUndefined(updateDto.open, existingApp.open); + existingApp.typeCode = filterUndefined( + updateDto.typeCode, + existingApp.typeCode, + ); + + await this.reviewRepository.save(existingApp); + return this.getDetailedReview(fileNumber); + } } diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts index 6053726a9b..6d3c7fcb52 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.controller.ts @@ -24,6 +24,7 @@ import { UpdateStaffJournalDto, CreateNoticeOfIntentStaffJournalDto, CreateNotificationStaffJournalDto, + CreatePlanningReviewStaffJournalDto, } from './staff-journal.dto'; import { StaffJournal } from './staff-journal.entity'; import { StaffJournalService } from './staff-journal.service'; @@ -92,6 +93,21 @@ export class StaffJournalController { return this.autoMapper.map(newRecord, StaffJournal, StaffJournalDto); } + @Post('/planning-review') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async createForPlanningReview( + @Body() record: CreatePlanningReviewStaffJournalDto, + @Req() req, + ): Promise { + const newRecord = await this.staffJournalService.createForPlanningReview( + record.planningReviewUuid, + record.body, + req.user.entity, + ); + + return this.autoMapper.map(newRecord, StaffJournal, StaffJournalDto); + } + @Patch() @UserRoles(...ROLES_ALLOWED_BOARDS) async update( diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts index 60c42b0704..4e15dcd8af 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.dto.ts @@ -20,42 +20,38 @@ export class StaffJournalDto { isEditable = false; } -export class CreateApplicationStaffJournalDto { - @IsString() - @IsNotEmpty() - applicationUuid: string; - +class BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() body: string; } -export class CreateNoticeOfIntentStaffJournalDto { +export class CreateApplicationStaffJournalDto extends BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() - noticeOfIntentUuid: string; + applicationUuid: string; +} +export class CreateNoticeOfIntentStaffJournalDto extends BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() - body: string; + noticeOfIntentUuid: string; } -export class CreateNotificationStaffJournalDto { +export class CreateNotificationStaffJournalDto extends BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() notificationUuid: string; +} +export class CreatePlanningReviewStaffJournalDto extends BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() - body: string; + planningReviewUuid: string; } -export class UpdateStaffJournalDto { +export class UpdateStaffJournalDto extends BaseCreateStaffJournalDto { @IsString() @IsNotEmpty() uuid: string; - - @IsString() - @IsNotEmpty() - body: string; } diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts index 0dbf764297..d9726c024a 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.entity.ts @@ -5,6 +5,7 @@ import { User } from '../../user/user.entity'; import { Application } from '../application/application.entity'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { Notification } from '../notification/notification.entity'; +import { PlanningReview } from '../planning-review/planning-review.entity'; @Entity() export class StaffJournal extends Base { @@ -48,4 +49,11 @@ export class StaffJournal extends Base { @Column({ nullable: true }) @Index() notificationUuid: string; + + @ManyToOne(() => PlanningReview) + planningReview: PlanningReview | null; + + @Column({ nullable: true }) + @Index() + planningReviewUuid: string; } diff --git a/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts b/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts index 729d6ca212..a3ab5c8c59 100644 --- a/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts +++ b/services/apps/alcs/src/alcs/staff-journal/staff-journal.service.ts @@ -32,6 +32,9 @@ export class StaffJournalService { { notificationUuid: parentUuid, }, + { + planningReviewUuid: parentUuid, + }, ], relations: this.DEFAULT_STAFF_JOURNAL_RELATIONS, order: { @@ -91,6 +94,20 @@ export class StaffJournalService { return await this.staffJournalRepository.save(record); } + async createForPlanningReview( + planningReviewUuid: string, + noteBody: string, + author: User, + ) { + const record = new StaffJournal({ + body: noteBody, + planningReviewUuid, + author, + }); + + return await this.staffJournalRepository.save(record); + } + async delete(uuid: string): Promise { const note = await this.staffJournalRepository.findOne({ where: { uuid }, diff --git a/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts index 6d2fecc01e..27d1bfdd6b 100644 --- a/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts @@ -1,10 +1,11 @@ +import { Injectable } from '@nestjs/common'; import { createMap, forMember, mapFrom, Mapper } from 'automapper-core'; import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; import { PlanningReferral } from '../../alcs/planning-review/planning-referral/planning-referral.entity'; import { PlanningReviewType } from '../../alcs/planning-review/planning-review-type.entity'; import { PlanningReferralDto, + PlanningReviewDetailedDto, PlanningReviewDto, PlanningReviewTypeDto, } from '../../alcs/planning-review/planning-review.dto'; @@ -33,6 +34,7 @@ export class PlanningReviewProfile extends AutomapperProfile { mapFrom((entity) => entity.submissionDate?.getTime()), ), ); + createMap(mapper, PlanningReview, PlanningReviewDetailedDto); }; } } diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709752843125-add_legacy_id_to_planning_review.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709752843125-add_legacy_id_to_planning_review.ts new file mode 100644 index 0000000000..bba87938c6 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1709752843125-add_legacy_id_to_planning_review.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddLegacyIdToPlanningReview1709752843125 + implements MigrationInterface +{ + name = 'AddLegacyIdToPlanningReview1709752843125'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_referral" ADD "legacy_id" text`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_referral" DROP COLUMN "legacy_id"`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709771987741-add_pr_staff_journal.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709771987741-add_pr_staff_journal.ts new file mode 100644 index 0000000000..fc26c56780 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1709771987741-add_pr_staff_journal.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPrStaffJournal1709771987741 implements MigrationInterface { + name = 'AddPrStaffJournal1709771987741'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" ADD "planning_review_uuid" uuid`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_dd6d16cefeda057f9f7d1f909b" ON "alcs"."staff_journal" ("planning_review_uuid") `, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" ADD CONSTRAINT "FK_dd6d16cefeda057f9f7d1f909bc" FOREIGN KEY ("planning_review_uuid") REFERENCES "alcs"."planning_review"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" DROP CONSTRAINT "FK_dd6d16cefeda057f9f7d1f909bc"`, + ); + await queryRunner.query( + `DROP INDEX "alcs"."IDX_dd6d16cefeda057f9f7d1f909b"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."staff_journal" DROP COLUMN "planning_review_uuid"`, + ); + } +}