From 138bd54292f85084f0feb7f7f36b462b2210cc96 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan Date: Wed, 6 Mar 2024 09:50:44 -0800 Subject: [PATCH 1/2] Planning Reviews V2 Pt.1 * Delete old V1 reviews * Add Referrals as sub entities * Update creation flow and dialog for V2 * Update unarchive card * Remove Non-application search as it will become Planning Review Search --- alcs-frontend/src/app/app-routing.module.ts | 8 + .../app/features/board/board.component.html | 33 +--- .../features/board/board.component.spec.ts | 35 ++-- .../src/app/features/board/board.component.ts | 158 +++++++++--------- .../card-dialog/card-dialog.component.ts | 2 +- ...eate-planning-review-dialog.component.html | 103 ++++++------ ...eate-planning-review-dialog.component.scss | 4 + ...e-planning-review-dialog.component.spec.ts | 1 - ...create-planning-review-dialog.component.ts | 34 +++- .../planning-review-dialog.component.html | 23 ++- .../planning-review-dialog.component.spec.ts | 23 ++- .../planning-review-dialog.component.ts | 59 +++++-- .../home/assigned/assigned.component.spec.ts | 2 +- .../home/assigned/assigned.component.ts | 17 +- .../planning-review.component.html | 1 + .../planning-review.component.scss | 0 .../planning-review.component.spec.ts | 21 +++ .../planning-review.component.ts | 10 ++ .../planning-review/planning-review.module.ts | 18 ++ .../src/app/services/board/board.dto.ts | 4 +- .../src/app/services/home/home.service.ts | 8 +- .../planning-referral.service.ts | 33 ++++ .../planning-review/planning-review.dto.ts | 28 +++- .../planning-review.service.spec.ts | 22 +-- .../planning-review.service.ts | 19 ++- .../unarchive-card.service.spec.ts | 26 +-- .../unarchive-card/unarchive-card.service.ts | 48 +++--- .../src/alcs/board/board.controller.spec.ts | 27 +-- .../alcs/src/alcs/board/board.controller.ts | 11 +- .../apps/alcs/src/alcs/board/board.dto.ts | 1 + .../alcs/card/card-type/card-type.entity.ts | 4 +- .../src/alcs/home/home.controller.spec.ts | 75 ++++----- .../alcs/src/alcs/home/home.controller.ts | 70 ++++---- .../planning-referral.controller.spec.ts | 58 +++++++ .../planning-referral.controller.ts | 29 ++++ .../planning-referral.entity.ts | 49 ++++++ .../planning-referral.service.spec.ts | 69 ++++++++ .../planning-referral.service.ts | 82 +++++++++ .../planning-review-type.entity.ts | 29 ++++ .../planning-review.controller.spec.ts | 48 +++--- .../planning-review.controller.ts | 50 ++++-- .../planning-review/planning-review.dto.ts | 65 +++++-- .../planning-review/planning-review.entity.ts | 41 +++-- .../planning-review/planning-review.module.ts | 24 ++- .../planning-review.service.spec.ts | 115 +++++-------- .../planning-review.service.ts | 141 ++++------------ .../non-applications-view.entity.ts | 96 ----------- .../non-applications.service.spec.ts | 90 ---------- .../non-applications.service.ts | 103 ------------ .../src/alcs/search/search.controller.spec.ts | 116 +------------ .../alcs/src/alcs/search/search.controller.ts | 80 +-------- .../apps/alcs/src/alcs/search/search.dto.ts | 1 - .../alcs/src/alcs/search/search.module.ts | 5 - .../src/alcs/search/search.service.spec.ts | 30 ---- .../alcs/src/alcs/search/search.service.ts | 13 +- .../planning-meeting.automapper.profile.ts | 18 -- .../planning-review.automapper.profile.ts | 38 +++++ .../1709662671997-planning_reviews_v2.ts | 108 ++++++++++++ .../1709663586391-seed_planning_reviews_v2.ts | 61 +++++++ 59 files changed, 1317 insertions(+), 1170 deletions(-) create mode 100644 alcs-frontend/src/app/features/planning-review/planning-review.component.html create mode 100644 alcs-frontend/src/app/features/planning-review/planning-review.component.scss create mode 100644 alcs-frontend/src/app/features/planning-review/planning-review.component.spec.ts create mode 100644 alcs-frontend/src/app/features/planning-review/planning-review.component.ts create mode 100644 alcs-frontend/src/app/features/planning-review/planning-review.module.ts create mode 100644 alcs-frontend/src/app/services/planning-review/planning-referral.service.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts create mode 100644 services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts delete mode 100644 services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts delete mode 100644 services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts delete mode 100644 services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts delete mode 100644 services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts create mode 100644 services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts diff --git a/alcs-frontend/src/app/app-routing.module.ts b/alcs-frontend/src/app/app-routing.module.ts index 94d71b7f2f..ae6b1aa322 100644 --- a/alcs-frontend/src/app/app-routing.module.ts +++ b/alcs-frontend/src/app/app-routing.module.ts @@ -53,6 +53,14 @@ const routes: Routes = [ }, loadChildren: () => import('./features/notification/notification.module').then((m) => m.NotificationModule), }, + { + path: 'planning-review', + canActivate: [HasRolesGuard], + data: { + roles: ROLES_ALLOWED_APPLICATIONS, + }, + loadChildren: () => import('./features/planning-review/planning-review.module').then((m) => m.PlanningReviewModule), + }, { path: 'schedule', canActivate: [HasRolesGuard], diff --git a/alcs-frontend/src/app/features/board/board.component.html b/alcs-frontend/src/app/features/board/board.component.html index a47d05b873..1a432a257c 100644 --- a/alcs-frontend/src/app/features/board/board.component.html +++ b/alcs-frontend/src/app/features/board/board.component.html @@ -7,34 +7,17 @@

- + + + + - - - - - - -
diff --git a/alcs-frontend/src/app/features/board/board.component.spec.ts b/alcs-frontend/src/app/features/board/board.component.spec.ts index e095242b07..4c1c21ad08 100644 --- a/alcs-frontend/src/app/features/board/board.component.spec.ts +++ b/alcs-frontend/src/app/features/board/board.component.spec.ts @@ -23,7 +23,8 @@ import { NoticeOfIntentModificationService } from '../../services/notice-of-inte import { NoticeOfIntentService } from '../../services/notice-of-intent/notice-of-intent.service'; import { NotificationDto } from '../../services/notification/notification.dto'; import { NotificationService } from '../../services/notification/notification.service'; -import { PlanningReviewDto } from '../../services/planning-review/planning-review.dto'; +import { PlanningReferralService } from '../../services/planning-review/planning-referral.service'; +import { PlanningReferralDto, PlanningReviewDto } from '../../services/planning-review/planning-review.dto'; import { PlanningReviewService } from '../../services/planning-review/planning-review.service'; import { ToastService } from '../../services/toast/toast.service'; import { CardType } from '../../shared/card/card.component'; @@ -40,7 +41,7 @@ describe('BoardComponent', () => { let router: DeepMocked; let cardService: DeepMocked; let reconsiderationService: DeepMocked; - let planningReviewService: DeepMocked; + let planningReferralService: DeepMocked; let modificationService: DeepMocked; let covenantService: DeepMocked; let titleService: DeepMocked; @@ -96,7 +97,7 @@ describe('BoardComponent', () => { applications: [], covenants: [], modifications: [], - planningReviews: [], + planningReferrals: [], reconsiderations: [], noticeOfIntents: [], noiModifications: [], @@ -109,7 +110,7 @@ describe('BoardComponent', () => { router = createMock(); cardService = createMock(); reconsiderationService = createMock(); - planningReviewService = createMock(); + planningReferralService = createMock(); modificationService = createMock(); covenantService = createMock(); titleService = createMock(); @@ -162,8 +163,8 @@ describe('BoardComponent', () => { useValue: reconsiderationService, }, { - provide: PlanningReviewService, - useValue: planningReviewService, + provide: PlanningReferralService, + useValue: planningReferralService, }, { provide: ApplicationModificationService, @@ -223,7 +224,7 @@ describe('BoardComponent', () => { applications: [mockApplication], covenants: [], modifications: [], - planningReviews: [], + planningReferrals: [], reconsiderations: [], noticeOfIntents: [], noiModifications: [], @@ -244,7 +245,7 @@ describe('BoardComponent', () => { applications: [], covenants: [], modifications: [], - planningReviews: [], + planningReferrals: [], reconsiderations: [mockRecon], noticeOfIntents: [], noiModifications: [], @@ -287,7 +288,7 @@ describe('BoardComponent', () => { applications: [mockApplication, highPriorityApplication, highActiveDays], covenants: [], modifications: [], - planningReviews: [], + planningReferrals: [], reconsiderations: [], noticeOfIntents: [], noiModifications: [], @@ -311,7 +312,7 @@ describe('BoardComponent', () => { new Map([ ['card', 'app-id'], ['type', CardType.APP], - ]) + ]), ); await sleep(1); @@ -327,7 +328,7 @@ describe('BoardComponent', () => { new Map([ ['card', 'app-id'], ['type', CardType.RECON], - ]) + ]), ); await sleep(1); @@ -337,18 +338,18 @@ describe('BoardComponent', () => { }); it('should load planning review and open dialog when url is set', async () => { - planningReviewService.fetchByCardUuid.mockResolvedValue({} as PlanningReviewDto); + planningReferralService.fetchByCardUuid.mockResolvedValue({} as PlanningReferralDto); queryParamMapEmitter.next( new Map([ ['card', 'app-id'], ['type', CardType.PLAN], - ]) + ]), ); await sleep(1); - expect(planningReviewService.fetchByCardUuid).toHaveBeenCalledTimes(1); + expect(planningReferralService.fetchByCardUuid).toHaveBeenCalledTimes(1); expect(dialog.open).toHaveBeenCalledTimes(1); }); @@ -359,7 +360,7 @@ describe('BoardComponent', () => { new Map([ ['card', 'app-id'], ['type', CardType.COV], - ]) + ]), ); await sleep(1); @@ -375,7 +376,7 @@ describe('BoardComponent', () => { new Map([ ['card', 'app-id'], ['type', CardType.NOTIFICATION], - ]) + ]), ); await sleep(1); @@ -391,7 +392,7 @@ describe('BoardComponent', () => { new Map([ ['card', 'app-id'], ['type', CardType.COV], - ]) + ]), ); await sleep(1); diff --git a/alcs-frontend/src/app/features/board/board.component.ts b/alcs-frontend/src/app/features/board/board.component.ts index 33e83eea8e..f79667dc02 100644 --- a/alcs-frontend/src/app/features/board/board.component.ts +++ b/alcs-frontend/src/app/features/board/board.component.ts @@ -23,13 +23,12 @@ import { NoticeOfIntentDto } from '../../services/notice-of-intent/notice-of-int import { NoticeOfIntentService } from '../../services/notice-of-intent/notice-of-intent.service'; import { NotificationDto } from '../../services/notification/notification.dto'; import { NotificationService } from '../../services/notification/notification.service'; -import { PlanningReviewDto } from '../../services/planning-review/planning-review.dto'; -import { PlanningReviewService } from '../../services/planning-review/planning-review.service'; +import { PlanningReferralService } from '../../services/planning-review/planning-referral.service'; +import { PlanningReferralDto } from '../../services/planning-review/planning-review.dto'; import { ToastService } from '../../services/toast/toast.service'; import { COVENANT_TYPE_LABEL, MODIFICATION_TYPE_LABEL, - PLANNING_TYPE_LABEL, RECON_TYPE_LABEL, RETROACTIVE_TYPE_LABEL, } from '../../shared/application-type-pill/application-type-pill.constants'; @@ -38,12 +37,9 @@ import { DragDropColumn } from '../../shared/drag-drop-board/drag-drop-column.in import { AppModificationDialogComponent } from './dialogs/app-modification/app-modification-dialog.component'; import { CreateAppModificationDialogComponent } from './dialogs/app-modification/create/create-app-modification-dialog.component'; import { ApplicationDialogComponent } from './dialogs/application/application-dialog.component'; -import { CreateApplicationDialogComponent } from './dialogs/application/create/create-application-dialog.component'; import { CovenantDialogComponent } from './dialogs/covenant/covenant-dialog.component'; -import { CreateCovenantDialogComponent } from './dialogs/covenant/create/create-covenant-dialog.component'; import { CreateNoiModificationDialogComponent } from './dialogs/noi-modification/create/create-noi-modification-dialog.component'; import { NoiModificationDialogComponent } from './dialogs/noi-modification/noi-modification-dialog.component'; -import { CreateNoticeOfIntentDialogComponent } from './dialogs/notice-of-intent/create/create-notice-of-intent-dialog.component'; import { NoticeOfIntentDialogComponent } from './dialogs/notice-of-intent/notice-of-intent-dialog.component'; import { NotificationDialogComponent } from './dialogs/notification/notification-dialog.component'; import { CreatePlanningReviewDialogComponent } from './dialogs/planning-review/create/create-planning-review-dialog.component'; @@ -67,19 +63,52 @@ export class BoardComponent implements OnInit, OnDestroy { $destroy = new Subject<void>(); cards: CardData[] = []; columns: DragDropColumn[] = []; + boards: BoardWithFavourite[] = []; boardTitle = ''; boardIsFavourite = false; - boardHasCreateApplication = false; - boardHasCreatePlanningReview = false; - boardHasCreateReconsideration = false; - boardHasCreateAppModification = false; - boardHasCreateCovenant = false; - boardHasCreateNOI = false; - boardHasCreateNOIModification = false; currentBoardCode = ''; - selectedBoardCode?: string; - boards: BoardWithFavourite[] = []; + creatableCards: { + label: string; + dialog: ComponentType<any>; + }[] = []; + + private createCardMap = new Map< + CardType, + { + label: string; + dialog: ComponentType<any>; + } + >([ + [ + CardType.RECON, + { + label: 'Reconsideration', + dialog: CreateReconsiderationDialogComponent, + }, + ], + [ + CardType.MODI, + { + label: 'Application Modification', + dialog: CreateAppModificationDialogComponent, + }, + ], + [ + CardType.NOI_MODI, + { + label: 'NOI Modification', + dialog: CreateNoiModificationDialogComponent, + }, + ], + [ + CardType.PLAN, + { + label: 'Planning Review', + dialog: CreatePlanningReviewDialogComponent, + }, + ], + ]); constructor( private applicationService: ApplicationService, @@ -90,13 +119,13 @@ export class BoardComponent implements OnInit, OnDestroy { private router: Router, private cardService: CardService, private reconsiderationService: ApplicationReconsiderationService, - private planningReviewService: PlanningReviewService, + private planningReferralService: PlanningReferralService, private modificationService: ApplicationModificationService, private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, private noiModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, - private titleService: Title + private titleService: Title, ) {} ngOnInit() { @@ -140,44 +169,8 @@ export class BoardComponent implements OnInit, OnDestroy { this.setUrl(card.uuid, card.cardType); } - onApplicationCreate() { - this.openDialog(CreateApplicationDialogComponent, { - currentBoardCode: this.selectedBoardCode, - }); - } - - onReconsiderationCreate() { - this.openDialog(CreateReconsiderationDialogComponent, { - currentBoardCode: this.selectedBoardCode, - }); - } - - onCreatePlanningReview() { - this.openDialog(CreatePlanningReviewDialogComponent, { - currentBoardCode: this.selectedBoardCode, - }); - } - - onCreateAppModification() { - this.openDialog(CreateAppModificationDialogComponent, { - currentBoardCode: this.selectedBoardCode, - }); - } - - onCreateCovenant() { - this.openDialog(CreateCovenantDialogComponent, { - currentBoardCode: this.selectedBoardCode, - }); - } - - onCreateNoticeOfIntent() { - this.openDialog(CreateNoticeOfIntentDialogComponent, { - currentBoardCode: this.selectedBoardCode, - }); - } - - onCreateNoiModifications() { - this.openDialog(CreateNoiModificationDialogComponent, { + onOpenCreateDialog(component: ComponentType<any>) { + this.openDialog(component, { currentBoardCode: this.selectedBoardCode, }); } @@ -228,13 +221,18 @@ export class BoardComponent implements OnInit, OnDestroy { const board = response.board; this.boardTitle = board.title; - this.boardHasCreateApplication = board.createCardTypes.includes(CardType.APP); - this.boardHasCreatePlanningReview = board.createCardTypes.includes(CardType.PLAN); - this.boardHasCreateReconsideration = board.createCardTypes.includes(CardType.RECON); - this.boardHasCreateAppModification = board.createCardTypes.includes(CardType.MODI); - this.boardHasCreateCovenant = board.createCardTypes.includes(CardType.COV); - this.boardHasCreateNOI = board.createCardTypes.includes(CardType.NOI); - this.boardHasCreateNOIModification = board.createCardTypes.includes(CardType.NOI_MODI); + + const creatableCards: { + label: string; + dialog: ComponentType<any>; + }[] = []; + for (const cardType of board.createCardTypes) { + const creator = this.createCardMap.get(cardType); + if (creator) { + creatableCards.push(creator); + } + } + this.creatableCards = creatableCards; const allStatuses = board.statuses.map((status) => status.statusCode); @@ -249,12 +247,12 @@ export class BoardComponent implements OnInit, OnDestroy { private mapAndSortCards(response: CardsDto, boardCode: string) { const mappedApps = response.applications.map(this.mapApplicationDtoToCard.bind(this)); const mappedRecons = response.reconsiderations.map(this.mapReconsiderationDtoToCard.bind(this)); - const mappedReviewMeetings = response.planningReviews.map(this.mapPlanningReviewToCard.bind(this)); + const mappedPlanningReferrals = response.planningReferrals.map(this.mapPlanningReferralToCard.bind(this)); const mappedModifications = response.modifications.map(this.mapModificationToCard.bind(this)); const mappedCovenants = response.covenants.map(this.mapCovenantToCard.bind(this)); const mappedNoticeOfIntents = response.noticeOfIntents.map(this.mapNoticeOfIntentToCard.bind(this)); const mappedNoticeOfIntentModifications = response.noiModifications.map( - this.mapNoticeOfIntentModificationToCard.bind(this) + this.mapNoticeOfIntentModificationToCard.bind(this), ); const mappedNotifications = response.notifications.map(this.mapNotificationToCard.bind(this)); if (boardCode === BOARD_TYPE_CODES.VETT) { @@ -267,7 +265,7 @@ export class BoardComponent implements OnInit, OnDestroy { this.cards = [ ...[...mappedNoticeOfIntents, ...mappedNoticeOfIntentModifications].sort(vettingSort), ...[...mappedApps, ...mappedRecons, ...mappedModifications].sort(vettingSort), - ...[...mappedReviewMeetings, ...mappedCovenants].sort(vettingSort), + ...[...mappedPlanningReferrals, ...mappedCovenants].sort(vettingSort), ...mappedNotifications, ]; } else if (boardCode === BOARD_TYPE_CODES.NOI) { @@ -291,7 +289,7 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedApps, ...mappedRecons, ...mappedModifications, - ...mappedReviewMeetings, + ...mappedPlanningReferrals, ...mappedCovenants, ...mappedNotifications, ].sort(noiSort); @@ -306,7 +304,7 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedApps.filter((a) => a.highPriority).sort((a, b) => b.activeDays! - a.activeDays!), ...mappedModifications.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedRecons.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), - ...mappedReviewMeetings.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), + ...mappedPlanningReferrals.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedCovenants.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedNotifications.filter((r) => r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), // non-high priority @@ -319,9 +317,9 @@ export class BoardComponent implements OnInit, OnDestroy { ...mappedApps.filter((a) => !a.highPriority).sort((a, b) => b.activeDays! - a.activeDays!), ...mappedModifications.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedRecons.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), - ...mappedReviewMeetings.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), + ...mappedPlanningReferrals.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ...mappedCovenants.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), - ...mappedNotifications.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived) + ...mappedNotifications.filter((r) => !r.highPriority).sort((a, b) => a.dateReceived - b.dateReceived), ); this.cards = sorted; } @@ -383,20 +381,20 @@ export class BoardComponent implements OnInit, OnDestroy { }; } - private mapPlanningReviewToCard(meeting: PlanningReviewDto): CardData { + private mapPlanningReferralToCard(referral: PlanningReferralDto): CardData { return { - status: meeting.card.status.code, + status: referral.card.status.code, typeLabel: 'Non-Application', - title: `${meeting.fileNumber} (${meeting.type})`, - titleTooltip: meeting.type, - assignee: meeting.card.assignee, - id: meeting.card.uuid, - labels: [PLANNING_TYPE_LABEL], + title: `${referral.planningReview.fileNumber} (${referral.planningReview.documentName})`, + titleTooltip: referral.planningReview.type.label, + assignee: referral.card.assignee, + id: referral.card.uuid, + labels: [referral.planningReview.type], cardType: CardType.PLAN, paused: false, - highPriority: meeting.card.highPriority, - cardUuid: meeting.card.uuid, - dateReceived: meeting.card.createdAt, + highPriority: referral.card.highPriority, + cardUuid: referral.card.uuid, + dateReceived: referral.card.createdAt, }; } @@ -517,7 +515,7 @@ export class BoardComponent implements OnInit, OnDestroy { this.openDialog(ReconsiderationDialogComponent, recon); break; case CardType.PLAN: - const planningReview = await this.planningReviewService.fetchByCardUuid(card.uuid); + const planningReview = await this.planningReferralService.fetchByCardUuid(card.uuid); this.openDialog(PlanningReviewDialogComponent, planningReview); break; case CardType.MODI: diff --git a/alcs-frontend/src/app/features/board/dialogs/card-dialog/card-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/card-dialog/card-dialog.component.ts index 626f270139..7338251c87 100644 --- a/alcs-frontend/src/app/features/board/dialogs/card-dialog/card-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/card-dialog/card-dialog.component.ts @@ -39,7 +39,7 @@ export class CardDialogComponent implements OnInit, OnDestroy { protected confirmationDialogService: ConfirmationDialogService, protected toastService: ToastService, protected userService: UserService, - protected boardService: BoardService + protected boardService: BoardService, ) {} ngOnInit(): void { diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.html index 923c72b159..6a8da68505 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.html @@ -4,61 +4,17 @@ <h2 class="card-title">Create Planning Review</h2> <form class="content" [formGroup]="createForm" (ngSubmit)="onSubmit()"> <mat-dialog-content> <div class="two-item-row"> - <mat-form-field appearance="outline"> - <mat-label>File ID</mat-label> - <input - id="fileNumber" - matInput - placeholder="791262" - formControlName="fileNumber" - [class.valid]=" - createForm.get('fileNumber')!.valid && - (createForm.get('fileNumber')!.dirty || createForm.get('fileNumber')!.touched) - " - [class.invalid]=" - createForm.get('fileNumber')!.invalid && - (createForm.get('fileNumber')!.dirty || createForm.get('fileNumber')!.touched) - " - /> - <mat-error - class="text-danger" - *ngIf="createForm.get('fileNumber')!.touched && createForm.get('fileNumber')!.hasError('required')" - > - This field is required. - </mat-error> - </mat-form-field> - <mat-form-field appearance="outline"> - <mat-label>Type</mat-label> - <input - id="type" - matInput - maxlength="40" - formControlName="type" - [class.valid]=" - createForm.get('type')!.valid && (createForm.get('type')!.dirty || createForm.get('type')!.touched) - " - [class.invalid]=" - createForm.get('type')!.invalid && (createForm.get('type')!.dirty || createForm.get('type')!.touched) - " - /> - <mat-error - class="text-danger" - *ngIf="createForm.get('type')!.touched && createForm.get('type')!.hasError('required')" - > - This field is required. - </mat-error> - </mat-form-field> <div> <ng-select appearance="outline" class="card-local-government" [items]="localGovernments" appendTo="body" - placeholder="Local Government *" + placeholder="Local Government*" bindLabel="name" bindValue="uuid" [clearable]="false" - formControlName="localGovernment" + [formControl]="localGovernmentControl" (change)="onSelectGovernment($event)" > <ng-template ng-option-tmp let-item="item" let-search="searchTerm"> @@ -69,24 +25,71 @@ <h2 class="card-title">Create Planning Review</h2> <div> <ng-select appearance="outline" - class="card-region" [items]="regions" appendTo="body" - placeholder="Region *" + placeholder="Region*" bindLabel="label" bindValue="code" [clearable]="false" - formControlName="region" + [formControl]="regionControl" > </ng-select> </div> </div> + <div class="two-item-row"> + <mat-form-field appearance="outline"> + <mat-label>Submitted to ALC</mat-label> + <input + matInput + (click)="submissionDate.open()" + [matDatepicker]="submissionDate" + [formControl]="submissionDateControl" + name="date" + id="date" + required + /> + <mat-datepicker-toggle matSuffix [for]="submissionDate"></mat-datepicker-toggle> + <mat-datepicker #submissionDate type="date"> </mat-datepicker> + </mat-form-field> + + <mat-form-field appearance="outline"> + <mat-label>Document Name</mat-label> + <input matInput placeholder="Document Name*" [formControl]="documentNameControl" required /> + </mat-form-field> + </div> + <div class="two-item-row"> + <div> + <ng-select + id="type" + appearance="outline" + [items]="types" + appendTo="body" + placeholder="Planning Review Type*" + bindLabel="label" + bindValue="code" + [clearable]="false" + [formControl]="typeControl" + > + </ng-select> + </div> + + <mat-form-field appearance="outline"> + <mat-label>Due Date</mat-label> + <input matInput (click)="dueDate.open()" [matDatepicker]="dueDate" [formControl]="dueDateControl" /> + <mat-datepicker-toggle matSuffix [for]="dueDate"></mat-datepicker-toggle> + <mat-datepicker #dueDate type="date"> </mat-datepicker> + </mat-form-field> + </div> + <mat-form-field class="description" appearance="outline"> + <mat-label>Description</mat-label> + <input matInput placeholder="Document Name*" [formControl]="descriptionControl" required /> + </mat-form-field> </mat-dialog-content> <mat-dialog-actions align="end"> <div class="button-container"> <button mat-stroked-button color="primary" [mat-dialog-close]="false">Cancel</button> <button [loading]="isLoading" mat-flat-button color="primary" type="submit" [disabled]="!createForm.valid"> - Save + Create </button> </div> </mat-dialog-actions> diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.scss b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.scss index 82f3eb6a10..50fe910c77 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.scss +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.scss @@ -9,3 +9,7 @@ grid-row-gap: 24px; margin-bottom: 24px; } + +.description { + width: 100%; +} diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.spec.ts b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.spec.ts index 038ef87ba2..4aa416f11e 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.spec.ts +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.spec.ts @@ -52,7 +52,6 @@ describe('CreatePlanningReviewDialogComponent', () => { fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('#fileNumber')).toBeTruthy(); expect(compiled.querySelector('#type')).toBeTruthy(); }); }); diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.ts b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.ts index 5e7b0444b4..c23c38c897 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.ts +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/create/create-planning-review-dialog.component.ts @@ -2,13 +2,17 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; +import { Moment } from 'moment'; import { Subject, takeUntil } from 'rxjs'; import { ApplicationRegionDto } from '../../../../../services/application/application-code.dto'; import { ApplicationLocalGovernmentDto } from '../../../../../services/application/application-local-government/application-local-government.dto'; import { ApplicationLocalGovernmentService } from '../../../../../services/application/application-local-government/application-local-government.service'; import { ApplicationService } from '../../../../../services/application/application.service'; import { CardService } from '../../../../../services/card/card.service'; -import { CreatePlanningReviewDto } from '../../../../../services/planning-review/planning-review.dto'; +import { + CreatePlanningReviewDto, + PlanningReviewTypeDto, +} from '../../../../../services/planning-review/planning-review.dto'; import { PlanningReviewService } from '../../../../../services/planning-review/planning-review.service'; @Component({ @@ -20,18 +24,25 @@ export class CreatePlanningReviewDialogComponent implements OnInit, OnDestroy { $destroy = new Subject<void>(); regions: ApplicationRegionDto[] = []; localGovernments: ApplicationLocalGovernmentDto[] = []; + types: PlanningReviewTypeDto[] = []; isLoading = false; - fileNumberControl = new FormControl<string | any>('', [Validators.required]); regionControl = new FormControl<string | null>(null, [Validators.required]); localGovernmentControl = new FormControl<string | null>(null, [Validators.required]); typeControl = new FormControl<string | null>(null, [Validators.required]); + documentNameControl = new FormControl<string | null>(null, [Validators.required]); + descriptionControl = new FormControl<string | null>(null, [Validators.required]); + submissionDateControl = new FormControl<Moment | null>(null, [Validators.required]); + dueDateControl = new FormControl<Moment | null>(null); createForm = new FormGroup({ - fileNumber: this.fileNumberControl, region: this.regionControl, localGovernment: this.localGovernmentControl, type: this.typeControl, + documentName: this.documentNameControl, + description: this.descriptionControl, + submissionDate: this.submissionDateControl, + dueDate: this.dueDateControl, }); constructor( @@ -58,6 +69,8 @@ export class CreatePlanningReviewDialogComponent implements OnInit, OnDestroy { this.applicationService.$applicationRegions.pipe(takeUntil(this.$destroy)).subscribe((regions) => { this.regions = regions; }); + + this.loadTypes(); } async onSubmit() { @@ -65,11 +78,13 @@ export class CreatePlanningReviewDialogComponent implements OnInit, OnDestroy { this.isLoading = true; const formValues = this.createForm.getRawValue(); const planningReview: CreatePlanningReviewDto = { - fileNumber: formValues.fileNumber!.trim(), regionCode: formValues.region!, localGovernmentUuid: formValues.localGovernment!, - type: formValues.type!, - boardCode: this.data.currentBoardCode, + typeCode: formValues.type!, + submissionDate: formValues.submissionDate!.valueOf(), + description: formValues.description!, + documentName: formValues.documentName!, + dueDate: formValues.dueDate?.valueOf(), }; const res = await this.planningReviewService.create(planningReview); @@ -95,4 +110,11 @@ export class CreatePlanningReviewDialogComponent implements OnInit, OnDestroy { this.$destroy.next(); this.$destroy.complete(); } + + private async loadTypes() { + const types = await this.planningReviewService.fetchTypes(); + if (types) { + this.types = types; + } + } } diff --git a/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.html b/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.html index df9bac4e20..54100e7d26 100644 --- a/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.html +++ b/alcs-frontend/src/app/features/board/dialogs/planning-review/planning-review-dialog.component.html @@ -1,6 +1,6 @@ <div mat-dialog-title> <div class="close"> - <h6 class="card-type-label">Non-Application</h6> + <h6 class="card-type-label">Planning Review</h6> <button mat-icon-button [mat-dialog-close]="isDirty"> <mat-icon>close</mat-icon> </button> @@ -9,12 +9,29 @@ <h6 class="card-type-label">Non-Application</h6> <div class="left"> <h3 class="card-title center"> <span class="margin-right">{{ cardTitle }}</span> - <app-application-type-pill [type]="planningType"></app-application-type-pill> + <app-application-type-pill *ngIf="planningType" [type]="planningType"></app-application-type-pill> </h3> </div> + <div class="center"> + <button + color="accent" + mat-flat-button + [mat-dialog-close]="isDirty" + [routerLink]="['planning-review', planningReview.fileNumber]" + > + View Detail + </button> + </div> </div> - <div class="split"> + <div> <span class="region">{{ planningReview.localGovernment.name }} - {{ planningReview.region.label }} Region</span> + </div> + <div class="split"> + <div class="body-text"> + <app-application-type-pill *ngIf="planningReview.open" [type]="OPEN_TYPE"></app-application-type-pill> + <app-application-type-pill *ngIf="!planningReview.open" [type]="CLOSED_TYPE"></app-application-type-pill> + <span>Due Date: {{ planningReferral.dueDate | momentFormat }}</span> + </div> <div class="right"> <button matTooltip="Move Board" [matMenuTriggerFor]="moveMenu" mat-icon-button> <mat-icon>move_down</mat-icon> 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 90fb212325..7cd024e858 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,6 +2,7 @@ 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'; @@ -11,12 +12,12 @@ import { BehaviorSubject } from 'rxjs'; import { BoardService, BoardWithFavourite } from '../../../../services/board/board.service'; import { CardDto } from '../../../../services/card/card.dto'; import { CardService } from '../../../../services/card/card.service'; -import { PlanningReviewDto } from '../../../../services/planning-review/planning-review.dto'; +import { PlanningReferralDto, PlanningReviewDto } from '../../../../services/planning-review/planning-review.dto'; import { ToastService } from '../../../../services/toast/toast.service'; -import { AssigneeDto, UserDto } from '../../../../services/user/user.dto'; +import { AssigneeDto } from '../../../../services/user/user.dto'; import { UserService } from '../../../../services/user/user.service'; import { ConfirmationDialogService } from '../../../../shared/confirmation-dialog/confirmation-dialog.service'; -import { SharedModule } from '../../../../shared/shared.module'; +import { MomentPipe } from '../../../../shared/pipes/moment.pipe'; import { PlanningReviewDialogComponent } from './planning-review-dialog.component'; describe('PlanningReviewDialogComponent', () => { @@ -25,8 +26,10 @@ describe('PlanningReviewDialogComponent', () => { let mockUserService: DeepMocked<UserService>; let mockBoardService: DeepMocked<BoardService>; - const mockReconDto: PlanningReviewDto = { - type: 'fake-type', + const mockPlanningReviewDto: PlanningReviewDto = { + documentName: '', + type: {} as any, + open: true, region: { code: 'region-code', label: 'region', @@ -39,12 +42,18 @@ describe('PlanningReviewDialogComponent', () => { isFirstNation: false, }, fileNumber: 'file-number', + }; + + const mockReferralDto: PlanningReferralDto = { card: { status: { code: 'FAKE_STATUS', }, boardCode: 'FAKE_BOARD', } as CardDto, + planningReview: mockPlanningReviewDto, + referralDescription: '', + submissionDate: 0, }; beforeEach(async () => { @@ -62,7 +71,7 @@ describe('PlanningReviewDialogComponent', () => { mockBoardService.$boards = new BehaviorSubject<BoardWithFavourite[]>([]); await TestBed.configureTestingModule({ - declarations: [PlanningReviewDialogComponent], + declarations: [PlanningReviewDialogComponent, MomentPipe], providers: [ { provide: MAT_DIALOG_DATA, @@ -108,7 +117,7 @@ describe('PlanningReviewDialogComponent', () => { fixture = TestBed.createComponent(PlanningReviewDialogComponent); component = fixture.componentInstance; - component.data = mockReconDto; + component.data = mockReferralDto; fixture.detectChanges(); }); 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 9a45cdee10..666a1c9e1c 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 @@ -1,17 +1,37 @@ -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { AuthenticationService } from '../../../../services/authentication/authentication.service'; import { BoardService, BoardWithFavourite } from '../../../../services/board/board.service'; import { CardService } from '../../../../services/card/card.service'; -import { PlanningReviewDto } from '../../../../services/planning-review/planning-review.dto'; -import { PlanningReviewService } from '../../../../services/planning-review/planning-review.service'; +import { PlanningReferralService } from '../../../../services/planning-review/planning-referral.service'; +import { PlanningReferralDto, PlanningReviewDto } from '../../../../services/planning-review/planning-review.dto'; import { ToastService } from '../../../../services/toast/toast.service'; import { UserService } from '../../../../services/user/user.service'; -import { PLANNING_TYPE_LABEL } from '../../../../shared/application-type-pill/application-type-pill.constants'; +import { ApplicationPill } from '../../../../shared/application-type-pill/application-type-pill.component'; 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', @@ -20,22 +40,25 @@ import { CardDialogComponent } from '../card-dialog/card-dialog.component'; export class PlanningReviewDialogComponent extends CardDialogComponent implements OnInit { selectedRegion?: string; title?: string; - planningType = PLANNING_TYPE_LABEL; + planningType?: ApplicationPill; cardTitle = ''; + OPEN_TYPE = OPEN_TYPE; + CLOSED_TYPE = CLOSED_TYPE; - planningReview: PlanningReviewDto = this.data; + planningReview: PlanningReviewDto = this.data.planningReview; + planningReferral: PlanningReferralDto = this.data; constructor( - @Inject(MAT_DIALOG_DATA) public data: PlanningReviewDto, + @Inject(MAT_DIALOG_DATA) public data: PlanningReferralDto, private dialogRef: MatDialogRef<PlanningReviewDialogComponent>, boardService: BoardService, userService: UserService, authService: AuthenticationService, toastService: ToastService, - private planningReviewService: PlanningReviewService, + private planningReferralService: PlanningReferralService, confirmationDialogService: ConfirmationDialogService, cardService: CardService, - private router: Router + private router: Router, ) { super(authService, dialogRef, cardService, confirmationDialogService, toastService, userService, boardService); } @@ -43,26 +66,30 @@ export class PlanningReviewDialogComponent extends CardDialogComponent implement override ngOnInit(): void { super.ngOnInit(); - this.planningReview = this.data; + this.planningReview = this.data.planningReview; + this.planningType = { + ...this.data.planningReview.type, + borderColor: this.data.planningReview.type.backgroundColor, + }; this.populateCardData(this.data.card); - this.selectedRegion = this.data.region.code; - this.cardTitle = `${this.data.fileNumber} (${this.data.type})`; + this.selectedRegion = this.data.planningReview.region.code; + this.cardTitle = `${this.data.planningReview.fileNumber} (${this.data.planningReview.documentName})`; this.title = this.planningReview.fileNumber; } private async reload() { - const planningReview = await this.planningReviewService.fetchByCardUuid(this.planningReview.card.uuid); - if (planningReview) { - this.populateCardData(planningReview.card); + const planningReferral = await this.planningReferralService.fetchByCardUuid(this.planningReferral.card.uuid); + if (planningReferral) { + await this.populateCardData(planningReferral.card); } } async onBoardSelected(board: BoardWithFavourite) { this.selectedBoard = board.code; try { - await this.boardService.changeBoard(this.planningReview.card.uuid, board.code); + await this.boardService.changeBoard(this.planningReferral.card.uuid, board.code); const loadedBoard = await this.boardService.fetchBoardDetail(board.code); if (loadedBoard) { this.boardStatuses = loadedBoard.statuses; diff --git a/alcs-frontend/src/app/features/home/assigned/assigned.component.spec.ts b/alcs-frontend/src/app/features/home/assigned/assigned.component.spec.ts index faaed9663b..2d24740773 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.spec.ts +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.spec.ts @@ -39,7 +39,7 @@ describe('AssignedComponent', () => { covenants: [], modifications: [], noticeOfIntentModifications: [], - planningReviews: [], + planningReferrals: [], reconsiderations: [], noticeOfIntents: [], notifications: [], diff --git a/alcs-frontend/src/app/features/home/assigned/assigned.component.ts b/alcs-frontend/src/app/features/home/assigned/assigned.component.ts index d899e8b003..ad2e3fd15b 100644 --- a/alcs-frontend/src/app/features/home/assigned/assigned.component.ts +++ b/alcs-frontend/src/app/features/home/assigned/assigned.component.ts @@ -8,7 +8,7 @@ import { HomeService } from '../../../services/home/home.service'; import { NoticeOfIntentModificationDto } from '../../../services/notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentDto } from '../../../services/notice-of-intent/notice-of-intent.dto'; import { NotificationDto } from '../../../services/notification/notification.dto'; -import { PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; +import { PlanningReferralDto, PlanningReviewDto } from '../../../services/planning-review/planning-review.dto'; import { COVENANT_TYPE_LABEL, MODIFICATION_TYPE_LABEL, @@ -31,7 +31,10 @@ export class AssignedComponent implements OnInit { notifications: AssignedToMeFile[] = []; totalFiles = 0; - constructor(private homeService: HomeService, private applicationService: ApplicationService) {} + constructor( + private homeService: HomeService, + private applicationService: ApplicationService, + ) {} ngOnInit(): void { this.applicationService.setup(); @@ -42,7 +45,7 @@ export class AssignedComponent implements OnInit { const { applications, reconsiderations, - planningReviews, + planningReferrals, modifications, covenants, noticeOfIntents, @@ -96,7 +99,7 @@ export class AssignedComponent implements OnInit { ]; this.nonApplications = [ - ...planningReviews + ...planningReferrals .filter((r) => r.card.highPriority) .map((r) => this.mapPlanning(r)) .sort((a, b) => a.date! - b.date!), @@ -104,7 +107,7 @@ export class AssignedComponent implements OnInit { .filter((r) => r.card.highPriority) .map((r) => this.mapCovenant(r)) .sort((a, b) => a.date! - b.date!), - ...planningReviews + ...planningReferrals .filter((r) => !r.card.highPriority) .map((r) => this.mapPlanning(r)) .sort((a, b) => a.date! - b.date!), @@ -140,9 +143,9 @@ export class AssignedComponent implements OnInit { }; } - private mapPlanning(p: PlanningReviewDto): AssignedToMeFile { + private mapPlanning(p: PlanningReferralDto): AssignedToMeFile { return { - title: `${p.fileNumber} (${p.type})`, + title: `${p.planningReview.fileNumber} (${p.planningReview.documentName})`, type: p.card.type, date: p.card.createdAt, card: p.card, 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 new file mode 100644 index 0000000000..aba50711ec --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.html @@ -0,0 +1 @@ +<p>planning-review works!</p> 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 new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..e8dac38a3c --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PlanningReviewComponent } from './planning-review.component'; + +describe('PlanningReviewComponent', () => { + let component: PlanningReviewComponent; + let fixture: ComponentFixture<PlanningReviewComponent>; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PlanningReviewComponent] + }); + fixture = TestBed.createComponent(PlanningReviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 0000000000..2ffef4fac0 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/planning-review.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-planning-review', + templateUrl: './planning-review.component.html', + styleUrls: ['./planning-review.component.scss'] +}) +export class PlanningReviewComponent { + +} 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 new file mode 100644 index 0000000000..54737bff42 --- /dev/null +++ b/alcs-frontend/src/app/features/planning-review/planning-review.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { SharedModule } from '../../shared/shared.module'; +import { PlanningReviewComponent } from './planning-review.component'; + +const routes: Routes = [ + { + path: ':fileNumber', + component: PlanningReviewComponent, + }, +]; + +@NgModule({ + declarations: [PlanningReviewComponent], + imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], +}) +export class PlanningReviewModule {} diff --git a/alcs-frontend/src/app/services/board/board.dto.ts b/alcs-frontend/src/app/services/board/board.dto.ts index 082e631b4e..5d44e29719 100644 --- a/alcs-frontend/src/app/services/board/board.dto.ts +++ b/alcs-frontend/src/app/services/board/board.dto.ts @@ -6,7 +6,7 @@ import { CovenantDto } from '../covenant/covenant.dto'; import { NoticeOfIntentModificationDto } from '../notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentDto } from '../notice-of-intent/notice-of-intent.dto'; import { NotificationDto } from '../notification/notification.dto'; -import { PlanningReviewDto } from '../planning-review/planning-review.dto'; +import { PlanningReferralDto, PlanningReviewDto } from '../planning-review/planning-review.dto'; export interface MinimalBoardDto { code: string; @@ -30,7 +30,7 @@ export interface CardsDto { board: BoardDto; applications: ApplicationDto[]; reconsiderations: ApplicationReconsiderationDto[]; - planningReviews: PlanningReviewDto[]; + planningReferrals: PlanningReferralDto[]; modifications: ApplicationModificationDto[]; covenants: CovenantDto[]; noticeOfIntents: NoticeOfIntentDto[]; diff --git a/alcs-frontend/src/app/services/home/home.service.ts b/alcs-frontend/src/app/services/home/home.service.ts index 4fb684b6f9..0a0c3384bc 100644 --- a/alcs-frontend/src/app/services/home/home.service.ts +++ b/alcs-frontend/src/app/services/home/home.service.ts @@ -10,7 +10,7 @@ import { CovenantDto } from '../covenant/covenant.dto'; import { NoticeOfIntentModificationDto } from '../notice-of-intent/notice-of-intent-modification/notice-of-intent-modification.dto'; import { NoticeOfIntentDto } from '../notice-of-intent/notice-of-intent.dto'; import { NotificationDto } from '../notification/notification.dto'; -import { PlanningReviewDto } from '../planning-review/planning-review.dto'; +import { PlanningReferralDto } from '../planning-review/planning-review.dto'; @Injectable({ providedIn: 'root', @@ -23,19 +23,19 @@ export class HomeService { this.http.get<{ applications: ApplicationDto[]; reconsiderations: ApplicationReconsiderationDto[]; - planningReviews: PlanningReviewDto[]; + planningReferrals: PlanningReferralDto[]; modifications: ApplicationModificationDto[]; covenants: CovenantDto[]; noticeOfIntents: NoticeOfIntentDto[]; noticeOfIntentModifications: NoticeOfIntentModificationDto[]; notifications: NotificationDto[]; - }>(`${environment.apiUrl}/home/assigned`) + }>(`${environment.apiUrl}/home/assigned`), ); } async fetchSubtasks(subtaskType: CARD_SUBTASK_TYPE) { return await firstValueFrom( - this.http.get<HomepageSubtaskDto[]>(`${environment.apiUrl}/home/subtask/${subtaskType}`) + this.http.get<HomepageSubtaskDto[]>(`${environment.apiUrl}/home/subtask/${subtaskType}`), ); } } diff --git a/alcs-frontend/src/app/services/planning-review/planning-referral.service.ts b/alcs-frontend/src/app/services/planning-review/planning-referral.service.ts new file mode 100644 index 0000000000..d2275120b8 --- /dev/null +++ b/alcs-frontend/src/app/services/planning-review/planning-referral.service.ts @@ -0,0 +1,33 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { firstValueFrom } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { ToastService } from '../toast/toast.service'; +import { + CreatePlanningReviewDto, + PlanningReferralDto, + PlanningReviewDto, + PlanningReviewTypeDto, +} from './planning-review.dto'; + +@Injectable({ + providedIn: 'root', +}) +export class PlanningReferralService { + private url = `${environment.apiUrl}/planning-referral`; + + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async fetchByCardUuid(id: string) { + try { + return await firstValueFrom(this.http.get<PlanningReferralDto>(`${this.url}/card/${id}`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch planning review'); + } + return; + } +} 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 ec601f1510..0176a6f063 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 @@ -1,19 +1,37 @@ +import { BaseCodeDto } from '../../shared/dto/base.dto'; import { ApplicationRegionDto } from '../application/application-code.dto'; import { ApplicationLocalGovernmentDto } from '../application/application-local-government/application-local-government.dto'; import { CardDto } from '../card/card.dto'; export interface CreatePlanningReviewDto { - fileNumber: string; - type: string; + description: string; + documentName: string; + submissionDate: number; + dueDate?: number; localGovernmentUuid: string; + typeCode: string; regionCode: string; - boardCode: string; } export interface PlanningReviewDto { fileNumber: string; - card: CardDto; + open: boolean; localGovernment: ApplicationLocalGovernmentDto; region: ApplicationRegionDto; - type: string; + type: PlanningReviewTypeDto; + documentName: string; +} + +export interface PlanningReviewTypeDto extends BaseCodeDto { + shortLabel: string; + backgroundColor: string; + textColor: string; +} + +export interface PlanningReferralDto { + referralDescription: string; + dueDate?: number; + submissionDate: number; + planningReview: PlanningReviewDto; + card: CardDto; } diff --git a/alcs-frontend/src/app/services/planning-review/planning-review.service.spec.ts b/alcs-frontend/src/app/services/planning-review/planning-review.service.spec.ts index a3bf674166..bc1f21e9ea 100644 --- a/alcs-frontend/src/app/services/planning-review/planning-review.service.spec.ts +++ b/alcs-frontend/src/app/services/planning-review/planning-review.service.spec.ts @@ -37,15 +37,16 @@ describe('PlanningReviewService', () => { httpClient.post.mockReturnValue( of({ fileNumber: '1', - }) + }), ); await service.create({ - fileNumber: '1', + description: '', + documentName: '', + submissionDate: 0, + typeCode: '', localGovernmentUuid: '', regionCode: '', - type: '', - boardCode: '', }); expect(httpClient.post).toHaveBeenCalledTimes(1); @@ -55,15 +56,16 @@ describe('PlanningReviewService', () => { httpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.create({ - fileNumber: '', + description: '', + documentName: '', + submissionDate: 0, + typeCode: '', localGovernmentUuid: '', regionCode: '', - type: '', - boardCode: '', }); expect(httpClient.post).toHaveBeenCalledTimes(1); @@ -75,7 +77,7 @@ describe('PlanningReviewService', () => { httpClient.get.mockReturnValue( of({ fileNumber: '1', - }) + }), ); const res = await service.fetchByCardUuid('1'); @@ -89,7 +91,7 @@ describe('PlanningReviewService', () => { httpClient.get.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); const res = await service.fetchByCardUuid('1'); 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 cbacb7936b..b2cc55019d 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 @@ -3,7 +3,12 @@ import { Injectable } from '@angular/core'; import { firstValueFrom } from 'rxjs'; import { environment } from '../../../environments/environment'; import { ToastService } from '../toast/toast.service'; -import { CreatePlanningReviewDto, PlanningReviewDto } from './planning-review.dto'; +import { + CreatePlanningReviewDto, + PlanningReferralDto, + PlanningReviewDto, + PlanningReviewTypeDto, +} from './planning-review.dto'; @Injectable({ providedIn: 'root', @@ -18,7 +23,7 @@ export class PlanningReviewService { async create(meeting: CreatePlanningReviewDto) { try { - const res = await firstValueFrom(this.http.post<PlanningReviewDto>(`${this.url}`, meeting)); + const res = await firstValueFrom(this.http.post<PlanningReferralDto>(`${this.url}`, meeting)); this.toastService.showSuccessToast('Planning meeting card created'); return res; } catch (err) { @@ -37,4 +42,14 @@ export class PlanningReviewService { } return; } + + async fetchTypes() { + try { + return await firstValueFrom(this.http.get<PlanningReviewTypeDto[]>(`${this.url}/types`)); + } catch (err) { + console.error(err); + this.toastService.showErrorToast('Failed to fetch planning review types'); + } + return; + } } diff --git a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts index c274ff5526..f376177820 100644 --- a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts +++ b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.spec.ts @@ -1,15 +1,15 @@ -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; -import { ApplicationService } from '../../application/application.service'; -import { CovenantService } from '../../covenant/covenant.service'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; import { ApplicationModificationService } from '../../application-decision/application-modification/application-modification.service'; import { ApplicationReconsiderationService } from '../../application-decision/application-reconsideration/application-reconsideration.service'; +import { ApplicationService } from '../../application/application.service'; +import { CovenantService } from '../../covenant/covenant.service'; import { NoticeOfIntentModificationService } from '../../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; import { NotificationService } from '../../notification/notification.service'; -import { PlanningReviewService } from '../../planning-review/planning-review.service'; +import { PlanningReferralService } from '../../planning-review/planning-referral/planning-referral.service'; import { UnarchiveCardService } from './unarchive-card.service'; describe('UnarchiveCardService', () => { @@ -17,7 +17,7 @@ describe('UnarchiveCardService', () => { let mockApplicationService: DeepMocked<ApplicationService>; let mockReconsiderationService: DeepMocked<ApplicationReconsiderationService>; - let mockPlanningReviewService: DeepMocked<PlanningReviewService>; + let mockPlanningReferralService: DeepMocked<PlanningReferralService>; let mockModificationService: DeepMocked<ApplicationModificationService>; let mockCovenantService: DeepMocked<CovenantService>; let mockNOIService: DeepMocked<NoticeOfIntentService>; @@ -27,7 +27,7 @@ describe('UnarchiveCardService', () => { beforeEach(async () => { mockApplicationService = createMock(); mockReconsiderationService = createMock(); - mockPlanningReviewService = createMock(); + mockPlanningReferralService = createMock(); mockModificationService = createMock(); mockCovenantService = createMock(); mockNOIService = createMock(); @@ -51,8 +51,8 @@ describe('UnarchiveCardService', () => { useValue: mockReconsiderationService, }, { - provide: PlanningReviewService, - useValue: mockPlanningReviewService, + provide: PlanningReferralService, + useValue: mockPlanningReferralService, }, { provide: ApplicationModificationService, @@ -87,18 +87,20 @@ describe('UnarchiveCardService', () => { it('should load from each service for fetch', async () => { mockApplicationService.getDeletedCard.mockResolvedValue(null); mockReconsiderationService.getDeletedCards.mockResolvedValue([]); - mockPlanningReviewService.getDeletedCards.mockResolvedValue([]); + mockPlanningReferralService.getDeletedCards.mockResolvedValue([]); mockModificationService.getDeletedCards.mockResolvedValue([]); mockCovenantService.getDeletedCards.mockResolvedValue([]); mockNOIService.getDeletedCards.mockResolvedValue([]); mockNOIModificationService.getDeletedCards.mockResolvedValue([]); mockNotificationService.getDeletedCards.mockResolvedValue([]); - const res = await service.fetchByFileId('uuid'); + await service.fetchByFileId('uuid'); expect(mockApplicationService.getDeletedCard).toHaveBeenCalledTimes(1); expect(mockReconsiderationService.getDeletedCards).toHaveBeenCalledTimes(1); - expect(mockPlanningReviewService.getDeletedCards).toHaveBeenCalledTimes(1); + expect(mockPlanningReferralService.getDeletedCards).toHaveBeenCalledTimes( + 1, + ); expect(mockModificationService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockCovenantService.getDeletedCards).toHaveBeenCalledTimes(1); expect(mockNOIService.getDeletedCards).toHaveBeenCalledTimes(1); diff --git a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts index 734c465f43..84f3a15589 100644 --- a/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts +++ b/services/apps/alcs/src/alcs/admin/unarchive-card/unarchive-card.service.ts @@ -6,19 +6,19 @@ import { CovenantService } from '../../covenant/covenant.service'; import { NoticeOfIntentModificationService } from '../../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentService } from '../../notice-of-intent/notice-of-intent.service'; import { NotificationService } from '../../notification/notification.service'; -import { PlanningReviewService } from '../../planning-review/planning-review.service'; +import { PlanningReferralService } from '../../planning-review/planning-referral/planning-referral.service'; @Injectable() export class UnarchiveCardService { constructor( private applicationService: ApplicationService, private reconsiderationService: ApplicationReconsiderationService, - private planningReviewService: PlanningReviewService, private modificationService: ApplicationModificationService, private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, private noticeOfIntentModificationService: NoticeOfIntentModificationService, private notificationService: NotificationService, + private planningReferralService: PlanningReferralService, ) {} async fetchByFileId(fileId: string) { @@ -39,7 +39,7 @@ export class UnarchiveCardService { } await this.fetchAndMapRecons(fileId, result); - await this.fetchAndMapPlanningReviews(fileId, result); + await this.fetchAndMapPlanningReferrals(fileId, result); await this.fetchAndMapModifications(fileId, result); await this.fetchAndMapCovenants(fileId, result); await this.fetchAndMapNOIs(fileId, result); @@ -89,27 +89,6 @@ export class UnarchiveCardService { } } - private async fetchAndMapPlanningReviews( - fileId: string, - result: { - cardUuid: string; - type: string; - status: string; - createdAt: number; - }[], - ) { - const planningReviews = - await this.planningReviewService.getDeletedCards(fileId); - for (const planningReview of planningReviews) { - result.push({ - cardUuid: planningReview.cardUuid, - createdAt: planningReview.auditCreatedAt.getTime(), - type: 'Planning Review', - status: planningReview.card!.status.label, - }); - } - } - private async fetchAndMapRecons( fileId: string, result: { @@ -184,4 +163,25 @@ export class UnarchiveCardService { }); } } + + private async fetchAndMapPlanningReferrals( + fileId: string, + result: { + cardUuid: string; + type: string; + status: string; + createdAt: number; + }[], + ) { + const notifications = + await this.planningReferralService.getDeletedCards(fileId); + for (const referral of notifications) { + result.push({ + cardUuid: referral.cardUuid, + createdAt: referral.auditCreatedAt.getTime(), + type: 'PLAN', + status: referral.card!.status.label, + }); + } + } } diff --git a/services/apps/alcs/src/alcs/board/board.controller.spec.ts b/services/apps/alcs/src/alcs/board/board.controller.spec.ts index d776352754..c302ca0b67 100644 --- a/services/apps/alcs/src/alcs/board/board.controller.spec.ts +++ b/services/apps/alcs/src/alcs/board/board.controller.spec.ts @@ -1,20 +1,21 @@ -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; import { ClsService } from 'nestjs-cls'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; import { BoardAutomapperProfile } from '../../common/automapper/board.automapper.profile'; import { ApplicationModificationService } from '../application-decision/application-modification/application-modification.service'; import { ApplicationReconsiderationService } from '../application-decision/application-reconsideration/application-reconsideration.service'; import { ApplicationService } from '../application/application.service'; -import { CardType, CARD_TYPE } from '../card/card-type/card-type.entity'; +import { CARD_TYPE, CardType } from '../card/card-type/card-type.entity'; import { Card } from '../card/card.entity'; import { CardService } from '../card/card.service'; import { CovenantService } from '../covenant/covenant.service'; import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service'; import { NotificationService } from '../notification/notification.service'; +import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; import { PlanningReviewService } from '../planning-review/planning-review.service'; import { BoardController } from './board.controller'; import { BOARD_CODES } from './board.dto'; @@ -29,7 +30,7 @@ describe('BoardController', () => { let appReconsiderationService: DeepMocked<ApplicationReconsiderationService>; let modificationService: DeepMocked<ApplicationModificationService>; let cardService: DeepMocked<CardService>; - let planningReviewService: DeepMocked<PlanningReviewService>; + let planningReferralService: DeepMocked<PlanningReferralService>; let covenantService: DeepMocked<CovenantService>; let noticeOfIntentService: DeepMocked<NoticeOfIntentService>; let noiModificationService: DeepMocked<NoticeOfIntentModificationService>; @@ -41,7 +42,7 @@ describe('BoardController', () => { appService = createMock(); appReconsiderationService = createMock(); modificationService = createMock(); - planningReviewService = createMock(); + planningReferralService = createMock(); cardService = createMock(); covenantService = createMock(); noticeOfIntentService = createMock(); @@ -60,8 +61,8 @@ describe('BoardController', () => { appService.mapToDtos.mockResolvedValue([]); appReconsiderationService.getByBoard.mockResolvedValue([]); appReconsiderationService.mapToDtos.mockResolvedValue([]); - planningReviewService.getByBoard.mockResolvedValue([]); - planningReviewService.mapToDtos.mockResolvedValue([]); + planningReferralService.getByBoard.mockResolvedValue([]); + planningReferralService.mapToDtos.mockResolvedValue([]); modificationService.getByBoard.mockResolvedValue([]); modificationService.mapToDtos.mockResolvedValue([]); covenantService.getByBoard.mockResolvedValue([]); @@ -92,8 +93,8 @@ describe('BoardController', () => { }, { provide: CardService, useValue: cardService }, { - provide: PlanningReviewService, - useValue: planningReviewService, + provide: PlanningReferralService, + useValue: planningReferralService, }, { provide: CovenantService, useValue: covenantService }, { @@ -148,8 +149,8 @@ describe('BoardController', () => { expect(appReconsiderationService.mapToDtos).toHaveBeenCalledTimes(1); expect(modificationService.getByBoard).toHaveBeenCalledTimes(0); expect(modificationService.mapToDtos).toHaveBeenCalledTimes(1); - expect(planningReviewService.getByBoard).toHaveBeenCalledTimes(0); - expect(planningReviewService.mapToDtos).toHaveBeenCalledTimes(1); + expect(planningReferralService.getByBoard).toHaveBeenCalledTimes(0); + expect(planningReferralService.mapToDtos).toHaveBeenCalledTimes(1); }); it('should call through to planning review service if board supports planning reviews', async () => { @@ -162,8 +163,8 @@ describe('BoardController', () => { await controller.getBoardWithCards(boardCode); - expect(planningReviewService.getByBoard).toHaveBeenCalledTimes(1); - expect(planningReviewService.mapToDtos).toHaveBeenCalledTimes(1); + expect(planningReferralService.getByBoard).toHaveBeenCalledTimes(1); + expect(planningReferralService.mapToDtos).toHaveBeenCalledTimes(1); }); it('should call through to modification service for boards that support it board', async () => { diff --git a/services/apps/alcs/src/alcs/board/board.controller.ts b/services/apps/alcs/src/alcs/board/board.controller.ts index b06ced459a..fe97ea2d39 100644 --- a/services/apps/alcs/src/alcs/board/board.controller.ts +++ b/services/apps/alcs/src/alcs/board/board.controller.ts @@ -20,6 +20,7 @@ import { CovenantService } from '../covenant/covenant.service'; import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/notice-of-intent-modification/notice-of-intent-modification.service'; import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service'; import { NotificationService } from '../notification/notification.service'; +import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service'; import { PlanningReviewService } from '../planning-review/planning-review.service'; import { BoardDto, MinimalBoardDto } from './board.dto'; import { Board } from './board.entity'; @@ -34,7 +35,7 @@ export class BoardController { private applicationService: ApplicationService, private cardService: CardService, private reconsiderationService: ApplicationReconsiderationService, - private planningReviewService: PlanningReviewService, + private planningReferralService: PlanningReferralService, private appModificationService: ApplicationModificationService, private noiModificationService: NoticeOfIntentModificationService, private covenantService: CovenantService, @@ -89,8 +90,8 @@ export class BoardController { ? await this.noticeOfIntentService.getByBoard(board.uuid) : []; - const planningReviews = allowedCodes.includes(CARD_TYPE.PLAN) - ? await this.planningReviewService.getByBoard(board.uuid) + const planningReferrals = allowedCodes.includes(CARD_TYPE.PLAN) + ? await this.planningReferralService.getByBoard(board.uuid) : []; const noiModifications = allowedCodes.includes(CARD_TYPE.NOI_MODI) @@ -105,8 +106,8 @@ export class BoardController { board: await this.autoMapper.mapAsync(board, Board, BoardDto), applications: await this.applicationService.mapToDtos(applications), reconsiderations: await this.reconsiderationService.mapToDtos(recons), - planningReviews: - await this.planningReviewService.mapToDtos(planningReviews), + planningReferrals: + await this.planningReferralService.mapToDtos(planningReferrals), modifications: await this.appModificationService.mapToDtos(modifications), covenants: await this.covenantService.mapToDtos(covenants), noticeOfIntents: diff --git a/services/apps/alcs/src/alcs/board/board.dto.ts b/services/apps/alcs/src/alcs/board/board.dto.ts index 422a13ce8e..4a281bc64e 100644 --- a/services/apps/alcs/src/alcs/board/board.dto.ts +++ b/services/apps/alcs/src/alcs/board/board.dto.ts @@ -5,6 +5,7 @@ export enum BOARD_CODES { CEO = 'ceo', SOIL = 'soil', EXECUTIVE_COMMITTEE = 'exec', + REGIONAL_PLANNING = 'rppp', } export class MinimalBoardDto { diff --git a/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts b/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts index 78330ac6aa..84ccd30d68 100644 --- a/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts +++ b/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts @@ -13,7 +13,9 @@ export enum CARD_TYPE { NOTIFICATION = 'NOTI', } -@Entity() +@Entity({ + comment: '', +}) export class CardType extends BaseCodeEntity { constructor(data?: Partial<CardType>) { super(); diff --git a/services/apps/alcs/src/alcs/home/home.controller.spec.ts b/services/apps/alcs/src/alcs/home/home.controller.spec.ts index d11864332a..478b071bbf 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.spec.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.spec.ts @@ -1,7 +1,7 @@ -import { classes } from 'automapper-classes'; -import { AutomapperModule } from 'automapper-nestjs'; import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; import { ClsService } from 'nestjs-cls'; import { In, Not } from 'typeorm'; import { @@ -33,8 +33,6 @@ import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service'; import { Notification } from '../notification/notification.entity'; import { NotificationService } from '../notification/notification.service'; -import { PlanningReview } from '../planning-review/planning-review.entity'; -import { PlanningReviewService } from '../planning-review/planning-review.service'; import { HomeController } from './home.controller'; describe('HomeController', () => { @@ -43,7 +41,6 @@ describe('HomeController', () => { let mockApplicationSubtaskService: DeepMocked<CardSubtaskService>; let mockApplicationReconsiderationService: DeepMocked<ApplicationReconsiderationService>; let mockApplicationModificationService: DeepMocked<ApplicationModificationService>; - let mockPlanningReviewService: DeepMocked<PlanningReviewService>; let mockCovenantService: DeepMocked<CovenantService>; let mockApplicationTimeTrackingService: DeepMocked<ApplicationTimeTrackingService>; let mockNoticeOfIntentService: DeepMocked<NoticeOfIntentService>; @@ -54,7 +51,6 @@ describe('HomeController', () => { mockApplicationService = createMock(); mockApplicationSubtaskService = createMock(); mockApplicationReconsiderationService = createMock(); - mockPlanningReviewService = createMock(); mockApplicationTimeTrackingService = createMock(); mockApplicationModificationService = createMock(); mockCovenantService = createMock(); @@ -98,10 +94,6 @@ describe('HomeController', () => { provide: ApplicationTimeTrackingService, useValue: mockApplicationTimeTrackingService, }, - { - provide: PlanningReviewService, - useValue: mockPlanningReviewService, - }, { provide: CovenantService, useValue: mockCovenantService, @@ -136,8 +128,6 @@ describe('HomeController', () => { mockApplicationReconsiderationService.mapToDtos.mockResolvedValue([]); mockApplicationModificationService.getBy.mockResolvedValue([]); mockApplicationModificationService.mapToDtos.mockResolvedValue([]); - mockPlanningReviewService.getBy.mockResolvedValue([]); - mockPlanningReviewService.mapToDtos.mockResolvedValue([]); mockCovenantService.getBy.mockResolvedValue([]); mockCovenantService.mapToDtos.mockResolvedValue([]); mockNoticeOfIntentService.getBy.mockResolvedValue([]); @@ -158,9 +148,6 @@ describe('HomeController', () => { mockApplicationReconsiderationService.getWithIncompleteSubtaskByType.mockResolvedValue( [], ); - mockPlanningReviewService.getWithIncompleteSubtaskByType.mockResolvedValue( - [], - ); mockApplicationModificationService.getWithIncompleteSubtaskByType.mockResolvedValue( [], ); @@ -213,11 +200,6 @@ describe('HomeController', () => { mockApplicationReconsiderationService.getBy.mock.calls[0][0], ).toEqual(filterCondition); - expect(mockPlanningReviewService.getBy).toHaveBeenCalledTimes(1); - expect(mockPlanningReviewService.getBy.mock.calls[0][0]).toEqual( - filterCondition, - ); - expect(mockNoticeOfIntentService.getBy).toHaveBeenCalledTimes(1); expect(mockNoticeOfIntentService.getBy.mock.calls[0][0]).toEqual( filterCondition, @@ -295,30 +277,31 @@ describe('HomeController', () => { expect(res[0].paused).toBeFalsy(); }); - it('should call Reconsideration Service and map it', async () => { - const mockPlanningReview = { - type: 'fake-type', - fileNumber: 'fileNumber', - card: initCardMockEntity('222'), - } as PlanningReview; - mockPlanningReviewService.getWithIncompleteSubtaskByType.mockResolvedValue( - [mockPlanningReview], - ); - - const res = await controller.getIncompleteSubtasksByType( - CARD_SUBTASK_TYPE.GIS, - ); - - expect(res.length).toEqual(1); - expect( - mockPlanningReviewService.getWithIncompleteSubtaskByType, - ).toHaveBeenCalledTimes(1); - - expect(res[0].title).toContain(mockPlanningReview.fileNumber); - expect(res[0].title).toContain(mockPlanningReview.type); - expect(res[0].activeDays).toBeUndefined(); - expect(res[0].paused).toBeFalsy(); - }); + // TODO: Fix when finishing planning reviews + // it('should call Planning Referral Service and map it', async () => { + // const mockPlanningReview = { + // type: 'fake-type', + // fileNumber: 'fileNumber', + // card: initCardMockEntity('222'), + // } as PlanningReview; + // mockPlanningReviewService.getWithIncompleteSubtaskByType.mockResolvedValue( + // [mockPlanningReview], + // ); + // + // const res = await controller.getIncompleteSubtasksByType( + // CARD_SUBTASK_TYPE.GIS, + // ); + // + // expect(res.length).toEqual(1); + // expect( + // mockPlanningReviewService.getWithIncompleteSubtaskByType, + // ).toHaveBeenCalledTimes(1); + // + // expect(res[0].title).toContain(mockPlanningReview.fileNumber); + // expect(res[0].title).toContain(mockPlanningReview.type); + // expect(res[0].activeDays).toBeUndefined(); + // expect(res[0].paused).toBeFalsy(); + // }); it('should call Modification Service and map it', async () => { const mockModification = initApplicationModificationMockEntity(); @@ -332,7 +315,7 @@ describe('HomeController', () => { expect(res.length).toEqual(1); expect( - mockPlanningReviewService.getWithIncompleteSubtaskByType, + mockApplicationModificationService.getWithIncompleteSubtaskByType, ).toHaveBeenCalledTimes(1); expect(res[0].title).toContain(mockModification.application.fileNumber); @@ -357,7 +340,7 @@ describe('HomeController', () => { expect(res.length).toEqual(1); expect( - mockPlanningReviewService.getWithIncompleteSubtaskByType, + mockCovenantService.getWithIncompleteSubtaskByType, ).toHaveBeenCalledTimes(1); expect(res[0].title).toContain(mockCovenant.fileNumber); diff --git a/services/apps/alcs/src/alcs/home/home.controller.ts b/services/apps/alcs/src/alcs/home/home.controller.ts index 3fe9faac21..1de8cee4de 100644 --- a/services/apps/alcs/src/alcs/home/home.controller.ts +++ b/services/apps/alcs/src/alcs/home/home.controller.ts @@ -1,7 +1,7 @@ -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; import * as config from 'config'; import { In, Not } from 'typeorm'; import { ANY_AUTH_ROLE } from '../../common/authorization/roles'; @@ -36,11 +36,11 @@ import { NoticeOfIntentModificationService } from '../notice-of-intent-decision/ import { NoticeOfIntentDto } from '../notice-of-intent/notice-of-intent.dto'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { NoticeOfIntentService } from '../notice-of-intent/notice-of-intent.service'; +import { NotificationDto } from '../notification/notification.dto'; import { Notification } from '../notification/notification.entity'; import { NotificationService } from '../notification/notification.service'; import { PlanningReviewDto } from '../planning-review/planning-review.dto'; import { PlanningReview } from '../planning-review/planning-review.entity'; -import { PlanningReviewService } from '../planning-review/planning-review.service'; const HIDDEN_CARD_STATUSES = [ CARD_STATUS.CANCELLED, @@ -56,7 +56,6 @@ export class HomeController { private applicationService: ApplicationService, private timeService: ApplicationTimeTrackingService, private reconsiderationService: ApplicationReconsiderationService, - private planningReviewService: PlanningReviewService, private modificationService: ApplicationModificationService, private covenantService: CovenantService, private noticeOfIntentService: NoticeOfIntentService, @@ -71,9 +70,10 @@ export class HomeController { noticeOfIntentModifications: NoticeOfIntentModificationDto[]; applications: ApplicationDto[]; reconsiderations: ApplicationReconsiderationDto[]; - planningReviews: PlanningReviewDto[]; + planningReferrals: PlanningReviewDto[]; modifications: ApplicationModificationDto[]; covenants: CovenantDto[]; + notifications: NotificationDto[]; }> { const userId = req.user.entity.uuid; const assignedFindOptions = { @@ -91,8 +91,8 @@ export class HomeController { const reconsiderations = await this.reconsiderationService.getBy(assignedFindOptions); - const planningReviews = - await this.planningReviewService.getBy(assignedFindOptions); + // const planningReviews = + // await this.planningReviewService.getBy(assignedFindOptions); const modifications = await this.modificationService.getBy(assignedFindOptions); @@ -108,7 +108,7 @@ export class HomeController { const notifications = await this.notificationService.getBy(assignedFindOptions); - const result = { + return { noticeOfIntents: await this.noticeOfIntentService.mapToDtos(noticeOfIntents), noticeOfIntentModifications: @@ -118,23 +118,21 @@ export class HomeController { applications: await this.applicationService.mapToDtos(applications), reconsiderations: await this.reconsiderationService.mapToDtos(reconsiderations), - planningReviews: - await this.planningReviewService.mapToDtos(planningReviews), + planningReferrals: [], modifications: await this.modificationService.mapToDtos(modifications), covenants: await this.covenantService.mapToDtos(covenants), notifications: await this.notificationService.mapToDtos(notifications), }; - - return result; } else { return { noticeOfIntents: [], noticeOfIntentModifications: [], applications: [], reconsiderations: [], - planningReviews: [], + planningReferrals: [], modifications: [], covenants: [], + notifications: [], }; } } @@ -156,13 +154,13 @@ export class HomeController { ); const reconSubtasks = this.mapReconToDto(reconsiderationWithSubtasks); - const planningReviewsWithSubtasks = - await this.planningReviewService.getWithIncompleteSubtaskByType( - subtaskType, - ); - const planningReviewSubtasks = this.mapPlanningReviewsToDtos( - planningReviewsWithSubtasks, - ); + // const planningReviewsWithSubtasks = + // await this.planningReviewService.getWithIncompleteSubtaskByType( + // subtaskType, + // ); + // const planningReviewSubtasks = this.mapPlanningReviewsToDtos( + // planningReviewsWithSubtasks, + // ); const modificationsWithSubtasks = await this.modificationService.getWithIncompleteSubtaskByType( @@ -205,7 +203,6 @@ export class HomeController { ...applicationSubtasks, ...reconSubtasks, ...modificationSubtasks, - ...planningReviewSubtasks, ...covenantReviewSubtasks, ...noiModificationsSubtasks, ...notificationSubtasks, @@ -270,21 +267,22 @@ export class HomeController { private mapPlanningReviewsToDtos(planingReviews: PlanningReview[]) { const result: HomepageSubtaskDTO[] = []; - for (const planningReview of planingReviews) { - for (const subtask of planningReview.card.subtasks) { - result.push({ - type: subtask.type, - createdAt: subtask.createdAt.getTime(), - assignee: this.mapper.map(subtask.assignee, User, AssigneeDto), - uuid: subtask.uuid, - card: this.mapper.map(planningReview.card, Card, CardDto), - completedAt: subtask.completedAt?.getTime(), - paused: false, - title: `${planningReview.fileNumber} (${planningReview.type})`, - parentType: PARENT_TYPE.PLANNING_REVIEW, - }); - } - } + // TODO + // for (const planningReview of planingReviews) { + // for (const subtask of planningReview.card.subtasks) { + // result.push({ + // type: subtask.type, + // createdAt: subtask.createdAt.getTime(), + // assignee: this.mapper.map(subtask.assignee, User, AssigneeDto), + // uuid: subtask.uuid, + // card: this.mapper.map(planningReview.card, Card, CardDto), + // completedAt: subtask.completedAt?.getTime(), + // paused: false, + // title: `${planningReview.fileNumber} (${planningReview.type})`, + // parentType: PARENT_TYPE.PLANNING_REVIEW, + // }); + // } + // } return result; } diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts new file mode 100644 index 0000000000..9665d5cbc0 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.spec.ts @@ -0,0 +1,58 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +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 { PlanningReviewType } from '../planning-review-type.entity'; +import { PlanningReferralController } from './planning-referral.controller'; +import { PlanningReferral } from './planning-referral.entity'; +import { PlanningReferralService } from './planning-referral.service'; + +describe('PlanningReviewController', () => { + let controller: PlanningReferralController; + let mockService: DeepMocked<PlanningReferralService>; + + beforeEach(async () => { + mockService = createMock(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + controllers: [PlanningReferralController], + providers: [ + PlanningReviewProfile, + { + provide: PlanningReferralService, + useValue: mockService, + }, + { + provide: ClsService, + useValue: {}, + }, + ...mockKeyCloakProviders, + ], + }).compile(); + + controller = module.get<PlanningReferralController>( + PlanningReferralController, + ); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should call through for fetchByCardUuid', async () => { + mockService.getByCardUuid.mockResolvedValue(new PlanningReferral()); + + const res = await controller.fetchByCardUuid('uuid'); + + expect(res).toBeDefined(); + expect(mockService.getByCardUuid).toHaveBeenCalledTimes(1); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts new file mode 100644 index 0000000000..a4349e5f31 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiOAuth2 } from '@nestjs/swagger'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; +import * as config from 'config'; +import { ROLES_ALLOWED_BOARDS } from '../../../common/authorization/roles'; +import { RolesGuard } from '../../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../../common/authorization/roles.decorator'; +import { PlanningReferralDto } from '../planning-review.dto'; +import { PlanningReferral } from './planning-referral.entity'; +import { PlanningReferralService } from './planning-referral.service'; + +@Controller('planning-referral') +@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES')) +@UseGuards(RolesGuard) +export class PlanningReferralController { + constructor( + private planningReferralService: PlanningReferralService, + @InjectMapper() + private mapper: Mapper, + ) {} + + @Get('/card/:uuid') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async fetchByCardUuid(@Param('uuuid') uuid: string) { + const review = await this.planningReferralService.getByCardUuid(uuid); + return this.mapper.map(review, PlanningReferral, PlanningReferralDto); + } +} 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 new file mode 100644 index 0000000000..27c603e937 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.entity.ts @@ -0,0 +1,49 @@ +import { AutoMap } from 'automapper-classes'; +import { Type } from 'class-transformer'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { Base } from '../../../common/entities/base.entity'; +import { Card } from '../../card/card.entity'; +import { PlanningReview } from '../planning-review.entity'; + +@Entity() +export class PlanningReferral extends Base { + constructor(data?: Partial<PlanningReferral>) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @Column({ type: 'timestamptz' }) + submissionDate: Date; + + @AutoMap() + @Column({ type: 'timestamptz', nullable: true }) + dueDate?: Date | null; + + @AutoMap() + @Column({ type: 'timestamptz', nullable: true }) + responseDate?: Date | null; + + @AutoMap() + @Column({ nullable: true, type: 'text' }) + referralDescription?: string | null; + + @AutoMap() + @Column({ nullable: true, type: 'text' }) + responseDescription?: string; + + @ManyToOne(() => PlanningReview) + @JoinColumn() + @Type(() => PlanningReview) + planningReview: PlanningReview; + + @Column({ type: 'uuid' }) + cardUuid: string; + + @OneToOne(() => Card, { cascade: true }) + @JoinColumn() + @Type(() => Card) + card: Card; +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts new file mode 100644 index 0000000000..3b6f292152 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.spec.ts @@ -0,0 +1,69 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; +import { Repository } from 'typeorm'; +import { CardService } from '../../card/card.service'; +import { PlanningReferral } from './planning-referral.entity'; +import { PlanningReferralService } from './planning-referral.service'; + +describe('PlanningReferralService', () => { + let service: PlanningReferralService; + let mockRepository: DeepMocked<Repository<PlanningReferral>>; + let mockCardService: DeepMocked<CardService>; + + beforeEach(async () => { + mockCardService = createMock<CardService>(); + mockRepository = createMock<Repository<PlanningReferral>>(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], + providers: [ + { + provide: getRepositoryToken(PlanningReferral), + useValue: mockRepository, + }, + { + provide: CardService, + useValue: mockCardService, + }, + PlanningReferralService, + ], + }).compile(); + + service = module.get<PlanningReferralService>(PlanningReferralService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should call through to the repo for get by card', async () => { + mockRepository.findOneOrFail.mockResolvedValue(new PlanningReferral()); + const cardUuid = 'fake-card-uuid'; + await service.getByCardUuid(cardUuid); + + expect(mockRepository.findOneOrFail).toHaveBeenCalledTimes(1); + }); + + it('should call through to the repo for get cards', async () => { + mockRepository.find.mockResolvedValue([]); + await service.getByBoard(''); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + }); + + it('should load deleted cards', async () => { + mockRepository.find.mockResolvedValue([]); + + await service.getDeletedCards('file-number'); + + expect(mockRepository.find).toHaveBeenCalledTimes(1); + expect(mockRepository.find.mock.calls[0][0]!.withDeleted).toEqual(true); + }); +}); diff --git a/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts new file mode 100644 index 0000000000..6bda8ac10d --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-referral/planning-referral.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; +import { FindOptionsRelations, IsNull, Not, Repository } from 'typeorm'; +import { PlanningReferralDto } from '../planning-review.dto'; +import { PlanningReferral } from './planning-referral.entity'; + +@Injectable() +export class PlanningReferralService { + constructor( + @InjectRepository(PlanningReferral) + private referralRepository: Repository<PlanningReferral>, + @InjectMapper() + private mapper: Mapper, + ) {} + + private DEFAULT_RELATIONS: FindOptionsRelations<PlanningReferral> = { + card: { + type: true, + status: true, + board: true, + }, + planningReview: { + localGovernment: true, + region: true, + type: true, + }, + }; + + async getByBoard(boardUuid: string) { + return this.referralRepository.find({ + where: { + card: { + boardUuid, + }, + }, + relations: this.DEFAULT_RELATIONS, + }); + } + + async mapToDtos(planningReferrals: PlanningReferral[]) { + return this.mapper.mapArray( + planningReferrals, + PlanningReferral, + PlanningReferralDto, + ); + } + + get(uuid: string) { + return this.referralRepository.findOneOrFail({ + where: { + uuid, + }, + relations: this.DEFAULT_RELATIONS, + }); + } + + async getByCardUuid(uuid: string) { + return this.referralRepository.findOneOrFail({ + where: { + cardUuid: uuid, + }, + relations: this.DEFAULT_RELATIONS, + }); + } + + getDeletedCards(fileNumber: string) { + return this.referralRepository.find({ + where: { + planningReview: { + fileNumber: fileNumber, + }, + card: { + auditDeletedDateAt: Not(IsNull()), + }, + }, + withDeleted: true, + relations: this.DEFAULT_RELATIONS, + }); + } +} diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts b/services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts new file mode 100644 index 0000000000..471a88d482 --- /dev/null +++ b/services/apps/alcs/src/alcs/planning-review/planning-review-type.entity.ts @@ -0,0 +1,29 @@ +import { AutoMap } from 'automapper-classes'; +import { Column, Entity } from 'typeorm'; +import { BaseCodeEntity } from '../../common/entities/base.code.entity'; + +@Entity() +export class PlanningReviewType extends BaseCodeEntity { + constructor(data?: Partial<PlanningReviewType>) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @Column() + shortLabel: string; + + @AutoMap() + @Column() + backgroundColor: string; + + @AutoMap() + @Column() + textColor: string; + + @AutoMap() + @Column({ type: 'text', default: '' }) + htmlDescription: string; +} 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 64126026ec..efdbf2ee18 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 @@ -1,29 +1,44 @@ import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; +import { classes } from 'automapper-classes'; +import { AutomapperModule } from 'automapper-nestjs'; import { ClsService } from 'nestjs-cls'; +import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; +import { FileNumberService } from '../../file-number/file-number.service'; import { Board } from '../board/board.entity'; import { BoardService } from '../board/board.service'; -import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; +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', () => { let controller: PlanningReviewController; let mockService: DeepMocked<PlanningReviewService>; + let mockPlanningReferralService: DeepMocked<PlanningReferralService>; let mockBoardService: DeepMocked<BoardService>; beforeEach(async () => { - mockService = createMock<PlanningReviewService>(); - mockBoardService = createMock<BoardService>(); + mockService = createMock(); + mockBoardService = createMock(); + mockPlanningReferralService = createMock(); const module: TestingModule = await Test.createTestingModule({ + imports: [ + AutomapperModule.forRoot({ + strategyInitializer: classes(), + }), + ], controllers: [PlanningReviewController], providers: [ { provide: PlanningReviewService, useValue: mockService, }, + { + provide: PlanningReferralService, + useValue: mockPlanningReferralService, + }, { provide: BoardService, useValue: mockBoardService, @@ -45,29 +60,22 @@ describe('PlanningReviewController', () => { it('should call board service then main service for create', async () => { mockBoardService.getOneOrFail.mockResolvedValue({} as Board); - mockService.create.mockResolvedValue({} as PlanningReview); - mockService.mapToDtos.mockResolvedValue([]); + mockService.create.mockResolvedValue(new PlanningReferral()); + mockPlanningReferralService.get.mockResolvedValue(new PlanningReferral()); + mockPlanningReferralService.mapToDtos.mockResolvedValue([]); await controller.create({ - type: 'type', + description: 'description', + documentName: 'documentName', + submissionDate: 0, + typeCode: 'typeCode', localGovernmentUuid: 'local-gov-uuid', - fileNumber: 'file-number', regionCode: 'region-code', - boardCode: 'board-code', }); expect(mockBoardService.getOneOrFail).toHaveBeenCalledTimes(1); expect(mockService.create).toHaveBeenCalledTimes(1); - expect(mockService.mapToDtos).toHaveBeenCalledTimes(1); - }); - - it('should call through to service for get card', async () => { - mockService.getByCardUuid.mockResolvedValue({} as PlanningReview); - mockService.mapToDtos.mockResolvedValue([]); - - await controller.getByCard('uuid'); - - expect(mockService.getByCardUuid).toHaveBeenCalledTimes(1); - expect(mockService.mapToDtos).toHaveBeenCalledTimes(1); + expect(mockPlanningReferralService.get).toHaveBeenCalledTimes(1); + expect(mockPlanningReferralService.mapToDtos).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 a1e7879159..793b1b879e 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,11 +1,19 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; +import { Mapper } from 'automapper-core'; +import { InjectMapper } from 'automapper-nestjs'; import * as config from 'config'; -import { BoardService } from '../board/board.service'; import { ROLES_ALLOWED_BOARDS } from '../../common/authorization/roles'; import { RolesGuard } from '../../common/authorization/roles-guard.service'; import { UserRoles } from '../../common/authorization/roles.decorator'; -import { CreatePlanningReviewDto } from './planning-review.dto'; +import { BOARD_CODES } from '../board/board.dto'; +import { BoardService } from '../board/board.service'; +import { PlanningReferralService } from './planning-referral/planning-referral.service'; +import { PlanningReviewType } from './planning-review-type.entity'; +import { + CreatePlanningReviewDto, + PlanningReviewTypeDto, +} from './planning-review.dto'; import { PlanningReviewService } from './planning-review.service'; @Controller('planning-review') @@ -14,35 +22,41 @@ import { PlanningReviewService } from './planning-review.service'; export class PlanningReviewController { constructor( private planningReviewService: PlanningReviewService, + private planningReferralService: PlanningReferralService, private boardService: BoardService, + @InjectMapper() + private mapper: Mapper, ) {} + @Get('/types') + @UserRoles(...ROLES_ALLOWED_BOARDS) + async fetchTypes() { + const types = await this.planningReviewService.listTypes(); + + return this.mapper.mapArray( + types, + PlanningReviewType, + PlanningReviewTypeDto, + ); + } + @Post() @UserRoles(...ROLES_ALLOWED_BOARDS) async create(@Body() createDto: CreatePlanningReviewDto) { const board = await this.boardService.getOneOrFail({ - code: createDto.boardCode, + code: BOARD_CODES.REGIONAL_PLANNING, }); - if (!board) { - throw new Error('Failed to load executive board'); - } - - const createdReview = await this.planningReviewService.create( + const createdReferral = await this.planningReviewService.create( createDto, board, ); - const mapped = await this.planningReviewService.mapToDtos([createdReview]); - return mapped[0]; - } + const referral = await this.planningReferralService.get( + createdReferral.uuid, + ); - @Get('/card/:uuid') - @UserRoles(...ROLES_ALLOWED_BOARDS) - async getByCard(@Param('uuid') cardUuid: string) { - const planningReview = - await this.planningReviewService.getByCardUuid(cardUuid); - const mapped = await this.planningReviewService.mapToDtos([planningReview]); + const mapped = await this.planningReferralService.mapToDtos([referral]); return mapped[0]; } } 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 1bd1e97c8f..2dd04a6eae 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 @@ -1,18 +1,37 @@ import { AutoMap } from 'automapper-classes'; -import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; -import { LocalGovernmentDto } from '../local-government/local-government.dto'; +import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { BaseCodeDto } from '../../common/dtos/base.dto'; import { CardDto } from '../card/card.dto'; import { ApplicationRegionDto } from '../code/application-code/application-region/application-region.dto'; +import { LocalGovernmentDto } from '../local-government/local-government.dto'; + +export class PlanningReviewTypeDto extends BaseCodeDto { + @AutoMap() + shortLabel: string; + + @AutoMap() + backgroundColor: string; + + @AutoMap() + textColor: string; +} export class CreatePlanningReviewDto { @IsString() @IsNotEmpty() - fileNumber: string; + description: string; @IsString() @IsNotEmpty() - @MaxLength(40) - type: string; + documentName: string; + + @IsNumber() + @IsNotEmpty() + submissionDate: number; + + @IsNumber() + @IsOptional() + dueDate?: number; @IsString() @IsNotEmpty() @@ -20,11 +39,11 @@ export class CreatePlanningReviewDto { @IsString() @IsNotEmpty() - regionCode: string; + typeCode: string; @IsString() @IsNotEmpty() - boardCode: string; + regionCode: string; } export class PlanningReviewDto { @@ -32,14 +51,40 @@ export class PlanningReviewDto { fileNumber: string; @AutoMap() - card: CardDto; + open: boolean; @AutoMap() - localGovernment: LocalGovernmentDto; + documentName: string; + + @AutoMap() + localGovernmentUuid: string; @AutoMap() + typeCode: string; + + @AutoMap() + regionCode: string; + + @AutoMap(() => LocalGovernmentDto) + localGovernment: LocalGovernmentDto; + + @AutoMap(() => ApplicationRegionDto) region: ApplicationRegionDto; + @AutoMap(() => PlanningReviewTypeDto) + type: PlanningReviewTypeDto; +} + +export class PlanningReferralDto { + dueDate: number; + submissionDate: number; + @AutoMap() - type: string; + referralDescription: string; + + @AutoMap(() => PlanningReviewDto) + planningReview: PlanningReviewDto; + + @AutoMap(() => CardDto) + card: CardDto; } 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 908e4f9d3e..82c76d9756 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,16 +1,9 @@ -import { Type } from 'class-transformer'; -import { - Column, - Entity, - Index, - JoinColumn, - ManyToOne, - OneToOne, -} from 'typeorm'; +import { Column, Entity, Index, ManyToOne } from 'typeorm'; import { Base } from '../../common/entities/base.entity'; -import { Card } from '../card/card.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 { PlanningReviewType } from './planning-review-type.entity'; @Entity() export class PlanningReview extends Base { @@ -21,20 +14,11 @@ export class PlanningReview extends Base { } } - @Index() @Column({ unique: true }) fileNumber: string; - @Column() - type: string; - - @Column({ type: 'uuid' }) - cardUuid: string; - - @OneToOne(() => Card, { cascade: true }) - @JoinColumn() - @Type(() => Card) - card: Card; + @Column({ nullable: false }) + documentName: string; @ManyToOne(() => LocalGovernment) localGovernment: LocalGovernment; @@ -50,4 +34,19 @@ export class PlanningReview extends Base { @Column() regionCode: string; + + @ManyToOne(() => PlanningReviewType, { nullable: false }) + type: PlanningReviewType; + + @Column() + typeCode: string; + + @Column({ default: true }) + open: boolean; + + @ManyToOne(() => User) + closedBy: User; + + @Column({ type: 'timestamptz', nullable: true }) + closedDate: Date | null; } diff --git a/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts b/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts index d54f7dbc68..0e9ee0b166 100644 --- a/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts +++ b/services/apps/alcs/src/alcs/planning-review/planning-review.module.ts @@ -1,22 +1,36 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { PlanningReviewProfile } from '../../common/automapper/planning-review.automapper.profile'; +import { FileNumberModule } from '../../file-number/file-number.module'; import { BoardModule } from '../board/board.module'; import { CardModule } from '../card/card.module'; import { CodeModule } from '../code/code.module'; -import { PlanningReviewProfile } from '../../common/automapper/planning-meeting.automapper.profile'; +import { PlanningReferralController } from './planning-referral/planning-referral.controller'; +import { PlanningReferral } from './planning-referral/planning-referral.entity'; +import { PlanningReferralService } from './planning-referral/planning-referral.service'; +import { PlanningReviewType } from './planning-review-type.entity'; import { PlanningReviewController } from './planning-review.controller'; import { PlanningReview } from './planning-review.entity'; import { PlanningReviewService } from './planning-review.service'; @Module({ imports: [ - TypeOrmModule.forFeature([PlanningReview]), + TypeOrmModule.forFeature([ + PlanningReview, + PlanningReferral, + PlanningReviewType, + ]), forwardRef(() => BoardModule), CardModule, CodeModule, + FileNumberModule, ], - controllers: [PlanningReviewController], - providers: [PlanningReviewService, PlanningReviewProfile], - exports: [PlanningReviewService], + controllers: [PlanningReviewController, PlanningReferralController], + providers: [ + PlanningReviewService, + PlanningReviewProfile, + PlanningReferralService, + ], + exports: [PlanningReviewService, PlanningReferralService], }) export class PlanningReviewModule {} 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 6e943f743e..eff498dfd6 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 @@ -4,20 +4,29 @@ import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import { FileNumberService } from '../../file-number/file-number.service'; import { Board } from '../board/board.entity'; import { Card } from '../card/card.entity'; import { CardService } from '../card/card.service'; +import { PlanningReferral } from './planning-referral/planning-referral.entity'; +import { PlanningReviewType } from './planning-review-type.entity'; import { PlanningReview } from './planning-review.entity'; import { PlanningReviewService } from './planning-review.service'; describe('PlanningReviewService', () => { let service: PlanningReviewService; let mockRepository: DeepMocked<Repository<PlanningReview>>; + let mockTypeRepository: DeepMocked<Repository<PlanningReviewType>>; + let mockReferralRepository: DeepMocked<Repository<PlanningReferral>>; let mockCardService: DeepMocked<CardService>; + let mockFileNumberService: DeepMocked<FileNumberService>; beforeEach(async () => { - mockCardService = createMock<CardService>(); - mockRepository = createMock<Repository<PlanningReview>>(); + mockCardService = createMock(); + mockRepository = createMock(); + mockTypeRepository = createMock(); + mockReferralRepository = createMock(); + mockFileNumberService = createMock(); const module: TestingModule = await Test.createTestingModule({ imports: [ @@ -30,6 +39,18 @@ describe('PlanningReviewService', () => { provide: getRepositoryToken(PlanningReview), useValue: mockRepository, }, + { + provide: getRepositoryToken(PlanningReviewType), + useValue: mockTypeRepository, + }, + { + provide: getRepositoryToken(PlanningReferral), + useValue: mockReferralRepository, + }, + { + provide: FileNumberService, + useValue: mockFileNumberService, + }, { provide: CardService, useValue: mockCardService, @@ -49,84 +70,33 @@ describe('PlanningReviewService', () => { const mockCard = {} as Card; const fakeBoard = {} as Board; - mockRepository.findOne.mockResolvedValueOnce(null); - mockRepository.findOne.mockResolvedValueOnce({} as PlanningReview); - mockRepository.save.mockResolvedValue({} as PlanningReview); - mockCardService.create.mockResolvedValue(mockCard); - - const res = await service.create( - { - type: 'fake-type', - fileNumber: '1512311', - localGovernmentUuid: 'fake-uuid', - regionCode: 'region-code', - boardCode: 'board-code', - }, - fakeBoard, + mockFileNumberService.generateNextFileNumber.mockResolvedValue(1); + mockTypeRepository.findOneOrFail.mockResolvedValue( + new PlanningReviewType(), ); - - expect(mockRepository.findOne).toHaveBeenCalledTimes(2); - expect(mockCardService.create).toHaveBeenCalledTimes(1); - expect(mockRepository.save).toHaveBeenCalledTimes(1); - expect(mockRepository.save.mock.calls[0][0].card).toBe(mockCard); - }); - - it('should throw an exception when creating a meeting with an existing file ID', async () => { - const mockCard = {} as Card; - const fakeBoard = {} as Board; - const existingFileNumber = '1512311'; - - mockRepository.findOne.mockResolvedValueOnce({} as PlanningReview); mockRepository.save.mockResolvedValue({} as PlanningReview); mockCardService.create.mockResolvedValue(mockCard); + mockReferralRepository.save.mockResolvedValue(new PlanningReferral()); - const promise = service.create( + await service.create( { - type: 'fake-type', - fileNumber: existingFileNumber, + description: '', + documentName: '', + submissionDate: 0, + typeCode: '', localGovernmentUuid: 'fake-uuid', regionCode: 'region-code', - boardCode: 'board-code', }, fakeBoard, ); - await expect(promise).rejects.toMatchObject( - new Error( - `Planning meeting already exists with File ID ${existingFileNumber}`, - ), + expect(mockFileNumberService.generateNextFileNumber).toHaveBeenCalledTimes( + 1, ); - - expect(mockRepository.findOne).toHaveBeenCalledTimes(1); - expect(mockCardService.create).not.toHaveBeenCalled(); - expect(mockRepository.save).not.toHaveBeenCalled(); - }); - - it('should call through to the repo for get by card', async () => { - mockRepository.findOne.mockResolvedValue({} as PlanningReview); - const cardUuid = 'fake-card-uuid'; - await service.getByCardUuid(cardUuid); - - expect(mockRepository.findOne).toHaveBeenCalledTimes(1); - }); - - it('should throw an exception when getting by card fails', async () => { - mockRepository.findOne.mockResolvedValue(null); - const cardUuid = 'fake-card-uuid'; - const promise = service.getByCardUuid(cardUuid); - - await expect(promise).rejects.toMatchObject( - new Error(`Failed to find planning meeting with card uuid ${cardUuid}`), - ); - - expect(mockRepository.findOne).toHaveBeenCalledTimes(1); - }); - - it('should call through to the repo for get cards', async () => { - mockRepository.find.mockResolvedValue([]); - await service.getByBoard(''); - - expect(mockRepository.find).toHaveBeenCalledTimes(1); + expect(mockTypeRepository.findOneOrFail).toHaveBeenCalledTimes(1); + expect(mockCardService.create).toHaveBeenCalledTimes(1); + expect(mockRepository.save).toHaveBeenCalledTimes(1); + expect(mockReferralRepository.save).toHaveBeenCalledTimes(1); }); it('should call through to the repo for getby', async () => { @@ -139,13 +109,4 @@ describe('PlanningReviewService', () => { expect(mockRepository.find).toHaveBeenCalledTimes(1); expect(mockRepository.find.mock.calls[0][0]!.where).toEqual(mockFilter); }); - - it('should load deleted cards', async () => { - mockRepository.find.mockResolvedValue([]); - - await service.getDeletedCards('file-number'); - - expect(mockRepository.find).toHaveBeenCalledTimes(1); - expect(mockRepository.find.mock.calls[0][0]!.withDeleted).toEqual(true); - }); }); 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 23ec5bc9d9..7637d37df4 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 @@ -1,21 +1,16 @@ -import { - ServiceNotFoundException, - ServiceValidationException, -} from '@app/common/exceptions/base.exception'; -import { Mapper } from 'automapper-core'; -import { InjectMapper } from 'automapper-nestjs'; +import { ServiceNotFoundException } from '@app/common/exceptions/base.exception'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { - FindOptionsRelations, - FindOptionsWhere, - IsNull, - Not, - Repository, -} from 'typeorm'; +import { Mapper } from 'automapper-core'; +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 { Board } from '../board/board.entity'; import { CARD_TYPE } from '../card/card-type/card-type.entity'; import { CardService } from '../card/card.service'; +import { PlanningReferral } from './planning-referral/planning-referral.entity'; +import { PlanningReviewType } from './planning-review-type.entity'; import { CreatePlanningReviewDto, PlanningReviewDto, @@ -27,49 +22,45 @@ export class PlanningReviewService { constructor( private cardService: CardService, @InjectRepository(PlanningReview) - private repository: Repository<PlanningReview>, + private reviewRepository: Repository<PlanningReview>, + @InjectRepository(PlanningReviewType) + private typeRepository: Repository<PlanningReviewType>, + @InjectRepository(PlanningReferral) + private referralRepository: Repository<PlanningReferral>, @InjectMapper() private mapper: Mapper, + private fileNumberService: FileNumberService, ) {} - private CARD_RELATION = { - board: true, - type: true, - status: true, - assignee: true, - }; - private DEFAULT_RELATIONS: FindOptionsRelations<PlanningReview> = { - card: this.CARD_RELATION, localGovernment: true, region: true, }; async create(data: CreatePlanningReviewDto, board: Board) { - const existingReview = await this.repository.findOne({ + const fileNumber = await this.fileNumberService.generateNextFileNumber(); + const type = await this.typeRepository.findOneOrFail({ where: { - fileNumber: data.fileNumber, + code: data.typeCode, }, }); - if (existingReview) { - throw new ServiceValidationException( - `Planning meeting already exists with File ID ${data.fileNumber}`, - ); - } - const planningReview = new PlanningReview({ - type: data.type, + type, localGovernmentUuid: data.localGovernmentUuid, - fileNumber: data.fileNumber, + fileNumber: fileNumber, regionCode: data.regionCode, + documentName: data.documentName, }); - planningReview.card = await this.cardService.create( - CARD_TYPE.PLAN, - board, - false, - ); - const savedReview = await this.repository.save(planningReview); - return this.getOrFail(savedReview.uuid); + const savedReview = await this.reviewRepository.save(planningReview); + + const referral = new PlanningReferral({ + planningReview: savedReview, + dueDate: formatIncomingDate(data.dueDate), + submissionDate: formatIncomingDate(data.submissionDate)!, + referralDescription: data.description, + card: await this.cardService.create(CARD_TYPE.PLAN, board, false), + }); + return await this.referralRepository.save(referral); } async getOrFail(uuid: string) { @@ -91,43 +82,15 @@ export class PlanningReviewService { ); } - async getByCardUuid(cardUuid: string) { - const planningReview = await this.repository.findOne({ - where: { cardUuid }, - relations: this.DEFAULT_RELATIONS, - }); - - if (!planningReview) { - throw new ServiceNotFoundException( - `Failed to find planning meeting with card uuid ${cardUuid}`, - ); - } - - return planningReview; - } - getBy(findOptions: FindOptionsWhere<PlanningReview>) { - return this.repository.find({ + return this.reviewRepository.find({ where: findOptions, relations: this.DEFAULT_RELATIONS, }); } - getDeletedCards(fileNumber: string) { - return this.repository.find({ - where: { - fileNumber, - card: { - auditDeletedDateAt: Not(IsNull()), - }, - }, - withDeleted: true, - relations: this.DEFAULT_RELATIONS, - }); - } - private get(uuid: string) { - return this.repository.findOne({ + return this.reviewRepository.findOne({ where: { uuid, }, @@ -135,42 +98,10 @@ export class PlanningReviewService { }); } - async getByBoard(boardUuid: string) { - const res = await this.repository.find({ - relations: { - ...this.DEFAULT_RELATIONS, - card: { ...this.CARD_RELATION, board: false }, - }, - where: { - card: { - boardUuid, - auditDeletedDateAt: IsNull(), - }, - }, - }); - //Typeorm bug its returning deleted cards - return res.filter((review) => !!review.card); - } - - async getWithIncompleteSubtaskByType(subtaskType: string) { - return this.repository.find({ - where: { - card: { - subtasks: { - completedAt: IsNull(), - type: { - code: subtaskType, - }, - }, - }, - }, - relations: { - card: { - status: true, - board: true, - type: true, - subtasks: { type: true, assignee: true }, - }, + async listTypes() { + return this.typeRepository.find({ + order: { + label: 'ASC', }, }); } diff --git a/services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts b/services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts deleted file mode 100644 index bd502a97d5..0000000000 --- a/services/apps/alcs/src/alcs/search/non-applications/non-applications-view.entity.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - JoinColumn, - ManyToOne, - PrimaryColumn, - ViewColumn, - ViewEntity, -} from 'typeorm'; -import { LocalGovernment } from '../../local-government/local-government.entity'; - -@ViewEntity({ - expression: ` - SELECT - non_applications."uuid" - ,non_applications."file_number" - ,non_applications."applicant" - ,non_applications."type" - ,non_applications."class" - ,non_applications."local_government_uuid" as "local_government_uuid" - ,non_applications."card_uuid" - ,non_applications."board_code" - ,non_applications."region_code" - FROM - ( - SELECT - cov.uuid AS "uuid", - cov.file_number AS "file_number", - "applicant", - NULL AS "type", - 'COV' AS "class", - cov.local_government_uuid AS "local_government_uuid", - card.uuid AS "card_uuid", - board.code AS "board_code", - cov.region_code AS "region_code" - FROM - alcs.covenant cov - LEFT JOIN alcs.card card ON - cov.card_uuid = card.uuid AND card.audit_deleted_date_at IS NULL - LEFT JOIN alcs.board board ON - board.uuid = card.board_uuid AND board.audit_deleted_date_at IS NULL - WHERE cov.audit_deleted_date_at IS NULL - UNION - SELECT - planning_review.uuid AS "uuid", - planning_review.file_number AS "file_number", - NULL AS "applicant", - "type", - 'PLAN' AS "class", - planning_review.local_government_uuid AS "local_government_uuid", - card.uuid AS "card_uuid", - board.code AS "board_code", - planning_review.region_code AS "region_code" - FROM - alcs.planning_review planning_review - LEFT JOIN alcs.card card ON - planning_review.card_uuid = card.uuid AND card.audit_deleted_date_at IS NULL - LEFT JOIN alcs.board board ON - board.uuid = card.board_uuid AND board.audit_deleted_date_at IS NULL - WHERE planning_review.audit_deleted_date_at IS NULL - ) AS non_applications -`, -}) -export class NonApplicationSearchView { - @ViewColumn() - @PrimaryColumn() - uuid: string; - - @ViewColumn() - fileNumber: string; - - @ViewColumn() - applicant: string | null; - - @ViewColumn() - type: string | null; - - @ViewColumn() - localGovernmentUuid: string | null; - - @ViewColumn() - class: 'COV' | 'PLAN'; - - @ViewColumn() - cardUuid: string | null; - - @ViewColumn() - boardCode: string | null; - - @ViewColumn() - regionCode: string; - - @ManyToOne(() => LocalGovernment, { - nullable: true, - }) - @JoinColumn({ name: 'local_government_uuid' }) - localGovernment: LocalGovernment | null; -} diff --git a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts b/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts deleted file mode 100644 index ba300db684..0000000000 --- a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { SearchRequestDto } from '../search.dto'; -import { NonApplicationSearchView } from './non-applications-view.entity'; -import { NonApplicationsAdvancedSearchService } from './non-applications.service'; - -describe('NonApplicationsService', () => { - let service: NonApplicationsAdvancedSearchService; - let mockNonApplicationsRepository: DeepMocked< - Repository<NonApplicationSearchView> - >; - - let mockQuery: any = {}; - - const mockSearchRequestDto: SearchRequestDto = { - fileNumber: '123', - governmentName: 'B', - regionCode: 'C', - name: 'D', - page: 1, - pageSize: 10, - sortField: 'applicant', - sortDirection: 'ASC', - fileTypes: [], - }; - - beforeEach(async () => { - mockNonApplicationsRepository = createMock(); - - mockQuery = { - getManyAndCount: jest.fn().mockResolvedValue([[], 0]), - orderBy: jest.fn().mockReturnThis(), - offset: jest.fn().mockReturnThis(), - limit: jest.fn().mockReturnThis(), - leftJoinAndMapOne: jest.fn().mockReturnThis(), - groupBy: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - andWhere: jest.fn().mockReturnThis(), - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - NonApplicationsAdvancedSearchService, - { - provide: getRepositoryToken(NonApplicationSearchView), - useValue: mockNonApplicationsRepository, - }, - ], - }).compile(); - - service = module.get<NonApplicationsAdvancedSearchService>( - NonApplicationsAdvancedSearchService, - ); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - it('should successfully build a query using all search parameters defined', async () => { - mockNonApplicationsRepository.createQueryBuilder.mockReturnValue( - mockQuery as any, - ); - - const result = await service.searchNonApplications(mockSearchRequestDto); - - expect(result).toEqual({ data: [], total: 0 }); - expect(mockNonApplicationsRepository.createQueryBuilder).toBeCalledTimes(1); - expect(mockQuery.andWhere).toBeCalledTimes(4); - expect(mockQuery.where).toBeCalledTimes(1); - }); - - it('should call compileSearchQuery method correctly', async () => { - const compileNonApplicationSearchQuerySpy = jest - .spyOn(service as any, 'compileSearchQuery') - .mockResolvedValue(mockQuery); - - const result = await service.searchNonApplications(mockSearchRequestDto); - - expect(result).toEqual({ data: [], total: 0 }); - expect(compileNonApplicationSearchQuerySpy).toBeCalledWith( - mockSearchRequestDto, - ); - expect(mockQuery.orderBy).toHaveBeenCalledTimes(1); - expect(mockQuery.offset).toHaveBeenCalledTimes(1); - expect(mockQuery.limit).toHaveBeenCalledTimes(1); - }); -}); diff --git a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts b/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts deleted file mode 100644 index 0586789645..0000000000 --- a/services/apps/alcs/src/alcs/search/non-applications/non-applications.service.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { formatStringToPostgresSearchStringArrayWithWildCard } from '../../../utils/search-helper'; -import { LocalGovernment } from '../../local-government/local-government.entity'; -import { AdvancedSearchResultDto, SearchRequestDto } from '../search.dto'; -import { NonApplicationSearchView } from './non-applications-view.entity'; - -@Injectable() -export class NonApplicationsAdvancedSearchService { - constructor( - @InjectRepository(NonApplicationSearchView) - private nonApplicationSearchRepository: Repository<NonApplicationSearchView>, - ) {} - - async searchNonApplications( - searchDto: SearchRequestDto, - ): Promise<AdvancedSearchResultDto<NonApplicationSearchView[]>> { - let query = await this.compileSearchQuery(searchDto); - - const sortQuery = this.compileSortQuery(searchDto); - - query = query - .orderBy( - sortQuery, - searchDto.sortDirection, - searchDto.sortDirection === 'ASC' ? 'NULLS FIRST' : 'NULLS LAST', - ) - .offset((searchDto.page - 1) * searchDto.pageSize) - .limit(searchDto.pageSize); - - const result = await query.getManyAndCount(); - - return { - data: result[0], - total: result[1], - }; - } - - private compileSortQuery(searchDto: SearchRequestDto) { - switch (searchDto.sortField) { - case 'applicant': - return '"nonApp"."applicant"'; - - case 'government': - return '"localGovernment"."name"'; - - case 'type': - return '"nonApp"."class"'; - - default: - case 'fileId': - return '"nonApp"."file_number"'; - } - } - - private async compileSearchQuery(searchDto: SearchRequestDto) { - let query = this.nonApplicationSearchRepository - .createQueryBuilder('nonApp') - .leftJoinAndMapOne( - 'nonApp.localGovernment', - LocalGovernment, - 'localGovernment', - '"nonApp"."local_government_uuid" = "localGovernment".uuid', - ) - .where('1 = 1'); - - if (searchDto.fileNumber) { - query = query.andWhere('nonApp.file_number = :fileNumber', { - fileNumber: searchDto.fileNumber ?? null, - }); - } - - if (searchDto.regionCode) { - query = query.andWhere('nonApp.region_code = :regionCode', { - regionCode: searchDto.regionCode, - }); - } - - if (searchDto.governmentName) { - query = query.andWhere('localGovernment.name = :localGovernmentName', { - localGovernmentName: searchDto.governmentName, - }); - } - - if (searchDto.name) { - const formattedSearchString = - formatStringToPostgresSearchStringArrayWithWildCard(searchDto.name!); - - query = query.andWhere('LOWER(nonApp.applicant) LIKE ANY (:names)', { - names: formattedSearchString, - }); - } - - if (searchDto.fileTypes.length > 0) { - query = query.andWhere('nonApp.class IN (:...typeCodes)', { - typeCodes: searchDto.fileTypes, - }); - } - - return query; - } -} diff --git a/services/apps/alcs/src/alcs/search/search.controller.spec.ts b/services/apps/alcs/src/alcs/search/search.controller.spec.ts index ebf3a417ff..41f513ec68 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.spec.ts @@ -1,8 +1,8 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { classes } from 'automapper-classes'; import { AutomapperModule } from 'automapper-nestjs'; -import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; -import { Test, TestingModule } from '@nestjs/testing'; import { ClsService } from 'nestjs-cls'; import { DataSource, QueryRunner, Repository } from 'typeorm'; import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes'; @@ -13,9 +13,7 @@ import { ApplicationType } from '../code/application-code/application-type/appli import { Covenant } from '../covenant/covenant.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'; import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service'; -import { NonApplicationsAdvancedSearchService } from './non-applications/non-applications.service'; import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; import { SearchController } from './search.controller'; @@ -27,7 +25,6 @@ describe('SearchController', () => { let mockSearchService: DeepMocked<SearchService>; let mockNoticeOfIntentAdvancedSearchService: DeepMocked<NoticeOfIntentAdvancedSearchService>; let mockApplicationAdvancedSearchService: DeepMocked<ApplicationAdvancedSearchService>; - let mockNonApplicationsAdvancedSearchService: DeepMocked<NonApplicationsAdvancedSearchService>; let mockNotificationAdvancedSearchService: DeepMocked<NotificationAdvancedSearchService>; let mockDataSource: DeepMocked<DataSource>; let mockQueryRunner: DeepMocked<QueryRunner>; @@ -37,7 +34,6 @@ describe('SearchController', () => { mockSearchService = createMock(); mockNoticeOfIntentAdvancedSearchService = createMock(); mockApplicationAdvancedSearchService = createMock(); - mockNonApplicationsAdvancedSearchService = createMock(); mockNotificationAdvancedSearchService = createMock(); mockDataSource = createMock(); mockAppTypeRepo = createMock(); @@ -61,10 +57,6 @@ describe('SearchController', () => { provide: ApplicationAdvancedSearchService, useValue: mockApplicationAdvancedSearchService, }, - { - provide: NonApplicationsAdvancedSearchService, - useValue: mockNonApplicationsAdvancedSearchService, - }, { provide: NotificationAdvancedSearchService, useValue: mockNotificationAdvancedSearchService, @@ -95,15 +87,6 @@ describe('SearchController', () => { mockSearchService.getApplication.mockResolvedValue(new Application()); mockSearchService.getNoi.mockResolvedValue(new NoticeOfIntent()); mockSearchService.getNotification.mockResolvedValue(new Notification()); - mockSearchService.getPlanningReview.mockResolvedValue( - new PlanningReview({ - card: { - board: { - code: 'fake_board', - } as Board, - } as Card, - }), - ); mockSearchService.getCovenant.mockResolvedValue( new Covenant({ card: { @@ -126,13 +109,6 @@ describe('SearchController', () => { total: 0, }); - mockNonApplicationsAdvancedSearchService.searchNonApplications.mockResolvedValue( - { - data: [], - total: 0, - }, - ); - mockNotificationAdvancedSearchService.search.mockResolvedValue({ data: [], total: 0, @@ -151,14 +127,12 @@ describe('SearchController', () => { expect(mockSearchService.getApplication).toBeCalledWith(searchString); expect(mockSearchService.getNoi).toBeCalledTimes(1); expect(mockSearchService.getNoi).toBeCalledWith(searchString); - expect(mockSearchService.getPlanningReview).toBeCalledTimes(1); - expect(mockSearchService.getPlanningReview).toBeCalledWith(searchString); expect(mockSearchService.getCovenant).toBeCalledTimes(1); expect(mockSearchService.getCovenant).toBeCalledWith(searchString); expect(mockSearchService.getNotification).toHaveBeenCalledTimes(1); expect(mockSearchService.getNotification).toBeCalledWith(searchString); expect(result).toBeDefined(); - expect(result.length).toBe(5); + expect(result.length).toBe(4); }); it('should call advanced search to retrieve Applications, NOIs, PlanningReviews, Covenants, Notifications', async () => { @@ -192,15 +166,6 @@ describe('SearchController', () => { ).toBeCalledWith(mockSearchRequestDto); expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); - - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledTimes(1); - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledWith(mockSearchRequestDto); - expect(result.nonApplications).toBeDefined(); - expect(result.totalNonApplications).toBe(0); }); it('should call applications advanced search to retrieve Applications', async () => { @@ -251,28 +216,6 @@ describe('SearchController', () => { expect(result.total).toBe(0); }); - it('should call non-applications advanced search to retrieve Non-Applications', async () => { - const mockSearchRequestDto: SearchRequestDto = { - pageSize: 1, - page: 1, - sortField: '1', - sortDirection: 'ASC', - fileTypes: [], - }; - - const result = - await controller.advancedSearchNonApplications(mockSearchRequestDto); - - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledTimes(1); - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledWith(mockSearchRequestDto); - expect(result.data).toBeDefined(); - expect(result.total).toBe(0); - }); - it('should call advanced search to retrieve Applications only when application file type selected', async () => { const mockSearchRequestDto = { pageSize: 1, @@ -320,57 +263,4 @@ describe('SearchController', () => { expect(result.noticeOfIntents).toBeDefined(); expect(result.totalNoticeOfIntents).toBe(0); }); - - it('should call advanced search to retrieve Non Applications only when non application file type selected', async () => { - const mockSearchRequestDto: SearchRequestDto = { - pageSize: 1, - page: 1, - sortField: '1', - sortDirection: 'ASC', - fileTypes: ['COV'], - }; - - const result = await controller.advancedSearch(mockSearchRequestDto); - - expect(result.totalNoticeOfIntents).toBe(0); - - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledTimes(1); - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledWith(mockSearchRequestDto); - expect(result.nonApplications).toBeDefined(); - expect(result.totalNonApplications).toBe(0); - }); - - it('should NOT call Non-applications advanced search to retrieve Non-applications if no non-application search fields specified', async () => { - const baseMockSearchRequestDto: SearchRequestDto = { - pageSize: 1, - page: 1, - sortField: '1', - sortDirection: 'ASC', - fileTypes: [], - }; - - const result = await controller.advancedSearch({ - ...baseMockSearchRequestDto, - legacyId: 'test', - }); - - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledTimes(1); - expect( - mockApplicationAdvancedSearchService.searchApplications, - ).toBeCalledWith({ ...baseMockSearchRequestDto, legacyId: 'test' }, {}); - expect(result.applications).toBeDefined(); - expect(result.totalApplications).toBe(0); - - expect( - mockNonApplicationsAdvancedSearchService.searchNonApplications, - ).toBeCalledTimes(0); - expect(result.nonApplications).toBeDefined(); - expect(result.totalNonApplications).toBe(0); - }); }); diff --git a/services/apps/alcs/src/alcs/search/search.controller.ts b/services/apps/alcs/src/alcs/search/search.controller.ts index 3cf07d8e6d..1d2616f5ca 100644 --- a/services/apps/alcs/src/alcs/search/search.controller.ts +++ b/services/apps/alcs/src/alcs/search/search.controller.ts @@ -11,7 +11,6 @@ import { UserRoles } from '../../common/authorization/roles.decorator'; import { APPLICATION_SUBMISSION_TYPES } from '../../portal/pdf-generation/generate-submission-document.service'; import { isStringSetAndNotEmpty } from '../../utils/string-helper'; import { Application } from '../application/application.entity'; -import { ApplicationService } from '../application/application.service'; import { CARD_TYPE } from '../card/card-type/card-type.entity'; import { ApplicationTypeDto } from '../code/application-code/application-type/application-type.dto'; import { ApplicationType } from '../code/application-code/application-type/application-type.entity'; @@ -21,8 +20,6 @@ import { Notification } from '../notification/notification.entity'; import { PlanningReview } from '../planning-review/planning-review.entity'; import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service'; import { ApplicationSubmissionSearchView } from './application/application-search-view.entity'; -import { NonApplicationSearchView } from './non-applications/non-applications-view.entity'; -import { NonApplicationsAdvancedSearchService } from './non-applications/non-applications.service'; import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service'; import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent/notice-of-intent-search-view.entity'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; @@ -31,7 +28,6 @@ import { AdvancedSearchResponseDto, AdvancedSearchResultDto, ApplicationSearchResultDto, - NonApplicationSearchResultDto, NoticeOfIntentSearchResultDto, NotificationSearchResultDto, SearchRequestDto, @@ -48,7 +44,6 @@ export class SearchController { @InjectMapper() private mapper: Mapper, private noticeOfIntentSearchService: NoticeOfIntentAdvancedSearchService, private applicationSearchService: ApplicationAdvancedSearchService, - private nonApplicationsSearchService: NonApplicationsAdvancedSearchService, private notificationSearchService: NotificationAdvancedSearchService, @InjectRepository(ApplicationType) private appTypeRepo: Repository<ApplicationType>, @@ -61,8 +56,6 @@ export class SearchController { async search(@Param('searchTerm') searchTerm) { const application = await this.searchService.getApplication(searchTerm); const noi = await this.searchService.getNoi(searchTerm); - const planningReview = - await this.searchService.getPlanningReview(searchTerm); const covenant = await this.searchService.getCovenant(searchTerm); const notification = await this.searchService.getNotification(searchTerm); @@ -72,7 +65,7 @@ export class SearchController { result, application, noi, - planningReview, + null, //TODO covenant, notification, ); @@ -142,17 +135,6 @@ export class SearchController { searchDto, ); } - - let nonApplications: AdvancedSearchResultDto< - NonApplicationSearchView[] - > | null = null; - if (searchNonApplications) { - nonApplications = - await this.nonApplicationsSearchService.searchNonApplications( - searchDto, - ); - } - let notifications: AdvancedSearchResultDto< NotificationSubmissionSearchView[] > | null = null; @@ -163,7 +145,7 @@ export class SearchController { return await this.mapAdvancedSearchResults( applicationSearchResult, noticeOfIntentSearchService, - nonApplications, + null, notifications, ); } finally { @@ -222,27 +204,6 @@ export class SearchController { }; } - @Post('/advanced/non-applications') - @UserRoles(...ROLES_ALLOWED_APPLICATIONS) - async advancedSearchNonApplications( - @Body() searchDto: SearchRequestDto, - ): Promise<AdvancedSearchResultDto<NonApplicationSearchResultDto[]>> { - const nonApplications = - await this.nonApplicationsSearchService.searchNonApplications(searchDto); - - const mappedSearchResult = await this.mapAdvancedSearchResults( - null, - null, - nonApplications, - null, - ); - - return { - total: mappedSearchResult.totalNonApplications, - data: mappedSearchResult.nonApplications, - }; - } - @Post('/advanced/notifications') @UserRoles(...ROLES_ALLOWED_APPLICATIONS) async advancedSearchNotifications( @@ -327,7 +288,7 @@ export class SearchController { noticeOfIntents: AdvancedSearchResultDto< NoticeOfIntentSubmissionSearchView[] > | null, - nonApplications: AdvancedSearchResultDto<NonApplicationSearchView[]> | null, + nonApplications: null, notifications: AdvancedSearchResultDto< NotificationSubmissionSearchView[] > | null, @@ -358,15 +319,6 @@ export class SearchController { ); } - const mappedNonApplications: NonApplicationSearchResultDto[] = []; - if (nonApplications?.data && nonApplications?.data.length > 0) { - mappedNonApplications.push( - ...nonApplications.data.map((nonApplication) => - this.mapNonApplicationToAdvancedSearchResult(nonApplication), - ), - ); - } - const mappedNotifications: NotificationSearchResultDto[] = []; if (notifications && notifications.data && notifications.data.length > 0) { mappedNotifications.push( @@ -380,8 +332,6 @@ export class SearchController { response.totalApplications = applications?.total ?? 0; response.noticeOfIntents = mappedNoticeOfIntents; response.totalNoticeOfIntents = noticeOfIntents?.total ?? 0; - response.nonApplications = mappedNonApplications; - response.totalNonApplications = nonApplications?.total ?? 0; response.notifications = mappedNotifications; response.totalNotifications = notifications?.total ?? 0; @@ -420,12 +370,12 @@ export class SearchController { private mapPlanningReviewToSearchResult( planning: PlanningReview, ): SearchResultDto { + //TODO return { - type: CARD_TYPE.PLAN, - referenceId: planning.cardUuid, - localGovernmentName: planning.localGovernment?.name, - fileNumber: planning.fileNumber, - boardCode: planning.card.board.code, + fileNumber: '', + localGovernmentName: undefined, + referenceId: '', + type: '', }; } @@ -491,20 +441,6 @@ export class SearchController { }; } - private mapNonApplicationToAdvancedSearchResult( - nonApplication: NonApplicationSearchView, - ): NonApplicationSearchResultDto { - return { - referenceId: nonApplication.cardUuid, - fileNumber: nonApplication.fileNumber, - applicant: nonApplication.applicant, - boardCode: nonApplication.boardCode, - type: nonApplication.type, - localGovernmentName: nonApplication.localGovernment?.name ?? null, - class: nonApplication.class, - }; - } - private mapNotificationToAdvancedSearchResult( notification: NotificationSubmissionSearchView, ): NoticeOfIntentSearchResultDto { diff --git a/services/apps/alcs/src/alcs/search/search.dto.ts b/services/apps/alcs/src/alcs/search/search.dto.ts index e169a54787..78b918f28f 100644 --- a/services/apps/alcs/src/alcs/search/search.dto.ts +++ b/services/apps/alcs/src/alcs/search/search.dto.ts @@ -69,7 +69,6 @@ export class NotificationSearchResultDto { export class AdvancedSearchResponseDto { applications: ApplicationSearchResultDto[]; noticeOfIntents: NoticeOfIntentSearchResultDto[]; - nonApplications: NonApplicationSearchResultDto[]; notifications: NotificationSearchResultDto[]; totalApplications: number; totalNoticeOfIntents: number; diff --git a/services/apps/alcs/src/alcs/search/search.module.ts b/services/apps/alcs/src/alcs/search/search.module.ts index 1ba7c64f06..6fbb75f13e 100644 --- a/services/apps/alcs/src/alcs/search/search.module.ts +++ b/services/apps/alcs/src/alcs/search/search.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ApplicationProfile } from '../../common/automapper/application.automapper.profile'; import { Application } from '../application/application.entity'; -import { ApplicationModule } from '../application/application.module'; import { ApplicationType } from '../code/application-code/application-type/application-type.entity'; import { Covenant } from '../covenant/covenant.entity'; import { LocalGovernment } from '../local-government/local-government.entity'; @@ -11,8 +10,6 @@ import { Notification } from '../notification/notification.entity'; import { PlanningReview } from '../planning-review/planning-review.entity'; import { ApplicationAdvancedSearchService } from './application/application-advanced-search.service'; import { ApplicationSubmissionSearchView } from './application/application-search-view.entity'; -import { NonApplicationSearchView } from './non-applications/non-applications-view.entity'; -import { NonApplicationsAdvancedSearchService } from './non-applications/non-applications.service'; import { NoticeOfIntentAdvancedSearchService } from './notice-of-intent/notice-of-intent-advanced-search.service'; import { NoticeOfIntentSubmissionSearchView } from './notice-of-intent/notice-of-intent-search-view.entity'; import { NotificationAdvancedSearchService } from './notification/notification-advanced-search.service'; @@ -32,7 +29,6 @@ import { SearchService } from './search.service'; LocalGovernment, ApplicationSubmissionSearchView, NoticeOfIntentSubmissionSearchView, - NonApplicationSearchView, NotificationSubmissionSearchView, ]), ], @@ -41,7 +37,6 @@ import { SearchService } from './search.service'; ApplicationProfile, ApplicationAdvancedSearchService, NoticeOfIntentAdvancedSearchService, - NonApplicationsAdvancedSearchService, NotificationAdvancedSearchService, ], controllers: [SearchController], diff --git a/services/apps/alcs/src/alcs/search/search.service.spec.ts b/services/apps/alcs/src/alcs/search/search.service.spec.ts index e901b4bc0c..a01f160f12 100644 --- a/services/apps/alcs/src/alcs/search/search.service.spec.ts +++ b/services/apps/alcs/src/alcs/search/search.service.spec.ts @@ -7,7 +7,6 @@ import { Covenant } from '../covenant/covenant.entity'; import { LocalGovernment } from '../local-government/local-government.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'; import { ApplicationSubmissionSearchView } from './application/application-search-view.entity'; import { SearchService } from './search.service'; @@ -15,7 +14,6 @@ describe('SearchService', () => { let service: SearchService; let mockApplicationRepository: DeepMocked<Repository<Application>>; let mockNoiRepository: DeepMocked<Repository<NoticeOfIntent>>; - let mockPlanningReviewRepository: DeepMocked<Repository<PlanningReview>>; let mockCovenantRepository: DeepMocked<Repository<Covenant>>; let mockApplicationSubmissionSearchView: DeepMocked< Repository<ApplicationSubmissionSearchView> @@ -28,7 +26,6 @@ describe('SearchService', () => { beforeEach(async () => { mockApplicationRepository = createMock(); mockNoiRepository = createMock(); - mockPlanningReviewRepository = createMock(); mockCovenantRepository = createMock(); mockApplicationSubmissionSearchView = createMock(); mockLocalGovernment = createMock(); @@ -45,10 +42,6 @@ describe('SearchService', () => { provide: getRepositoryToken(NoticeOfIntent), useValue: mockNoiRepository, }, - { - provide: getRepositoryToken(PlanningReview), - useValue: mockPlanningReviewRepository, - }, { provide: getRepositoryToken(Covenant), useValue: mockCovenantRepository, @@ -112,29 +105,6 @@ describe('SearchService', () => { expect(result).toBeDefined(); }); - it('should call repository to get planning review', async () => { - mockPlanningReviewRepository.findOne.mockResolvedValue( - new PlanningReview(), - ); - - const result = await service.getPlanningReview('fake'); - - expect(mockPlanningReviewRepository.findOne).toBeCalledTimes(1); - expect(mockPlanningReviewRepository.findOne).toBeCalledWith({ - where: { - fileNumber: fakeFileNumber, - card: { archived: false }, - }, - relations: { - card: { - board: true, - }, - localGovernment: true, - }, - }); - expect(result).toBeDefined(); - }); - it('should call repository to get covenant', async () => { mockCovenantRepository.findOne.mockResolvedValue(new Covenant()); diff --git a/services/apps/alcs/src/alcs/search/search.service.ts b/services/apps/alcs/src/alcs/search/search.service.ts index c69dc2906b..1351386f20 100644 --- a/services/apps/alcs/src/alcs/search/search.service.ts +++ b/services/apps/alcs/src/alcs/search/search.service.ts @@ -5,6 +5,7 @@ import { Application } from '../application/application.entity'; import { Covenant } from '../covenant/covenant.entity'; import { NoticeOfIntent } from '../notice-of-intent/notice-of-intent.entity'; import { Notification } from '../notification/notification.entity'; +import { PlanningReferral } from '../planning-review/planning-referral/planning-referral.entity'; import { PlanningReview } from '../planning-review/planning-review.entity'; const CARD_RELATIONSHIP = { @@ -21,8 +22,6 @@ export class SearchService { private applicationRepository: Repository<Application>, @InjectRepository(NoticeOfIntent) private noiRepository: Repository<NoticeOfIntent>, - @InjectRepository(PlanningReview) - private planningReviewRepository: Repository<PlanningReview>, @InjectRepository(Covenant) private covenantRepository: Repository<Covenant>, @InjectRepository(Notification) @@ -54,16 +53,6 @@ export class SearchService { }); } - async getPlanningReview(fileNumber: string) { - return await this.planningReviewRepository.findOne({ - where: { - fileNumber, - card: { archived: false }, - }, - relations: CARD_RELATIONSHIP, - }); - } - async getCovenant(fileNumber: string) { return await this.covenantRepository.findOne({ where: { diff --git a/services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts b/services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts deleted file mode 100644 index c5f68bd4dd..0000000000 --- a/services/apps/alcs/src/common/automapper/planning-meeting.automapper.profile.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createMap, Mapper } from 'automapper-core'; -import { AutomapperProfile, InjectMapper } from 'automapper-nestjs'; -import { Injectable } from '@nestjs/common'; -import { PlanningReviewDto } from '../../alcs/planning-review/planning-review.dto'; -import { PlanningReview } from '../../alcs/planning-review/planning-review.entity'; - -@Injectable() -export class PlanningReviewProfile extends AutomapperProfile { - constructor(@InjectMapper() mapper: Mapper) { - super(mapper); - } - - override get profile() { - return (mapper) => { - createMap(mapper, PlanningReview, PlanningReviewDto); - }; - } -} 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 new file mode 100644 index 0000000000..6d2fecc01e --- /dev/null +++ b/services/apps/alcs/src/common/automapper/planning-review.automapper.profile.ts @@ -0,0 +1,38 @@ +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, + PlanningReviewDto, + PlanningReviewTypeDto, +} from '../../alcs/planning-review/planning-review.dto'; +import { PlanningReview } from '../../alcs/planning-review/planning-review.entity'; + +@Injectable() +export class PlanningReviewProfile extends AutomapperProfile { + constructor(@InjectMapper() mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper) => { + createMap(mapper, PlanningReviewType, PlanningReviewTypeDto); + createMap(mapper, PlanningReview, PlanningReviewDto); + createMap( + mapper, + PlanningReferral, + PlanningReferralDto, + forMember( + (dto) => dto.dueDate, + mapFrom((entity) => entity.dueDate?.getTime()), + ), + forMember( + (dto) => dto.submissionDate, + mapFrom((entity) => entity.submissionDate?.getTime()), + ), + ); + }; + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts new file mode 100644 index 0000000000..464a5f8c2f --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1709662671997-planning_reviews_v2.ts @@ -0,0 +1,108 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class PlanningReviewsV21709662671997 implements MigrationInterface { + name = 'PlanningReviewsV21709662671997'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`DROP VIEW "alcs"."non_application_search_view"`); + await queryRunner.query(`TRUNCATE TABLE "alcs"."planning_review"`); + await queryRunner.query( + `UPDATE "alcs"."card" SET "audit_deleted_date_at" = NOW(), "archived" = true WHERE "type_code" = 'PLAN'`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "FK_735dcdd4fa909a60d0fa1828f24"`, + ); + await queryRunner.query( + `DROP INDEX "alcs"."IDX_a62913da5fae4a128c8e8f264f"`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."planning_review_type" ("audit_deleted_date_at" TIMESTAMP WITH TIME ZONE, "audit_created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "audit_updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(), "audit_created_by" character varying NOT NULL, "audit_updated_by" character varying, "label" character varying NOT NULL, "code" text NOT NULL, "description" text NOT NULL, "short_label" character varying NOT NULL, "background_color" character varying NOT NULL, "text_color" character varying NOT NULL, "html_description" text NOT NULL DEFAULT '', CONSTRAINT "UQ_ab764743ecbd39b1fc823d2445d" UNIQUE ("description"), CONSTRAINT "PK_d06659689a2bb22ccdc6a1a033b" PRIMARY KEY ("code"))`, + ); + await queryRunner.query( + `CREATE TABLE "alcs"."planning_referral" ("audit_deleted_date_at" TIMESTAMP WITH TIME ZONE, "audit_created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "audit_updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(), "audit_created_by" character varying NOT NULL, "audit_updated_by" character varying, "uuid" uuid NOT NULL DEFAULT gen_random_uuid(), "submission_date" TIMESTAMP WITH TIME ZONE NOT NULL, "due_date" TIMESTAMP WITH TIME ZONE, "response_date" TIMESTAMP WITH TIME ZONE, "referral_description" text, "response_description" text, "card_uuid" uuid NOT NULL, "planning_review_uuid" uuid, CONSTRAINT "REL_57f6fea41fefa2ca864a33b795" UNIQUE ("card_uuid"), CONSTRAINT "PK_1cd8a7e0399adfcc4cbd1ca7cb9" PRIMARY KEY ("uuid"))`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "type"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "REL_03a05aa8fefbc2fc1cdf138d80"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "card_uuid"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "document_name" character varying NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "type_code" text NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "open" boolean NOT NULL DEFAULT true`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "closed_date" TIMESTAMP WITH TIME ZONE`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "closed_by_uuid" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "FK_d06659689a2bb22ccdc6a1a033b" FOREIGN KEY ("type_code") REFERENCES "alcs"."planning_review_type"("code") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "FK_84caebfef3502f3fb80e168ba44" FOREIGN KEY ("closed_by_uuid") REFERENCES "alcs"."user"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_referral" ADD CONSTRAINT "FK_095877a396b8c604d81d674f6f8" FOREIGN KEY ("planning_review_uuid") REFERENCES "alcs"."planning_review"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_referral" ADD CONSTRAINT "FK_57f6fea41fefa2ca864a33b7950" FOREIGN KEY ("card_uuid") REFERENCES "alcs"."card"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "alcs"."planning_referral" DROP CONSTRAINT "FK_57f6fea41fefa2ca864a33b7950"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_referral" DROP CONSTRAINT "FK_095877a396b8c604d81d674f6f8"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "FK_84caebfef3502f3fb80e168ba44"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP CONSTRAINT "FK_d06659689a2bb22ccdc6a1a033b"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "closed_by_uuid"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "closed_date"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "open"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "type_code"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" DROP COLUMN "document_name"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "card_uuid" uuid NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "REL_03a05aa8fefbc2fc1cdf138d80" UNIQUE ("card_uuid")`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD "type" character varying NOT NULL`, + ); + await queryRunner.query(`DROP TABLE "alcs"."planning_referral"`); + await queryRunner.query(`DROP TABLE "alcs"."planning_review_type"`); + await queryRunner.query( + `CREATE INDEX "IDX_a62913da5fae4a128c8e8f264f" ON "alcs"."planning_review" ("file_number") `, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."planning_review" ADD CONSTRAINT "FK_735dcdd4fa909a60d0fa1828f24" FOREIGN KEY ("card_uuid") REFERENCES "alcs"."card"("uuid") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts new file mode 100644 index 0000000000..271bdd32f0 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1709663586391-seed_planning_reviews_v2.ts @@ -0,0 +1,61 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SeedPlanningReviewsV21709663586391 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<void> { + //Statuses + await queryRunner.query(` + INSERT INTO "alcs"."planning_review_type" + ("audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "label", "code", "description", "short_label", "background_color", "text_color", "html_description") VALUES + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Agricultural Area Plan', 'AAPP', 'Agricultural Area Plan', 'AAP', '#F5B8BA', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Misc Studies and Projects', 'MISC', 'Misc Studies and Projects', 'MISC', '#C8FCFC', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'L/FNG Boundary Adjustment', 'BAPP', 'L/FNG Boundary Adjustment', 'BA', '#FFDBE3', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'ALR Boundary', 'ALRB', 'ALR Boundary', 'ALRB', '#BDDCBD', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Regional Growth Strategy', 'RGSP', 'Regional Growth Strategy', 'RGS', '#FFE1B3', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Crown Land Use Plan', 'CLUP', 'Crown Land Use Plan', 'CLUP', '#B5C7E1', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Official Community Plan', 'OCPP', 'Official Community Plan', 'OCP', '#FFF9C5', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Transportation Plan', 'TPPP', 'Transportation Plan', 'TP', '#EDC0F5', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Utility/Energy Planning', 'UEPP', 'Utility/Energy Planning', 'UEP', '#E1F8C7', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Zoning Bylaw', 'ZBPP', 'Zoning Bylaw', 'ZB', '#B5D5E0', '#313132', DEFAULT), + (NULL, NOW(), NULL, 'migration-seed', NULL, 'Parks Planning', 'PARK', 'Parks Planning', 'PARK', '#C8E0FD', '#313132', DEFAULT); + `); + + //New Board + await queryRunner.query(` + INSERT INTO "alcs"."board" + ("uuid", "audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "code", "title", "show_on_schedule") VALUES + ('e7b18852-4f8f-419e-83e3-60e706b4a494', NULL, NOW(), NULL, 'migration_seed', NULL, 'rppp', 'Regional Planning', 'f'); + `); + + //Allow Planning Cards on new board + await queryRunner.query(` + INSERT INTO "alcs"."board_allowed_card_types_card_type" ("board_uuid", "card_type_code") VALUES + ('e7b18852-4f8f-419e-83e3-60e706b4a494', 'PLAN'); + `); + + //Remove from Vetting + await queryRunner.query(` + DELETE FROM "alcs"."board_allowed_card_types_card_type" WHERE ("board_uuid" = 'bb70eb85-6250-49b9-9a5c-e3c2e0b9f3a2' AND "card_type_code" = 'PLAN'); + `); + + //Change creation from Executive Committee to Regional Planning Board + await queryRunner.query(` + DELETE FROM "alcs"."board_create_card_types_card_type" WHERE ("board_uuid" = 'd8c18278-cb41-474e-a180-534a101243ab' AND "card_type_code" = 'PLAN'); + `); + + await queryRunner.query(` + INSERT INTO "alcs"."board_create_card_types_card_type" ("board_uuid", "card_type_code") VALUES + ('e7b18852-4f8f-419e-83e3-60e706b4a494', 'PLAN'); + `); + + //Add column to board + await queryRunner.query(` + INSERT INTO "alcs"."board_status" + ("uuid", "audit_deleted_date_at", "audit_created_at", "audit_updated_at", "audit_created_by", "audit_updated_by", "order", "board_uuid", "status_code") VALUES + ('6560aaec-9b9d-4ad6-9b8b-ccf2ce384b69', NULL, NOW(), NULL, 'migration_seed', NULL, 0, 'e7b18852-4f8f-419e-83e3-60e706b4a494', 'SUBM'); + `); + } + + public async down(): Promise<void> { + //Nope + } +} From f72ab316250d4cd9ee0cec73fa78348c31753fe2 Mon Sep 17 00:00:00 2001 From: Daniel Haselhan <daniel@bit3.ca> Date: Wed, 6 Mar 2024 11:47:32 -0800 Subject: [PATCH 2/2] Code Review Feedback --- .../src/app/features/board/board.component.ts | 3 ++- .../alcs/card/card-type/card-type.entity.ts | 4 +--- .../planning-referral.entity.ts | 5 ++++- .../planning-review/planning-review.entity.ts | 4 +++- .../1709754346579-add_table_comments_to_pr.ts | 18 ++++++++++++++++++ 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts diff --git a/alcs-frontend/src/app/features/board/board.component.ts b/alcs-frontend/src/app/features/board/board.component.ts index f79667dc02..e0b80970e8 100644 --- a/alcs-frontend/src/app/features/board/board.component.ts +++ b/alcs-frontend/src/app/features/board/board.component.ts @@ -384,7 +384,7 @@ export class BoardComponent implements OnInit, OnDestroy { private mapPlanningReferralToCard(referral: PlanningReferralDto): CardData { return { status: referral.card.status.code, - typeLabel: 'Non-Application', + typeLabel: 'Planning Review', title: `${referral.planningReview.fileNumber} (${referral.planningReview.documentName})`, titleTooltip: referral.planningReview.type.label, assignee: referral.card.assignee, @@ -395,6 +395,7 @@ export class BoardComponent implements OnInit, OnDestroy { highPriority: referral.card.highPriority, cardUuid: referral.card.uuid, dateReceived: referral.card.createdAt, + dueDate: referral.dueDate ? new Date(referral.dueDate) : undefined, }; } diff --git a/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts b/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts index 84ccd30d68..78330ac6aa 100644 --- a/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts +++ b/services/apps/alcs/src/alcs/card/card-type/card-type.entity.ts @@ -13,9 +13,7 @@ export enum CARD_TYPE { NOTIFICATION = 'NOTI', } -@Entity({ - comment: '', -}) +@Entity() export class CardType extends BaseCodeEntity { constructor(data?: Partial<CardType>) { super(); 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 27c603e937..e126888a8f 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 @@ -5,7 +5,10 @@ import { Base } from '../../../common/entities/base.entity'; import { Card } from '../../card/card.entity'; import { PlanningReview } from '../planning-review.entity'; -@Entity() +@Entity({ + comment: + 'Planning Referrals represent each pass of a Planning Review with their own cards', +}) export class PlanningReferral extends Base { constructor(data?: Partial<PlanningReferral>) { super(); 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 82c76d9756..9fa30d217e 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 @@ -5,7 +5,9 @@ import { ApplicationRegion } from '../code/application-code/application-region/a import { LocalGovernment } from '../local-government/local-government.entity'; import { PlanningReviewType } from './planning-review-type.entity'; -@Entity() +@Entity({ + comment: 'A review of a local government or municipalities plan', +}) export class PlanningReview extends Base { constructor(data?: Partial<PlanningReview>) { super(); diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts b/services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts new file mode 100644 index 0000000000..98bc58f880 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1709754346579-add_table_comments_to_pr.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTableCommentsToPr1709754346579 implements MigrationInterface { + name = 'AddTableCommentsToPr1709754346579'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_review" IS 'A review of a local government or municipalities plan'`, + ); + await queryRunner.query( + `COMMENT ON TABLE "alcs"."planning_referral" IS 'Planning Referrals represent each pass of a Planning Review with their own cards'`, + ); + } + + public async down(): Promise<void> { + //No + } +}